Compare commits
3 Commits
main
..
00ca94a9f3
| Author | SHA1 | Date | |
|---|---|---|---|
| 00ca94a9f3 | |||
| 8a1511f689 | |||
| 26b147b929 |
@@ -181,3 +181,5 @@ fi
|
||||
# ble.sh
|
||||
# https://github.com/akinomyoga/ble.sh#13-set-up-bashrc
|
||||
[[ ${BLE_VERSION-} ]] && ble-attach
|
||||
|
||||
if command -v wt >/dev/null 2>&1; then eval "$(command wt config shell init bash)"; fi
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
{
|
||||
"attribution": {
|
||||
"commit": "",
|
||||
"pr": ""
|
||||
},
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(npx tsc:*)",
|
||||
"Bash(pnpm run build:*)",
|
||||
"Bash(pnpm tsc:*)",
|
||||
"Bash(pnpm build:*)",
|
||||
"Bash(pnpm test:*)",
|
||||
"Bash(pnpm install:*)",
|
||||
"Bash(git diff:*)"
|
||||
],
|
||||
"deny": [
|
||||
"Bash(git push *)",
|
||||
"Bash(ssh *)",
|
||||
"Bash(ssh)",
|
||||
"Bash(scp *)"
|
||||
],
|
||||
"defaultMode": "auto"
|
||||
},
|
||||
"hooks": {
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "~/.claude/hooks/block-dangerous-git.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"statusLine": {
|
||||
"type": "command",
|
||||
"command": "bash /home/tgrosinger/.claude/statusline-command.sh"
|
||||
},
|
||||
"enabledPlugins": {
|
||||
"pr-review-toolkit@claude-plugins-official": true,
|
||||
"gopls-lsp@claude-plugins-official": true,
|
||||
"frontend-design@claude-plugins-official": true,
|
||||
"code-simplifier@claude-plugins-official": true,
|
||||
"skill-creator@claude-plugins-official": true,
|
||||
"typescript-lsp@claude-plugins-official": true,
|
||||
"claude-md-management@claude-plugins-official": true,
|
||||
"security-guidance@claude-plugins-official": true
|
||||
},
|
||||
"sandbox": {
|
||||
"enabled": true,
|
||||
"autoAllowBashIfSandboxed": true,
|
||||
"allowUnsandboxedCommands": false,
|
||||
"network": {
|
||||
"allowedDomains": [
|
||||
"github.com",
|
||||
"*.github.com",
|
||||
"*.githubusercontent.com",
|
||||
"registry.npmjs.org",
|
||||
"proxy.golang.org",
|
||||
"sum.golang.org",
|
||||
"cache.nixos.org",
|
||||
"nodejs.org",
|
||||
"go.dev",
|
||||
"dl.google.com",
|
||||
"mise.en.dev"
|
||||
]
|
||||
},
|
||||
"filesystem": {
|
||||
"allowWrite": [
|
||||
"~/.local/share/pnpm",
|
||||
"~/.cache/pnpm"
|
||||
],
|
||||
"denyRead": [
|
||||
"~/.ssh",
|
||||
"~/.config/Signal",
|
||||
"~/Documents"
|
||||
]
|
||||
},
|
||||
"excludedCommands": [
|
||||
"git push *",
|
||||
"brew *",
|
||||
"devbox add *",
|
||||
"nix *"
|
||||
]
|
||||
},
|
||||
"spinnerVerbs": {
|
||||
"mode": "replace",
|
||||
"verbs": [
|
||||
"Thinking"
|
||||
]
|
||||
},
|
||||
"effortLevel": "high",
|
||||
"tui": "fullscreen",
|
||||
"voice": {
|
||||
"enabled": false,
|
||||
"mode": "hold"
|
||||
},
|
||||
"prefersReducedMotion": true,
|
||||
"autoMemoryEnabled": true,
|
||||
"autoDreamEnabled": true,
|
||||
"theme": "light",
|
||||
"editorMode": "vim",
|
||||
"agentPushNotifEnabled": true,
|
||||
"skipAutoPermissionPrompt": true
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
---
|
||||
name: brainstorming
|
||||
description: "You MUST use this before any creative work - creating features, building components, adding functionality, or modifying behavior. Explores user intent, requirements and design before implementation."
|
||||
source: Modified from https://github.com/obra/superpowers/blob/main/skills/brainstorming/SKILL.md
|
||||
---
|
||||
|
||||
# Brainstorming Ideas Into Designs
|
||||
|
||||
Help turn ideas into fully formed designs and specs through natural collaborative dialogue.
|
||||
|
||||
Start by understanding the current project context, then ask questions one at a time to refine the idea. Once you understand what you're building, present the design and get user approval.
|
||||
|
||||
<HARD-GATE>
|
||||
Do NOT invoke any implementation skill, write any code, scaffold any project, or take any implementation action until you have presented a design and the user has approved it. This applies to EVERY project regardless of perceived simplicity.
|
||||
</HARD-GATE>
|
||||
|
||||
## Anti-Pattern: "This Is Too Simple To Need A Design"
|
||||
|
||||
Every project goes through this process. A todo list, a single-function utility, a config change — all of them. "Simple" projects are where unexamined assumptions cause the most wasted work. The design can be short (a few sentences for truly simple projects), but you MUST present it and get approval.
|
||||
|
||||
## Checklist
|
||||
|
||||
You MUST create a task for each of these items and complete them in order:
|
||||
|
||||
1. **Explore project context** — check files, docs, recent commits
|
||||
3. **Ask clarifying questions** — one at a time, understand purpose/constraints/success criteria
|
||||
4. **Propose 2-3 approaches** — with trade-offs and your recommendation
|
||||
5. **Present design** — in sections scaled to their complexity, get user approval after each section
|
||||
6. **Write design doc** — save to `docs/superpowers/specs/YYYY-MM-DD-<topic>-design.md` and commit
|
||||
7. **Spec self-review** — quick inline check for placeholders, contradictions, ambiguity, scope (see below)
|
||||
8. **User reviews written spec** — ask user to review the spec file before proceeding
|
||||
9. **Transition to implementation** — invoke skills such as grill-with-docs or to-prd to create implementation plan
|
||||
|
||||
## Process Flow
|
||||
|
||||
```dot
|
||||
digraph brainstorming {
|
||||
"Explore project context" [shape=box];
|
||||
"Visual questions ahead?" [shape=diamond];
|
||||
"Ask clarifying questions" [shape=box];
|
||||
"Propose 2-3 approaches" [shape=box];
|
||||
"Present design sections" [shape=box];
|
||||
"User approves design?" [shape=diamond];
|
||||
"Write design doc" [shape=box];
|
||||
"Spec self-review\n(fix inline)" [shape=box];
|
||||
"User reviews spec?" [shape=diamond];
|
||||
"Transition to implementation" [shape=doublecircle];
|
||||
|
||||
"Explore project context" -> "Visual questions ahead?";
|
||||
"Visual questions ahead?" -> "Ask clarifying questions" [label="no"];
|
||||
"Ask clarifying questions" -> "Propose 2-3 approaches";
|
||||
"Propose 2-3 approaches" -> "Present design sections";
|
||||
"Present design sections" -> "User approves design?";
|
||||
"User approves design?" -> "Present design sections" [label="no, revise"];
|
||||
"User approves design?" -> "Write design doc" [label="yes"];
|
||||
"Write design doc" -> "Spec self-review\n(fix inline)";
|
||||
"Spec self-review\n(fix inline)" -> "User reviews spec?";
|
||||
"User reviews spec?" -> "Write design doc" [label="changes requested"];
|
||||
"User reviews spec?" -> "Transition to implementation" [label="approved"];
|
||||
}
|
||||
```
|
||||
|
||||
**The terminal state is writing a plan.** Do NOT invoke frontend-design, mcp-builder, or any other implementation skill. The ONLY skills you invoke after brainstorming are grill-with-docs or to-prd.
|
||||
|
||||
## The Process
|
||||
|
||||
**Understanding the idea:**
|
||||
|
||||
- Check out the current project state first (files, docs, recent commits)
|
||||
- Before asking detailed questions, assess scope: if the request describes multiple independent subsystems (e.g., "build a platform with chat, file storage, billing, and analytics"), flag this immediately. Don't spend questions refining details of a project that needs to be decomposed first.
|
||||
- If the project is too large for a single spec, help the user decompose into sub-projects: what are the independent pieces, how do they relate, what order should they be built? Then brainstorm the first sub-project through the normal design flow. Each sub-project gets its own spec → plan → implementation cycle.
|
||||
- For appropriately-scoped projects, ask questions one at a time to refine the idea
|
||||
- Prefer multiple choice questions when possible, but open-ended is fine too
|
||||
- Only one question per message - if a topic needs more exploration, break it into multiple questions
|
||||
- Focus on understanding: purpose, constraints, success criteria
|
||||
|
||||
**Exploring approaches:**
|
||||
|
||||
- Propose 2-3 different approaches with trade-offs
|
||||
- Present options conversationally with your recommendation and reasoning
|
||||
- Lead with your recommended option and explain why
|
||||
|
||||
**Presenting the design:**
|
||||
|
||||
- Once you believe you understand what you're building, present the design
|
||||
- Scale each section to its complexity: a few sentences if straightforward, up to 200-300 words if nuanced
|
||||
- Ask after each section whether it looks right so far
|
||||
- Cover: architecture, components, data flow, error handling, testing
|
||||
- Be ready to go back and clarify if something doesn't make sense
|
||||
|
||||
**Design for isolation and clarity:**
|
||||
|
||||
- Break the system into smaller units that each have one clear purpose, communicate through well-defined interfaces, and can be understood and tested independently
|
||||
- For each unit, you should be able to answer: what does it do, how do you use it, and what does it depend on?
|
||||
- Can someone understand what a unit does without reading its internals? Can you change the internals without breaking consumers? If not, the boundaries need work.
|
||||
- Smaller, well-bounded units are also easier for you to work with - you reason better about code you can hold in context at once, and your edits are more reliable when files are focused. When a file grows large, that's often a signal that it's doing too much.
|
||||
|
||||
**Working in existing codebases:**
|
||||
|
||||
- Explore the current structure before proposing changes. Follow existing patterns.
|
||||
- Where existing code has problems that affect the work (e.g., a file that's grown too large, unclear boundaries, tangled responsibilities), include targeted improvements as part of the design - the way a good developer improves code they're working in.
|
||||
- Don't propose unrelated refactoring. Stay focused on what serves the current goal.
|
||||
|
||||
## After the Design
|
||||
|
||||
**Documentation:**
|
||||
|
||||
- Write the validated design (spec) to `docs/superpowers/specs/YYYY-MM-DD-<topic>-design.md`
|
||||
- (User preferences for spec location override this default)
|
||||
- Use elements-of-style:writing-clearly-and-concisely skill if available
|
||||
- Commit the design document to git
|
||||
|
||||
**Spec Self-Review:**
|
||||
After writing the spec document, look at it with fresh eyes:
|
||||
|
||||
1. **Placeholder scan:** Any "TBD", "TODO", incomplete sections, or vague requirements? Fix them.
|
||||
2. **Internal consistency:** Do any sections contradict each other? Does the architecture match the feature descriptions?
|
||||
3. **Scope check:** Is this focused enough for a single implementation plan, or does it need decomposition?
|
||||
4. **Ambiguity check:** Could any requirement be interpreted two different ways? If so, pick one and make it explicit.
|
||||
|
||||
Fix any issues inline. No need to re-review — just fix and move on.
|
||||
|
||||
**User Review Gate:**
|
||||
After the spec review loop passes, ask the user to review the written spec before proceeding:
|
||||
|
||||
> "Spec written and committed to `<path>`. Please review it and let me know if you want to make any changes before we start writing out the implementation plan."
|
||||
|
||||
Wait for the user's response. If they request changes, make them and re-run the spec review loop. Only proceed once the user approves.
|
||||
|
||||
**Implementation:**
|
||||
|
||||
- Invoke the grill-with-docs skill to create a detailed implementation plan
|
||||
- Do NOT invoke any other skill. grill-with-docs is the next step.
|
||||
|
||||
## Key Principles
|
||||
|
||||
- **One question at a time** - Don't overwhelm with multiple questions
|
||||
- **Multiple choice preferred** - Easier to answer than open-ended when possible
|
||||
- **YAGNI ruthlessly** - Remove unnecessary features from all designs
|
||||
- **Explore alternatives** - Always propose 2-3 approaches before settling
|
||||
- **Incremental validation** - Present design, get approval before moving on
|
||||
- **Be flexible** - Go back and clarify when something doesn't make sense
|
||||
|
||||
@@ -1,644 +0,0 @@
|
||||
---
|
||||
name: json-canvas
|
||||
description: Create and edit JSON Canvas files (.canvas) with nodes, edges, groups, and connections. Use when working with .canvas files, creating visual canvases, mind maps, flowcharts, or when the user mentions Canvas files in Obsidian.
|
||||
source: https://github.com/kepano/obsidian-skills/blob/main/skills/json-canvas/SKILL.md
|
||||
---
|
||||
|
||||
# JSON Canvas Skill
|
||||
|
||||
This skill enables Claude Code to create and edit valid JSON Canvas files (`.canvas`) used in Obsidian and other applications.
|
||||
|
||||
## Overview
|
||||
|
||||
JSON Canvas is an open file format for infinite canvas data. Canvas files use the `.canvas` extension and contain valid JSON following the [JSON Canvas Spec 1.0](https://jsoncanvas.org/spec/1.0/).
|
||||
|
||||
## File Structure
|
||||
|
||||
A canvas file contains two top-level arrays:
|
||||
|
||||
```json
|
||||
{
|
||||
"nodes": [],
|
||||
"edges": []
|
||||
}
|
||||
```
|
||||
|
||||
- `nodes` (optional): Array of node objects
|
||||
- `edges` (optional): Array of edge objects connecting nodes
|
||||
|
||||
## Nodes
|
||||
|
||||
Nodes are objects placed on the canvas. There are four node types:
|
||||
- `text` - Text content with Markdown
|
||||
- `file` - Reference to files/attachments
|
||||
- `link` - External URL
|
||||
- `group` - Visual container for other nodes
|
||||
|
||||
### Z-Index Ordering
|
||||
|
||||
Nodes are ordered by z-index in the array:
|
||||
- First node = bottom layer (displayed below others)
|
||||
- Last node = top layer (displayed above others)
|
||||
|
||||
### Generic Node Attributes
|
||||
|
||||
All nodes share these attributes:
|
||||
|
||||
| Attribute | Required | Type | Description |
|
||||
|-----------|----------|------|-------------|
|
||||
| `id` | Yes | string | Unique identifier for the node |
|
||||
| `type` | Yes | string | Node type: `text`, `file`, `link`, or `group` |
|
||||
| `x` | Yes | integer | X position in pixels |
|
||||
| `y` | Yes | integer | Y position in pixels |
|
||||
| `width` | Yes | integer | Width in pixels |
|
||||
| `height` | Yes | integer | Height in pixels |
|
||||
| `color` | No | canvasColor | Node color (see Color section) |
|
||||
|
||||
### Text Nodes
|
||||
|
||||
Text nodes contain Markdown content.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "6f0ad84f44ce9c17",
|
||||
"type": "text",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 400,
|
||||
"height": 200,
|
||||
"text": "# Hello World\n\nThis is **Markdown** content."
|
||||
}
|
||||
```
|
||||
|
||||
| Attribute | Required | Type | Description |
|
||||
|-----------|----------|------|-------------|
|
||||
| `text` | Yes | string | Plain text with Markdown syntax |
|
||||
|
||||
### File Nodes
|
||||
|
||||
File nodes reference files or attachments (images, videos, PDFs, notes, etc.).
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "a1b2c3d4e5f67890",
|
||||
"type": "file",
|
||||
"x": 500,
|
||||
"y": 0,
|
||||
"width": 400,
|
||||
"height": 300,
|
||||
"file": "Attachments/diagram.png"
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "b2c3d4e5f6789012",
|
||||
"type": "file",
|
||||
"x": 500,
|
||||
"y": 400,
|
||||
"width": 400,
|
||||
"height": 300,
|
||||
"file": "Notes/Project Overview.md",
|
||||
"subpath": "#Implementation"
|
||||
}
|
||||
```
|
||||
|
||||
| Attribute | Required | Type | Description |
|
||||
|-----------|----------|------|-------------|
|
||||
| `file` | Yes | string | Path to file within the system |
|
||||
| `subpath` | No | string | Link to heading or block (starts with `#`) |
|
||||
|
||||
### Link Nodes
|
||||
|
||||
Link nodes display external URLs.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "c3d4e5f678901234",
|
||||
"type": "link",
|
||||
"x": 1000,
|
||||
"y": 0,
|
||||
"width": 400,
|
||||
"height": 200,
|
||||
"url": "https://obsidian.md"
|
||||
}
|
||||
```
|
||||
|
||||
| Attribute | Required | Type | Description |
|
||||
|-----------|----------|------|-------------|
|
||||
| `url` | Yes | string | External URL |
|
||||
|
||||
### Group Nodes
|
||||
|
||||
Group nodes are visual containers for organizing other nodes.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "d4e5f6789012345a",
|
||||
"type": "group",
|
||||
"x": -50,
|
||||
"y": -50,
|
||||
"width": 1000,
|
||||
"height": 600,
|
||||
"label": "Project Overview",
|
||||
"color": "4"
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "e5f67890123456ab",
|
||||
"type": "group",
|
||||
"x": 0,
|
||||
"y": 700,
|
||||
"width": 800,
|
||||
"height": 500,
|
||||
"label": "Resources",
|
||||
"background": "Attachments/background.png",
|
||||
"backgroundStyle": "cover"
|
||||
}
|
||||
```
|
||||
|
||||
| Attribute | Required | Type | Description |
|
||||
|-----------|----------|------|-------------|
|
||||
| `label` | No | string | Text label for the group |
|
||||
| `background` | No | string | Path to background image |
|
||||
| `backgroundStyle` | No | string | Background rendering style |
|
||||
|
||||
#### Background Styles
|
||||
|
||||
| Value | Description |
|
||||
|-------|-------------|
|
||||
| `cover` | Fills entire width and height of node |
|
||||
| `ratio` | Maintains aspect ratio of background image |
|
||||
| `repeat` | Repeats image as pattern in both directions |
|
||||
|
||||
## Edges
|
||||
|
||||
Edges are lines connecting nodes.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "f67890123456789a",
|
||||
"fromNode": "6f0ad84f44ce9c17",
|
||||
"toNode": "a1b2c3d4e5f67890"
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "0123456789abcdef",
|
||||
"fromNode": "6f0ad84f44ce9c17",
|
||||
"fromSide": "right",
|
||||
"fromEnd": "none",
|
||||
"toNode": "b2c3d4e5f6789012",
|
||||
"toSide": "left",
|
||||
"toEnd": "arrow",
|
||||
"color": "1",
|
||||
"label": "leads to"
|
||||
}
|
||||
```
|
||||
|
||||
| Attribute | Required | Type | Default | Description |
|
||||
|-----------|----------|------|---------|-------------|
|
||||
| `id` | Yes | string | - | Unique identifier for the edge |
|
||||
| `fromNode` | Yes | string | - | Node ID where connection starts |
|
||||
| `fromSide` | No | string | - | Side where edge starts |
|
||||
| `fromEnd` | No | string | `none` | Shape at edge start |
|
||||
| `toNode` | Yes | string | - | Node ID where connection ends |
|
||||
| `toSide` | No | string | - | Side where edge ends |
|
||||
| `toEnd` | No | string | `arrow` | Shape at edge end |
|
||||
| `color` | No | canvasColor | - | Line color |
|
||||
| `label` | No | string | - | Text label for the edge |
|
||||
|
||||
### Side Values
|
||||
|
||||
| Value | Description |
|
||||
|-------|-------------|
|
||||
| `top` | Top edge of node |
|
||||
| `right` | Right edge of node |
|
||||
| `bottom` | Bottom edge of node |
|
||||
| `left` | Left edge of node |
|
||||
|
||||
### End Shapes
|
||||
|
||||
| Value | Description |
|
||||
|-------|-------------|
|
||||
| `none` | No endpoint shape |
|
||||
| `arrow` | Arrow endpoint |
|
||||
|
||||
## Colors
|
||||
|
||||
The `canvasColor` type can be specified in two ways:
|
||||
|
||||
### Hex Colors
|
||||
|
||||
```json
|
||||
{
|
||||
"color": "#FF0000"
|
||||
}
|
||||
```
|
||||
|
||||
### Preset Colors
|
||||
|
||||
```json
|
||||
{
|
||||
"color": "1"
|
||||
}
|
||||
```
|
||||
|
||||
| Preset | Color |
|
||||
|--------|-------|
|
||||
| `"1"` | Red |
|
||||
| `"2"` | Orange |
|
||||
| `"3"` | Yellow |
|
||||
| `"4"` | Green |
|
||||
| `"5"` | Cyan |
|
||||
| `"6"` | Purple |
|
||||
|
||||
Note: Specific color values for presets are intentionally undefined, allowing applications to use their own brand colors.
|
||||
|
||||
## Complete Examples
|
||||
|
||||
### Simple Canvas with Text and Connections
|
||||
|
||||
```json
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "8a9b0c1d2e3f4a5b",
|
||||
"type": "text",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 300,
|
||||
"height": 150,
|
||||
"text": "# Main Idea\n\nThis is the central concept."
|
||||
},
|
||||
{
|
||||
"id": "1a2b3c4d5e6f7a8b",
|
||||
"type": "text",
|
||||
"x": 400,
|
||||
"y": -100,
|
||||
"width": 250,
|
||||
"height": 100,
|
||||
"text": "## Supporting Point A\n\nDetails here."
|
||||
},
|
||||
{
|
||||
"id": "2b3c4d5e6f7a8b9c",
|
||||
"type": "text",
|
||||
"x": 400,
|
||||
"y": 100,
|
||||
"width": 250,
|
||||
"height": 100,
|
||||
"text": "## Supporting Point B\n\nMore details."
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "3c4d5e6f7a8b9c0d",
|
||||
"fromNode": "8a9b0c1d2e3f4a5b",
|
||||
"fromSide": "right",
|
||||
"toNode": "1a2b3c4d5e6f7a8b",
|
||||
"toSide": "left"
|
||||
},
|
||||
{
|
||||
"id": "4d5e6f7a8b9c0d1e",
|
||||
"fromNode": "8a9b0c1d2e3f4a5b",
|
||||
"fromSide": "right",
|
||||
"toNode": "2b3c4d5e6f7a8b9c",
|
||||
"toSide": "left"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Project Board with Groups
|
||||
|
||||
```json
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "5e6f7a8b9c0d1e2f",
|
||||
"type": "group",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 300,
|
||||
"height": 500,
|
||||
"label": "To Do",
|
||||
"color": "1"
|
||||
},
|
||||
{
|
||||
"id": "6f7a8b9c0d1e2f3a",
|
||||
"type": "group",
|
||||
"x": 350,
|
||||
"y": 0,
|
||||
"width": 300,
|
||||
"height": 500,
|
||||
"label": "In Progress",
|
||||
"color": "3"
|
||||
},
|
||||
{
|
||||
"id": "7a8b9c0d1e2f3a4b",
|
||||
"type": "group",
|
||||
"x": 700,
|
||||
"y": 0,
|
||||
"width": 300,
|
||||
"height": 500,
|
||||
"label": "Done",
|
||||
"color": "4"
|
||||
},
|
||||
{
|
||||
"id": "8b9c0d1e2f3a4b5c",
|
||||
"type": "text",
|
||||
"x": 20,
|
||||
"y": 50,
|
||||
"width": 260,
|
||||
"height": 80,
|
||||
"text": "## Task 1\n\nImplement feature X"
|
||||
},
|
||||
{
|
||||
"id": "9c0d1e2f3a4b5c6d",
|
||||
"type": "text",
|
||||
"x": 370,
|
||||
"y": 50,
|
||||
"width": 260,
|
||||
"height": 80,
|
||||
"text": "## Task 2\n\nReview PR #123",
|
||||
"color": "2"
|
||||
},
|
||||
{
|
||||
"id": "0d1e2f3a4b5c6d7e",
|
||||
"type": "text",
|
||||
"x": 720,
|
||||
"y": 50,
|
||||
"width": 260,
|
||||
"height": 80,
|
||||
"text": "## Task 3\n\n~~Setup CI/CD~~"
|
||||
}
|
||||
],
|
||||
"edges": []
|
||||
}
|
||||
```
|
||||
|
||||
### Research Canvas with Files and Links
|
||||
|
||||
```json
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "1e2f3a4b5c6d7e8f",
|
||||
"type": "text",
|
||||
"x": 300,
|
||||
"y": 200,
|
||||
"width": 400,
|
||||
"height": 200,
|
||||
"text": "# Research Topic\n\n## Key Questions\n\n- How does X affect Y?\n- What are the implications?",
|
||||
"color": "5"
|
||||
},
|
||||
{
|
||||
"id": "2f3a4b5c6d7e8f9a",
|
||||
"type": "file",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 250,
|
||||
"height": 150,
|
||||
"file": "Literature/Paper A.pdf"
|
||||
},
|
||||
{
|
||||
"id": "3a4b5c6d7e8f9a0b",
|
||||
"type": "file",
|
||||
"x": 0,
|
||||
"y": 200,
|
||||
"width": 250,
|
||||
"height": 150,
|
||||
"file": "Notes/Meeting Notes.md",
|
||||
"subpath": "#Key Insights"
|
||||
},
|
||||
{
|
||||
"id": "4b5c6d7e8f9a0b1c",
|
||||
"type": "link",
|
||||
"x": 0,
|
||||
"y": 400,
|
||||
"width": 250,
|
||||
"height": 100,
|
||||
"url": "https://example.com/research"
|
||||
},
|
||||
{
|
||||
"id": "5c6d7e8f9a0b1c2d",
|
||||
"type": "file",
|
||||
"x": 750,
|
||||
"y": 150,
|
||||
"width": 300,
|
||||
"height": 250,
|
||||
"file": "Attachments/diagram.png"
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "6d7e8f9a0b1c2d3e",
|
||||
"fromNode": "2f3a4b5c6d7e8f9a",
|
||||
"fromSide": "right",
|
||||
"toNode": "1e2f3a4b5c6d7e8f",
|
||||
"toSide": "left",
|
||||
"label": "supports"
|
||||
},
|
||||
{
|
||||
"id": "7e8f9a0b1c2d3e4f",
|
||||
"fromNode": "3a4b5c6d7e8f9a0b",
|
||||
"fromSide": "right",
|
||||
"toNode": "1e2f3a4b5c6d7e8f",
|
||||
"toSide": "left",
|
||||
"label": "informs"
|
||||
},
|
||||
{
|
||||
"id": "8f9a0b1c2d3e4f5a",
|
||||
"fromNode": "4b5c6d7e8f9a0b1c",
|
||||
"fromSide": "right",
|
||||
"toNode": "1e2f3a4b5c6d7e8f",
|
||||
"toSide": "left",
|
||||
"toEnd": "arrow",
|
||||
"color": "6"
|
||||
},
|
||||
{
|
||||
"id": "9a0b1c2d3e4f5a6b",
|
||||
"fromNode": "1e2f3a4b5c6d7e8f",
|
||||
"fromSide": "right",
|
||||
"toNode": "5c6d7e8f9a0b1c2d",
|
||||
"toSide": "left",
|
||||
"label": "visualized by"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Flowchart
|
||||
|
||||
```json
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "a0b1c2d3e4f5a6b7",
|
||||
"type": "text",
|
||||
"x": 200,
|
||||
"y": 0,
|
||||
"width": 150,
|
||||
"height": 60,
|
||||
"text": "**Start**",
|
||||
"color": "4"
|
||||
},
|
||||
{
|
||||
"id": "b1c2d3e4f5a6b7c8",
|
||||
"type": "text",
|
||||
"x": 200,
|
||||
"y": 100,
|
||||
"width": 150,
|
||||
"height": 60,
|
||||
"text": "Step 1:\nGather data"
|
||||
},
|
||||
{
|
||||
"id": "c2d3e4f5a6b7c8d9",
|
||||
"type": "text",
|
||||
"x": 200,
|
||||
"y": 200,
|
||||
"width": 150,
|
||||
"height": 80,
|
||||
"text": "**Decision**\n\nIs data valid?",
|
||||
"color": "3"
|
||||
},
|
||||
{
|
||||
"id": "d3e4f5a6b7c8d9e0",
|
||||
"type": "text",
|
||||
"x": 400,
|
||||
"y": 200,
|
||||
"width": 150,
|
||||
"height": 60,
|
||||
"text": "Process data"
|
||||
},
|
||||
{
|
||||
"id": "e4f5a6b7c8d9e0f1",
|
||||
"type": "text",
|
||||
"x": 0,
|
||||
"y": 200,
|
||||
"width": 150,
|
||||
"height": 60,
|
||||
"text": "Request new data",
|
||||
"color": "1"
|
||||
},
|
||||
{
|
||||
"id": "f5a6b7c8d9e0f1a2",
|
||||
"type": "text",
|
||||
"x": 400,
|
||||
"y": 320,
|
||||
"width": 150,
|
||||
"height": 60,
|
||||
"text": "**End**",
|
||||
"color": "4"
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "a6b7c8d9e0f1a2b3",
|
||||
"fromNode": "a0b1c2d3e4f5a6b7",
|
||||
"fromSide": "bottom",
|
||||
"toNode": "b1c2d3e4f5a6b7c8",
|
||||
"toSide": "top"
|
||||
},
|
||||
{
|
||||
"id": "b7c8d9e0f1a2b3c4",
|
||||
"fromNode": "b1c2d3e4f5a6b7c8",
|
||||
"fromSide": "bottom",
|
||||
"toNode": "c2d3e4f5a6b7c8d9",
|
||||
"toSide": "top"
|
||||
},
|
||||
{
|
||||
"id": "c8d9e0f1a2b3c4d5",
|
||||
"fromNode": "c2d3e4f5a6b7c8d9",
|
||||
"fromSide": "right",
|
||||
"toNode": "d3e4f5a6b7c8d9e0",
|
||||
"toSide": "left",
|
||||
"label": "Yes",
|
||||
"color": "4"
|
||||
},
|
||||
{
|
||||
"id": "d9e0f1a2b3c4d5e6",
|
||||
"fromNode": "c2d3e4f5a6b7c8d9",
|
||||
"fromSide": "left",
|
||||
"toNode": "e4f5a6b7c8d9e0f1",
|
||||
"toSide": "right",
|
||||
"label": "No",
|
||||
"color": "1"
|
||||
},
|
||||
{
|
||||
"id": "e0f1a2b3c4d5e6f7",
|
||||
"fromNode": "e4f5a6b7c8d9e0f1",
|
||||
"fromSide": "top",
|
||||
"fromEnd": "none",
|
||||
"toNode": "b1c2d3e4f5a6b7c8",
|
||||
"toSide": "left",
|
||||
"toEnd": "arrow"
|
||||
},
|
||||
{
|
||||
"id": "f1a2b3c4d5e6f7a8",
|
||||
"fromNode": "d3e4f5a6b7c8d9e0",
|
||||
"fromSide": "bottom",
|
||||
"toNode": "f5a6b7c8d9e0f1a2",
|
||||
"toSide": "top"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## ID Generation
|
||||
|
||||
Node and edge IDs must be unique strings. Obsidian generates 16-character hexadecimal IDs:
|
||||
|
||||
```json
|
||||
"id": "6f0ad84f44ce9c17"
|
||||
"id": "a3b2c1d0e9f8g7h6"
|
||||
"id": "1234567890abcdef"
|
||||
```
|
||||
|
||||
This format is a 16-character lowercase hex string (64-bit random value).
|
||||
|
||||
## Layout Guidelines
|
||||
|
||||
### Positioning
|
||||
|
||||
- Coordinates can be negative (canvas extends infinitely)
|
||||
- `x` increases to the right
|
||||
- `y` increases downward
|
||||
- Position refers to top-left corner of node
|
||||
|
||||
### Recommended Sizes
|
||||
|
||||
| Node Type | Suggested Width | Suggested Height |
|
||||
|-----------|-----------------|------------------|
|
||||
| Small text | 200-300 | 80-150 |
|
||||
| Medium text | 300-450 | 150-300 |
|
||||
| Large text | 400-600 | 300-500 |
|
||||
| File preview | 300-500 | 200-400 |
|
||||
| Link preview | 250-400 | 100-200 |
|
||||
| Group | Varies | Varies |
|
||||
|
||||
### Spacing
|
||||
|
||||
- Leave 20-50px padding inside groups
|
||||
- Space nodes 50-100px apart for readability
|
||||
- Align nodes to grid (multiples of 10 or 20) for cleaner layouts
|
||||
|
||||
## Validation Rules
|
||||
|
||||
1. All `id` values must be unique across nodes and edges
|
||||
2. `fromNode` and `toNode` must reference existing node IDs
|
||||
3. Required fields must be present for each node type
|
||||
4. `type` must be one of: `text`, `file`, `link`, `group`
|
||||
5. `backgroundStyle` must be one of: `cover`, `ratio`, `repeat`
|
||||
6. `fromSide`, `toSide` must be one of: `top`, `right`, `bottom`, `left`
|
||||
7. `fromEnd`, `toEnd` must be one of: `none`, `arrow`
|
||||
8. Color presets must be `"1"` through `"6"` or valid hex color
|
||||
|
||||
## References
|
||||
|
||||
- [JSON Canvas Spec 1.0](https://jsoncanvas.org/spec/1.0/)
|
||||
- [JSON Canvas GitHub](https://github.com/obsidianmd/jsoncanvas)
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
---
|
||||
name: magpie-review
|
||||
description: Run a multi-AI adversarial code review using the `magpie` CLI. Multiple AI models independently review the changes, debate findings, and a verifier audits each issue against the actual code. Use whenever the user asks for a "magpie review", a "multi-AI review", an "adversarial review", a "second opinion review", or wants magpie to look at local uncommitted changes, the current branch, or a GitHub PR. Also use when the user wants to `magpie discuss` a topic or do a whole-repo review.
|
||||
---
|
||||
|
||||
# magpie-review
|
||||
|
||||
Wraps the `magpie` CLI to run adversarial multi-AI code reviews. Magpie spawns several reviewer models (Claude Code, Codex, Gemini, etc.) that independently review a change, debate across rounds, then a verifier audits each reported issue against the actual code.
|
||||
|
||||
## When to pick which mode
|
||||
|
||||
Magpie supports three review targets — pick based on what the user is reviewing:
|
||||
|
||||
| User's intent | Command |
|
||||
|---|---|
|
||||
| Review work I haven't committed yet (staged + unstaged) | `magpie review --local` |
|
||||
| Review the commits on my current branch vs a base | `magpie review --branch [base]` (base defaults to `main`) |
|
||||
| Review a GitHub PR | `magpie review <pr-number>` or `magpie review <pr-url>` |
|
||||
| Review specific files only | `magpie review --files <path> [path...]` |
|
||||
| Review the entire repository | `magpie review --repo` |
|
||||
| Discuss a topic / design question | `magpie discuss "<topic>"` or `magpie discuss <path-to-file.md>` |
|
||||
|
||||
If the user is ambiguous (e.g. "review my changes"), check `git status` and `git log @{u}..HEAD` to figure out whether they mean uncommitted, committed-but-unpushed, or already-pushed work — then pick the matching mode rather than guessing.
|
||||
|
||||
## Running a review
|
||||
|
||||
Always run `magpie` from inside the target repo's working tree. For PR mode it uses the `origin` remote to find the repo by default.
|
||||
|
||||
### Common flags
|
||||
|
||||
These apply to both `review` and `discuss` unless noted:
|
||||
|
||||
- `-i, --interactive` — Pause between turns for Q&A. Use when the user wants to drive the review themselves.
|
||||
- `-a, --all` — Use every configured reviewer without an interactive picker. Good for non-interactive/batch runs.
|
||||
- `--reviewers <ids>` — Comma-separated reviewer IDs (e.g. `claude-code,gemini-cli`) when you want a specific subset.
|
||||
- `-r, --rounds <n>` — Cap the debate rounds (default 5).
|
||||
- `--no-converge` — Disable early-stop on consensus. Use when you want the full debate even if reviewers agree quickly.
|
||||
- `-o, --output <file>` and `-f, --format <markdown|json>` — Save results to a file.
|
||||
- `--fail-fast` — Abort the whole flow if any reviewer fails. Default is resilient (continues with surviving reviewers). Use fail-fast when debugging provider/auth issues or when the user wants a guarantee every reviewer participated.
|
||||
- `--plan-only` — Generate the review plan without running reviewers. Useful for a quick preview of what magpie *would* do.
|
||||
|
||||
### `review`-only flags worth knowing
|
||||
|
||||
- `--skip-context` — Skip the context-gathering phase (call chains, related PRs). Faster, less informed.
|
||||
- `--no-post` — Skip the post-debate GitHub comment-posting flow. Use in non-interactive contexts where you just want the review output, not the interactive post-each-issue loop.
|
||||
- `--no-conclusion` — Skip the final summarizer. Useful for bot/CI use.
|
||||
- `--git-remote <remote>` — Override the remote used for PR-URL detection (default `origin`).
|
||||
- `--reanalyze` — Bypass the analyzer cache and re-analyze from scratch.
|
||||
|
||||
### Repo-mode flags (with `--repo`)
|
||||
|
||||
- `--path <subdir>` — Limit the repo review to a subdirectory.
|
||||
- `--ignore <patterns...>` — Skip matching paths.
|
||||
- `--quick` — Architecture overview only.
|
||||
- `--deep` — Full analysis, no prompts.
|
||||
- `--list-sessions` / `--session <id>` / `--export <file>` — Manage long-running repo review sessions (they persist so you can pause and resume).
|
||||
|
||||
## Interactive vs non-interactive
|
||||
|
||||
Magpie's default flow includes interactive prompts (reviewer selection, per-issue post/edit/skip after the debate). When you (Claude) are invoking magpie programmatically on the user's behalf:
|
||||
|
||||
- Prefer `-a` (or `--reviewers`) to skip the reviewer-selection prompt.
|
||||
- Prefer `--no-post` to skip the per-issue posting loop — the user can still read the review output.
|
||||
- If you want a clean machine-readable result, add `-f json -o <file>`.
|
||||
|
||||
When the user wants to drive the review themselves, hand the command back to them to run (e.g. via `! magpie review ...`) so they get the interactive UX rather than running it through a tool call.
|
||||
|
||||
## Running it as a long task (it takes minutes)
|
||||
|
||||
A full review runs several models across multiple debate rounds, so it takes minutes. Run it as a background `Bash` task with `run_in_background: true` and save the output (`-f markdown -o <file>`). The background task auto-notifies you when it completes — that completion notification is all you need; read the saved output file then.
|
||||
|
||||
- **Do not add a separate `Monitor` on the same output file.** The background task already notifies on completion, so a monitor watching the same file is redundant. Only add a `Monitor` if you genuinely need streamed interim progress, and even then it is usually unnecessary for a fire-and-forget review.
|
||||
- **Never copy the output path by hand.** Pass the same explicit `-o <file>` path you chose (e.g. `/tmp/magpie-review.md`) to your follow-up `Read` — don't transcribe the long auto-generated task-output path from the completion notification, which is easy to typo.
|
||||
- **Don't suppress stderr** (`2>/dev/null`) on magpie or on any watcher command. If something fails — bad path, auth error, missing reviewer — you want to see why, not a bare non-zero exit. Use `2>&1 | tee <log>` if you want both a saved log and visible errors.
|
||||
|
||||
## Examples
|
||||
|
||||
**User: "Have magpie look at what I'm working on right now."**
|
||||
They likely mean uncommitted work. Run:
|
||||
```
|
||||
magpie review --local
|
||||
```
|
||||
|
||||
**User: "Get a magpie review on this branch before I push."**
|
||||
Current branch vs main:
|
||||
```
|
||||
magpie review --branch
|
||||
```
|
||||
|
||||
**User: "Run magpie on PR 4521."**
|
||||
```
|
||||
magpie review 4521
|
||||
```
|
||||
|
||||
**User: "Use magpie to review just the changes to `src/auth/`."**
|
||||
Pick the files mode:
|
||||
```
|
||||
magpie review --files src/auth/login.ts src/auth/session.ts
|
||||
```
|
||||
|
||||
**User: "Get a fast magpie sanity check on PR 4521 — I just want the output, don't post anything."**
|
||||
```
|
||||
magpie review 4521 -a --no-post --skip-context
|
||||
```
|
||||
|
||||
**User: "Have magpie debate whether we should adopt tRPC."**
|
||||
```
|
||||
magpie discuss "Should we adopt tRPC for our internal APIs?"
|
||||
```
|
||||
|
||||
## Configuration notes
|
||||
|
||||
- Magpie reads `~/.magpie/config.yaml` for providers, reviewers, analyzer, summarizer, and the context-gatherer config.
|
||||
- CLI providers (`claude-code`, `codex-cli`, `gemini-cli`, `qwen-code`, `opencode-cli`) use the user's existing subscriptions/logins — no API keys needed and they're the recommended choice.
|
||||
- If the user hasn't run `magpie init` yet, suggest `magpie init` (interactive) or `magpie init -y` (defaults) before the first review.
|
||||
- If `magpie` is not on PATH, the project at `/home/tgrosinger/code/magpie` may need `npm install && npm run build && npm link` from its root.
|
||||
|
||||
## When *not* to use this skill
|
||||
|
||||
- The user is asking how magpie itself is implemented or wants to modify magpie's source — that's a normal code task in the magpie repo, not an invocation of this skill.
|
||||
- The user wants a single-model review (just Claude reviewing the diff). Use the built-in `/code-review` skill instead.
|
||||
@@ -1,620 +0,0 @@
|
||||
---
|
||||
name: obsidian-bases
|
||||
description: Create and edit Obsidian Bases (.base files) with views, filters, formulas, and summaries. Use when working with .base files, creating database-like views of notes, or when the user mentions Bases, table views, card views, filters, or formulas in Obsidian.
|
||||
source: https://github.com/kepano/obsidian-skills/blob/main/skills/obsidian-bases/SKILL.md
|
||||
---
|
||||
|
||||
# Obsidian Bases Skill
|
||||
|
||||
This skill enables Claude Code to create and edit valid Obsidian Bases (`.base` files) including views, filters, formulas, and all related configurations.
|
||||
|
||||
## Overview
|
||||
|
||||
Obsidian Bases are YAML-based files that define dynamic views of notes in an Obsidian vault. A Base file can contain multiple views, global filters, formulas, property configurations, and custom summaries.
|
||||
|
||||
## File Format
|
||||
|
||||
Base files use the `.base` extension and contain valid YAML. They can also be embedded in Markdown code blocks.
|
||||
|
||||
## Complete Schema
|
||||
|
||||
```yaml
|
||||
# Global filters apply to ALL views in the base
|
||||
filters:
|
||||
# Can be a single filter string
|
||||
# OR a recursive filter object with and/or/not
|
||||
and: []
|
||||
or: []
|
||||
not: []
|
||||
|
||||
# Define formula properties that can be used across all views
|
||||
formulas:
|
||||
formula_name: 'expression'
|
||||
|
||||
# Configure display names and settings for properties
|
||||
properties:
|
||||
property_name:
|
||||
displayName: "Display Name"
|
||||
formula.formula_name:
|
||||
displayName: "Formula Display Name"
|
||||
file.ext:
|
||||
displayName: "Extension"
|
||||
|
||||
# Define custom summary formulas
|
||||
summaries:
|
||||
custom_summary_name: 'values.mean().round(3)'
|
||||
|
||||
# Define one or more views
|
||||
views:
|
||||
- type: table | cards | list | map
|
||||
name: "View Name"
|
||||
limit: 10 # Optional: limit results
|
||||
groupBy: # Optional: group results
|
||||
property: property_name
|
||||
direction: ASC | DESC
|
||||
filters: # View-specific filters
|
||||
and: []
|
||||
order: # Properties to display in order
|
||||
- file.name
|
||||
- property_name
|
||||
- formula.formula_name
|
||||
summaries: # Map properties to summary formulas
|
||||
property_name: Average
|
||||
```
|
||||
|
||||
## Filter Syntax
|
||||
|
||||
Filters narrow down results. They can be applied globally or per-view.
|
||||
|
||||
### Filter Structure
|
||||
|
||||
```yaml
|
||||
# Single filter
|
||||
filters: 'status == "done"'
|
||||
|
||||
# AND - all conditions must be true
|
||||
filters:
|
||||
and:
|
||||
- 'status == "done"'
|
||||
- 'priority > 3'
|
||||
|
||||
# OR - any condition can be true
|
||||
filters:
|
||||
or:
|
||||
- 'file.hasTag("book")'
|
||||
- 'file.hasTag("article")'
|
||||
|
||||
# NOT - exclude matching items
|
||||
filters:
|
||||
not:
|
||||
- 'file.hasTag("archived")'
|
||||
|
||||
# Nested filters
|
||||
filters:
|
||||
or:
|
||||
- file.hasTag("tag")
|
||||
- and:
|
||||
- file.hasTag("book")
|
||||
- file.hasLink("Textbook")
|
||||
- not:
|
||||
- file.hasTag("book")
|
||||
- file.inFolder("Required Reading")
|
||||
```
|
||||
|
||||
### Filter Operators
|
||||
|
||||
| Operator | Description |
|
||||
|----------|-------------|
|
||||
| `==` | equals |
|
||||
| `!=` | not equal |
|
||||
| `>` | greater than |
|
||||
| `<` | less than |
|
||||
| `>=` | greater than or equal |
|
||||
| `<=` | less than or equal |
|
||||
| `&&` | logical and |
|
||||
| `\|\|` | logical or |
|
||||
| <code>!</code> | logical not |
|
||||
|
||||
## Properties
|
||||
|
||||
### Three Types of Properties
|
||||
|
||||
1. **Note properties** - From frontmatter: `note.author` or just `author`
|
||||
2. **File properties** - File metadata: `file.name`, `file.mtime`, etc.
|
||||
3. **Formula properties** - Computed values: `formula.my_formula`
|
||||
|
||||
### File Properties Reference
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `file.name` | String | File name |
|
||||
| `file.basename` | String | File name without extension |
|
||||
| `file.path` | String | Full path to file |
|
||||
| `file.folder` | String | Parent folder path |
|
||||
| `file.ext` | String | File extension |
|
||||
| `file.size` | Number | File size in bytes |
|
||||
| `file.ctime` | Date | Created time |
|
||||
| `file.mtime` | Date | Modified time |
|
||||
| `file.tags` | List | All tags in file |
|
||||
| `file.links` | List | Internal links in file |
|
||||
| `file.backlinks` | List | Files linking to this file |
|
||||
| `file.embeds` | List | Embeds in the note |
|
||||
| `file.properties` | Object | All frontmatter properties |
|
||||
|
||||
### The `this` Keyword
|
||||
|
||||
- In main content area: refers to the base file itself
|
||||
- When embedded: refers to the embedding file
|
||||
- In sidebar: refers to the active file in main content
|
||||
|
||||
## Formula Syntax
|
||||
|
||||
Formulas compute values from properties. Defined in the `formulas` section.
|
||||
|
||||
```yaml
|
||||
formulas:
|
||||
# Simple arithmetic
|
||||
total: "price * quantity"
|
||||
|
||||
# Conditional logic
|
||||
status_icon: 'if(done, "✅", "⏳")'
|
||||
|
||||
# String formatting
|
||||
formatted_price: 'if(price, price.toFixed(2) + " dollars")'
|
||||
|
||||
# Date formatting
|
||||
created: 'file.ctime.format("YYYY-MM-DD")'
|
||||
|
||||
# Complex expressions
|
||||
days_old: '((now() - file.ctime) / 86400000).round(0)'
|
||||
```
|
||||
|
||||
## Functions Reference
|
||||
|
||||
### Global Functions
|
||||
|
||||
| Function | Signature | Description |
|
||||
|----------|-----------|-------------|
|
||||
| `date()` | `date(string): date` | Parse string to date. Format: `YYYY-MM-DD HH:mm:ss` |
|
||||
| `duration()` | `duration(string): duration` | Parse duration string |
|
||||
| `now()` | `now(): date` | Current date and time |
|
||||
| `today()` | `today(): date` | Current date (time = 00:00:00) |
|
||||
| `if()` | `if(condition, trueResult, falseResult?)` | Conditional |
|
||||
| `min()` | `min(n1, n2, ...): number` | Smallest number |
|
||||
| `max()` | `max(n1, n2, ...): number` | Largest number |
|
||||
| `number()` | `number(any): number` | Convert to number |
|
||||
| `link()` | `link(path, display?): Link` | Create a link |
|
||||
| `list()` | `list(element): List` | Wrap in list if not already |
|
||||
| `file()` | `file(path): file` | Get file object |
|
||||
| `image()` | `image(path): image` | Create image for rendering |
|
||||
| `icon()` | `icon(name): icon` | Lucide icon by name |
|
||||
| `html()` | `html(string): html` | Render as HTML |
|
||||
| `escapeHTML()` | `escapeHTML(string): string` | Escape HTML characters |
|
||||
|
||||
### Any Type Functions
|
||||
|
||||
| Function | Signature | Description |
|
||||
|----------|-----------|-------------|
|
||||
| `isTruthy()` | `any.isTruthy(): boolean` | Coerce to boolean |
|
||||
| `isType()` | `any.isType(type): boolean` | Check type |
|
||||
| `toString()` | `any.toString(): string` | Convert to string |
|
||||
|
||||
### Date Functions & Fields
|
||||
|
||||
**Fields:** `date.year`, `date.month`, `date.day`, `date.hour`, `date.minute`, `date.second`, `date.millisecond`
|
||||
|
||||
| Function | Signature | Description |
|
||||
|----------|-----------|-------------|
|
||||
| `date()` | `date.date(): date` | Remove time portion |
|
||||
| `format()` | `date.format(string): string` | Format with Moment.js pattern |
|
||||
| `time()` | `date.time(): string` | Get time as string |
|
||||
| `relative()` | `date.relative(): string` | Human-readable relative time |
|
||||
| `isEmpty()` | `date.isEmpty(): boolean` | Always false for dates |
|
||||
|
||||
### Date Arithmetic
|
||||
|
||||
```yaml
|
||||
# Duration units: y/year/years, M/month/months, d/day/days,
|
||||
# w/week/weeks, h/hour/hours, m/minute/minutes, s/second/seconds
|
||||
|
||||
# Add/subtract durations
|
||||
"date + \"1M\"" # Add 1 month
|
||||
"date - \"2h\"" # Subtract 2 hours
|
||||
"now() + \"1 day\"" # Tomorrow
|
||||
"today() + \"7d\"" # A week from today
|
||||
|
||||
# Subtract dates for millisecond difference
|
||||
"now() - file.ctime"
|
||||
|
||||
# Complex duration arithmetic
|
||||
"now() + (duration('1d') * 2)"
|
||||
```
|
||||
|
||||
### String Functions
|
||||
|
||||
**Field:** `string.length`
|
||||
|
||||
| Function | Signature | Description |
|
||||
|----------|-----------|-------------|
|
||||
| `contains()` | `string.contains(value): boolean` | Check substring |
|
||||
| `containsAll()` | `string.containsAll(...values): boolean` | All substrings present |
|
||||
| `containsAny()` | `string.containsAny(...values): boolean` | Any substring present |
|
||||
| `startsWith()` | `string.startsWith(query): boolean` | Starts with query |
|
||||
| `endsWith()` | `string.endsWith(query): boolean` | Ends with query |
|
||||
| `isEmpty()` | `string.isEmpty(): boolean` | Empty or not present |
|
||||
| `lower()` | `string.lower(): string` | To lowercase |
|
||||
| `title()` | `string.title(): string` | To Title Case |
|
||||
| `trim()` | `string.trim(): string` | Remove whitespace |
|
||||
| `replace()` | `string.replace(pattern, replacement): string` | Replace pattern |
|
||||
| `repeat()` | `string.repeat(count): string` | Repeat string |
|
||||
| `reverse()` | `string.reverse(): string` | Reverse string |
|
||||
| `slice()` | `string.slice(start, end?): string` | Substring |
|
||||
| `split()` | `string.split(separator, n?): list` | Split to list |
|
||||
|
||||
### Number Functions
|
||||
|
||||
| Function | Signature | Description |
|
||||
|----------|-----------|-------------|
|
||||
| `abs()` | `number.abs(): number` | Absolute value |
|
||||
| `ceil()` | `number.ceil(): number` | Round up |
|
||||
| `floor()` | `number.floor(): number` | Round down |
|
||||
| `round()` | `number.round(digits?): number` | Round to digits |
|
||||
| `toFixed()` | `number.toFixed(precision): string` | Fixed-point notation |
|
||||
| `isEmpty()` | `number.isEmpty(): boolean` | Not present |
|
||||
|
||||
### List Functions
|
||||
|
||||
**Field:** `list.length`
|
||||
|
||||
| Function | Signature | Description |
|
||||
|----------|-----------|-------------|
|
||||
| `contains()` | `list.contains(value): boolean` | Element exists |
|
||||
| `containsAll()` | `list.containsAll(...values): boolean` | All elements exist |
|
||||
| `containsAny()` | `list.containsAny(...values): boolean` | Any element exists |
|
||||
| `filter()` | `list.filter(expression): list` | Filter by condition (uses `value`, `index`) |
|
||||
| `map()` | `list.map(expression): list` | Transform elements (uses `value`, `index`) |
|
||||
| `reduce()` | `list.reduce(expression, initial): any` | Reduce to single value (uses `value`, `index`, `acc`) |
|
||||
| `flat()` | `list.flat(): list` | Flatten nested lists |
|
||||
| `join()` | `list.join(separator): string` | Join to string |
|
||||
| `reverse()` | `list.reverse(): list` | Reverse order |
|
||||
| `slice()` | `list.slice(start, end?): list` | Sublist |
|
||||
| `sort()` | `list.sort(): list` | Sort ascending |
|
||||
| `unique()` | `list.unique(): list` | Remove duplicates |
|
||||
| `isEmpty()` | `list.isEmpty(): boolean` | No elements |
|
||||
|
||||
### File Functions
|
||||
|
||||
| Function | Signature | Description |
|
||||
|----------|-----------|-------------|
|
||||
| `asLink()` | `file.asLink(display?): Link` | Convert to link |
|
||||
| `hasLink()` | `file.hasLink(otherFile): boolean` | Has link to file |
|
||||
| `hasTag()` | `file.hasTag(...tags): boolean` | Has any of the tags |
|
||||
| `hasProperty()` | `file.hasProperty(name): boolean` | Has property |
|
||||
| `inFolder()` | `file.inFolder(folder): boolean` | In folder or subfolder |
|
||||
|
||||
### Link Functions
|
||||
|
||||
| Function | Signature | Description |
|
||||
|----------|-----------|-------------|
|
||||
| `asFile()` | `link.asFile(): file` | Get file object |
|
||||
| `linksTo()` | `link.linksTo(file): boolean` | Links to file |
|
||||
|
||||
### Object Functions
|
||||
|
||||
| Function | Signature | Description |
|
||||
|----------|-----------|-------------|
|
||||
| `isEmpty()` | `object.isEmpty(): boolean` | No properties |
|
||||
| `keys()` | `object.keys(): list` | List of keys |
|
||||
| `values()` | `object.values(): list` | List of values |
|
||||
|
||||
### Regular Expression Functions
|
||||
|
||||
| Function | Signature | Description |
|
||||
|----------|-----------|-------------|
|
||||
| `matches()` | `regexp.matches(string): boolean` | Test if matches |
|
||||
|
||||
## View Types
|
||||
|
||||
### Table View
|
||||
|
||||
```yaml
|
||||
views:
|
||||
- type: table
|
||||
name: "My Table"
|
||||
order:
|
||||
- file.name
|
||||
- status
|
||||
- due_date
|
||||
summaries:
|
||||
price: Sum
|
||||
count: Average
|
||||
```
|
||||
|
||||
### Cards View
|
||||
|
||||
```yaml
|
||||
views:
|
||||
- type: cards
|
||||
name: "Gallery"
|
||||
order:
|
||||
- file.name
|
||||
- cover_image
|
||||
- description
|
||||
```
|
||||
|
||||
### List View
|
||||
|
||||
```yaml
|
||||
views:
|
||||
- type: list
|
||||
name: "Simple List"
|
||||
order:
|
||||
- file.name
|
||||
- status
|
||||
```
|
||||
|
||||
### Map View
|
||||
|
||||
Requires latitude/longitude properties and the Maps plugin.
|
||||
|
||||
```yaml
|
||||
views:
|
||||
- type: map
|
||||
name: "Locations"
|
||||
# Map-specific settings for lat/lng properties
|
||||
```
|
||||
|
||||
## Default Summary Formulas
|
||||
|
||||
| Name | Input Type | Description |
|
||||
|------|------------|-------------|
|
||||
| `Average` | Number | Mathematical mean |
|
||||
| `Min` | Number | Smallest number |
|
||||
| `Max` | Number | Largest number |
|
||||
| `Sum` | Number | Sum of all numbers |
|
||||
| `Range` | Number | Max - Min |
|
||||
| `Median` | Number | Mathematical median |
|
||||
| `Stddev` | Number | Standard deviation |
|
||||
| `Earliest` | Date | Earliest date |
|
||||
| `Latest` | Date | Latest date |
|
||||
| `Range` | Date | Latest - Earliest |
|
||||
| `Checked` | Boolean | Count of true values |
|
||||
| `Unchecked` | Boolean | Count of false values |
|
||||
| `Empty` | Any | Count of empty values |
|
||||
| `Filled` | Any | Count of non-empty values |
|
||||
| `Unique` | Any | Count of unique values |
|
||||
|
||||
## Complete Examples
|
||||
|
||||
### Task Tracker Base
|
||||
|
||||
```yaml
|
||||
filters:
|
||||
and:
|
||||
- file.hasTag("task")
|
||||
- 'file.ext == "md"'
|
||||
|
||||
formulas:
|
||||
days_until_due: 'if(due, ((date(due) - today()) / 86400000).round(0), "")'
|
||||
is_overdue: 'if(due, date(due) < today() && status != "done", false)'
|
||||
priority_label: 'if(priority == 1, "🔴 High", if(priority == 2, "🟡 Medium", "🟢 Low"))'
|
||||
|
||||
properties:
|
||||
status:
|
||||
displayName: Status
|
||||
formula.days_until_due:
|
||||
displayName: "Days Until Due"
|
||||
formula.priority_label:
|
||||
displayName: Priority
|
||||
|
||||
views:
|
||||
- type: table
|
||||
name: "Active Tasks"
|
||||
filters:
|
||||
and:
|
||||
- 'status != "done"'
|
||||
order:
|
||||
- file.name
|
||||
- status
|
||||
- formula.priority_label
|
||||
- due
|
||||
- formula.days_until_due
|
||||
groupBy:
|
||||
property: status
|
||||
direction: ASC
|
||||
summaries:
|
||||
formula.days_until_due: Average
|
||||
|
||||
- type: table
|
||||
name: "Completed"
|
||||
filters:
|
||||
and:
|
||||
- 'status == "done"'
|
||||
order:
|
||||
- file.name
|
||||
- completed_date
|
||||
```
|
||||
|
||||
### Reading List Base
|
||||
|
||||
```yaml
|
||||
filters:
|
||||
or:
|
||||
- file.hasTag("book")
|
||||
- file.hasTag("article")
|
||||
|
||||
formulas:
|
||||
reading_time: 'if(pages, (pages * 2).toString() + " min", "")'
|
||||
status_icon: 'if(status == "reading", "📖", if(status == "done", "✅", "📚"))'
|
||||
year_read: 'if(finished_date, date(finished_date).year, "")'
|
||||
|
||||
properties:
|
||||
author:
|
||||
displayName: Author
|
||||
formula.status_icon:
|
||||
displayName: ""
|
||||
formula.reading_time:
|
||||
displayName: "Est. Time"
|
||||
|
||||
views:
|
||||
- type: cards
|
||||
name: "Library"
|
||||
order:
|
||||
- cover
|
||||
- file.name
|
||||
- author
|
||||
- formula.status_icon
|
||||
filters:
|
||||
not:
|
||||
- 'status == "dropped"'
|
||||
|
||||
- type: table
|
||||
name: "Reading List"
|
||||
filters:
|
||||
and:
|
||||
- 'status == "to-read"'
|
||||
order:
|
||||
- file.name
|
||||
- author
|
||||
- pages
|
||||
- formula.reading_time
|
||||
```
|
||||
|
||||
### Project Notes Base
|
||||
|
||||
```yaml
|
||||
filters:
|
||||
and:
|
||||
- file.inFolder("Projects")
|
||||
- 'file.ext == "md"'
|
||||
|
||||
formulas:
|
||||
last_updated: 'file.mtime.relative()'
|
||||
link_count: 'file.links.length'
|
||||
|
||||
summaries:
|
||||
avgLinks: 'values.filter(value.isType("number")).mean().round(1)'
|
||||
|
||||
properties:
|
||||
formula.last_updated:
|
||||
displayName: "Updated"
|
||||
formula.link_count:
|
||||
displayName: "Links"
|
||||
|
||||
views:
|
||||
- type: table
|
||||
name: "All Projects"
|
||||
order:
|
||||
- file.name
|
||||
- status
|
||||
- formula.last_updated
|
||||
- formula.link_count
|
||||
summaries:
|
||||
formula.link_count: avgLinks
|
||||
groupBy:
|
||||
property: status
|
||||
direction: ASC
|
||||
|
||||
- type: list
|
||||
name: "Quick List"
|
||||
order:
|
||||
- file.name
|
||||
- status
|
||||
```
|
||||
|
||||
### Daily Notes Index
|
||||
|
||||
```yaml
|
||||
filters:
|
||||
and:
|
||||
- file.inFolder("Daily Notes")
|
||||
- '/^\d{4}-\d{2}-\d{2}$/.matches(file.basename)'
|
||||
|
||||
formulas:
|
||||
word_estimate: '(file.size / 5).round(0)'
|
||||
day_of_week: 'date(file.basename).format("dddd")'
|
||||
|
||||
properties:
|
||||
formula.day_of_week:
|
||||
displayName: "Day"
|
||||
formula.word_estimate:
|
||||
displayName: "~Words"
|
||||
|
||||
views:
|
||||
- type: table
|
||||
name: "Recent Notes"
|
||||
limit: 30
|
||||
order:
|
||||
- file.name
|
||||
- formula.day_of_week
|
||||
- formula.word_estimate
|
||||
- file.mtime
|
||||
```
|
||||
|
||||
## Embedding Bases
|
||||
|
||||
Embed in Markdown files:
|
||||
|
||||
```markdown
|
||||
![[MyBase.base]]
|
||||
|
||||
<!-- Specific view -->
|
||||
![[MyBase.base#View Name]]
|
||||
```
|
||||
|
||||
## YAML Quoting Rules
|
||||
|
||||
- Use single quotes for formulas containing double quotes: `'if(done, "Yes", "No")'`
|
||||
- Use double quotes for simple strings: `"My View Name"`
|
||||
- Escape nested quotes properly in complex expressions
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Filter by Tag
|
||||
```yaml
|
||||
filters:
|
||||
and:
|
||||
- file.hasTag("project")
|
||||
```
|
||||
|
||||
### Filter by Folder
|
||||
```yaml
|
||||
filters:
|
||||
and:
|
||||
- file.inFolder("Notes")
|
||||
```
|
||||
|
||||
### Filter by Date Range
|
||||
```yaml
|
||||
filters:
|
||||
and:
|
||||
- 'file.mtime > now() - "7d"'
|
||||
```
|
||||
|
||||
### Filter by Property Value
|
||||
```yaml
|
||||
filters:
|
||||
and:
|
||||
- 'status == "active"'
|
||||
- 'priority >= 3'
|
||||
```
|
||||
|
||||
### Combine Multiple Conditions
|
||||
```yaml
|
||||
filters:
|
||||
or:
|
||||
- and:
|
||||
- file.hasTag("important")
|
||||
- 'status != "done"'
|
||||
- and:
|
||||
- 'priority == 1'
|
||||
- 'due != ""'
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- [Bases Syntax](https://help.obsidian.md/bases/syntax)
|
||||
- [Functions](https://help.obsidian.md/bases/functions)
|
||||
- [Views](https://help.obsidian.md/bases/views)
|
||||
- [Formulas](https://help.obsidian.md/formulas)
|
||||
|
||||
@@ -1,622 +0,0 @@
|
||||
---
|
||||
name: obsidian-markdown
|
||||
description: Create and edit Obsidian Flavored Markdown with wikilinks, embeds, callouts, properties, and other Obsidian-specific syntax. Use when working with .md files in Obsidian, or when the user mentions wikilinks, callouts, frontmatter, tags, embeds, or Obsidian notes.
|
||||
source: https://github.com/kepano/obsidian-skills/blob/main/skills/obsidian-markdown/SKILL.md
|
||||
---
|
||||
|
||||
# Obsidian Flavored Markdown Skill
|
||||
|
||||
This skill enables Claude Code to create and edit valid Obsidian Flavored Markdown, including all Obsidian-specific syntax extensions.
|
||||
|
||||
## Overview
|
||||
|
||||
Obsidian uses a combination of Markdown flavors:
|
||||
- [CommonMark](https://commonmark.org/)
|
||||
- [GitHub Flavored Markdown](https://github.github.com/gfm/)
|
||||
- [LaTeX](https://www.latex-project.org/) for math
|
||||
- Obsidian-specific extensions (wikilinks, callouts, embeds, etc.)
|
||||
|
||||
## Basic Formatting
|
||||
|
||||
### Paragraphs and Line Breaks
|
||||
|
||||
```markdown
|
||||
This is a paragraph.
|
||||
|
||||
This is another paragraph (blank line between creates separate paragraphs).
|
||||
|
||||
For a line break within a paragraph, add two spaces at the end
|
||||
or use Shift+Enter.
|
||||
```
|
||||
|
||||
### Headings
|
||||
|
||||
```markdown
|
||||
# Heading 1
|
||||
## Heading 2
|
||||
### Heading 3
|
||||
#### Heading 4
|
||||
##### Heading 5
|
||||
###### Heading 6
|
||||
```
|
||||
|
||||
### Text Formatting
|
||||
|
||||
| Style | Syntax | Example | Output |
|
||||
|-------|--------|---------|--------|
|
||||
| Bold | `**text**` or `__text__` | `**Bold**` | **Bold** |
|
||||
| Italic | `*text*` or `_text_` | `*Italic*` | *Italic* |
|
||||
| Bold + Italic | `***text***` | `***Both***` | ***Both*** |
|
||||
| Strikethrough | `~~text~~` | `~~Striked~~` | ~~Striked~~ |
|
||||
| Highlight | `==text==` | `==Highlighted==` | ==Highlighted== |
|
||||
| Inline code | `` `code` `` | `` `code` `` | `code` |
|
||||
|
||||
### Escaping Formatting
|
||||
|
||||
Use backslash to escape special characters:
|
||||
```markdown
|
||||
\*This won't be italic\*
|
||||
\#This won't be a heading
|
||||
1\. This won't be a list item
|
||||
```
|
||||
|
||||
Common characters to escape: `\*`, `\_`, `\#`, `` \` ``, `\|`, `\~`
|
||||
|
||||
## Internal Links (Wikilinks)
|
||||
|
||||
### Basic Links
|
||||
|
||||
```markdown
|
||||
[[Note Name]]
|
||||
[[Note Name.md]]
|
||||
[[Note Name|Display Text]]
|
||||
```
|
||||
|
||||
### Link to Headings
|
||||
|
||||
```markdown
|
||||
[[Note Name#Heading]]
|
||||
[[Note Name#Heading|Custom Text]]
|
||||
[[#Heading in same note]]
|
||||
[[##Search all headings in vault]]
|
||||
```
|
||||
|
||||
### Link to Blocks
|
||||
|
||||
```markdown
|
||||
[[Note Name#^block-id]]
|
||||
[[Note Name#^block-id|Custom Text]]
|
||||
```
|
||||
|
||||
Define a block ID by adding `^block-id` at the end of a paragraph:
|
||||
```markdown
|
||||
This is a paragraph that can be linked to. ^my-block-id
|
||||
```
|
||||
|
||||
For lists and quotes, add the block ID on a separate line:
|
||||
```markdown
|
||||
> This is a quote
|
||||
> With multiple lines
|
||||
|
||||
^quote-id
|
||||
```
|
||||
|
||||
### Search Links
|
||||
|
||||
```markdown
|
||||
[[##heading]] Search for headings containing "heading"
|
||||
[[^^block]] Search for blocks containing "block"
|
||||
```
|
||||
|
||||
## Markdown-Style Links
|
||||
|
||||
```markdown
|
||||
[Display Text](Note%20Name.md)
|
||||
[Display Text](Note%20Name.md#Heading)
|
||||
[Display Text](https://example.com)
|
||||
[Note](obsidian://open?vault=VaultName&file=Note.md)
|
||||
```
|
||||
|
||||
Note: Spaces must be URL-encoded as `%20` in Markdown links.
|
||||
|
||||
## Embeds
|
||||
|
||||
### Embed Notes
|
||||
|
||||
```markdown
|
||||
![[Note Name]]
|
||||
![[Note Name#Heading]]
|
||||
![[Note Name#^block-id]]
|
||||
```
|
||||
|
||||
### Embed Images
|
||||
|
||||
```markdown
|
||||
![[image.png]]
|
||||
![[image.png|640x480]] Width x Height
|
||||
![[image.png|300]] Width only (maintains aspect ratio)
|
||||
```
|
||||
|
||||
### External Images
|
||||
|
||||
```markdown
|
||||

|
||||

|
||||
```
|
||||
|
||||
### Embed Audio
|
||||
|
||||
```markdown
|
||||
![[audio.mp3]]
|
||||
![[audio.ogg]]
|
||||
```
|
||||
|
||||
### Embed PDF
|
||||
|
||||
```markdown
|
||||
![[document.pdf]]
|
||||
![[document.pdf#page=3]]
|
||||
![[document.pdf#height=400]]
|
||||
```
|
||||
|
||||
### Embed Lists
|
||||
|
||||
```markdown
|
||||
![[Note#^list-id]]
|
||||
```
|
||||
|
||||
Where the list has been defined with a block ID:
|
||||
```markdown
|
||||
- Item 1
|
||||
- Item 2
|
||||
- Item 3
|
||||
|
||||
^list-id
|
||||
```
|
||||
|
||||
### Embed Search Results
|
||||
|
||||
````markdown
|
||||
```query
|
||||
tag:#project status:done
|
||||
```
|
||||
````
|
||||
|
||||
## Callouts
|
||||
|
||||
### Basic Callout
|
||||
|
||||
```markdown
|
||||
> [!note]
|
||||
> This is a note callout.
|
||||
|
||||
> [!info] Custom Title
|
||||
> This callout has a custom title.
|
||||
|
||||
> [!tip] Title Only
|
||||
```
|
||||
|
||||
### Foldable Callouts
|
||||
|
||||
```markdown
|
||||
> [!faq]- Collapsed by default
|
||||
> This content is hidden until expanded.
|
||||
|
||||
> [!faq]+ Expanded by default
|
||||
> This content is visible but can be collapsed.
|
||||
```
|
||||
|
||||
### Nested Callouts
|
||||
|
||||
```markdown
|
||||
> [!question] Outer callout
|
||||
> > [!note] Inner callout
|
||||
> > Nested content
|
||||
```
|
||||
|
||||
### Supported Callout Types
|
||||
|
||||
| Type | Aliases | Description |
|
||||
|------|---------|-------------|
|
||||
| `note` | - | Blue, pencil icon |
|
||||
| `abstract` | `summary`, `tldr` | Teal, clipboard icon |
|
||||
| `info` | - | Blue, info icon |
|
||||
| `todo` | - | Blue, checkbox icon |
|
||||
| `tip` | `hint`, `important` | Cyan, flame icon |
|
||||
| `success` | `check`, `done` | Green, checkmark icon |
|
||||
| `question` | `help`, `faq` | Yellow, question mark |
|
||||
| `warning` | `caution`, `attention` | Orange, warning icon |
|
||||
| `failure` | `fail`, `missing` | Red, X icon |
|
||||
| `danger` | `error` | Red, zap icon |
|
||||
| `bug` | - | Red, bug icon |
|
||||
| `example` | - | Purple, list icon |
|
||||
| `quote` | `cite` | Gray, quote icon |
|
||||
|
||||
### Custom Callouts (CSS)
|
||||
|
||||
```css
|
||||
.callout[data-callout="custom-type"] {
|
||||
--callout-color: 255, 0, 0;
|
||||
--callout-icon: lucide-alert-circle;
|
||||
}
|
||||
```
|
||||
|
||||
## Lists
|
||||
|
||||
### Unordered Lists
|
||||
|
||||
```markdown
|
||||
- Item 1
|
||||
- Item 2
|
||||
- Nested item
|
||||
- Another nested
|
||||
- Item 3
|
||||
|
||||
* Also works with asterisks
|
||||
+ Or plus signs
|
||||
```
|
||||
|
||||
### Ordered Lists
|
||||
|
||||
```markdown
|
||||
1. First item
|
||||
2. Second item
|
||||
1. Nested numbered
|
||||
2. Another nested
|
||||
3. Third item
|
||||
|
||||
1) Alternative syntax
|
||||
2) With parentheses
|
||||
```
|
||||
|
||||
### Task Lists
|
||||
|
||||
```markdown
|
||||
- [ ] Incomplete task
|
||||
- [x] Completed task
|
||||
- [ ] Task with sub-tasks
|
||||
- [ ] Subtask 1
|
||||
- [x] Subtask 2
|
||||
```
|
||||
|
||||
## Quotes
|
||||
|
||||
```markdown
|
||||
> This is a blockquote.
|
||||
> It can span multiple lines.
|
||||
>
|
||||
> And include multiple paragraphs.
|
||||
>
|
||||
> > Nested quotes work too.
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
### Inline Code
|
||||
|
||||
```markdown
|
||||
Use `backticks` for inline code.
|
||||
Use double backticks for ``code with a ` backtick inside``.
|
||||
```
|
||||
|
||||
### Code Blocks
|
||||
|
||||
````markdown
|
||||
```
|
||||
Plain code block
|
||||
```
|
||||
|
||||
```javascript
|
||||
// Syntax highlighted code block
|
||||
function hello() {
|
||||
console.log("Hello, world!");
|
||||
}
|
||||
```
|
||||
|
||||
```python
|
||||
# Python example
|
||||
def greet(name):
|
||||
print(f"Hello, {name}!")
|
||||
```
|
||||
````
|
||||
|
||||
### Nesting Code Blocks
|
||||
|
||||
Use more backticks or tildes for the outer block:
|
||||
|
||||
`````markdown
|
||||
````markdown
|
||||
Here's how to create a code block:
|
||||
```js
|
||||
console.log("Hello")
|
||||
```
|
||||
````
|
||||
`````
|
||||
|
||||
## Tables
|
||||
|
||||
```markdown
|
||||
| Header 1 | Header 2 | Header 3 |
|
||||
|----------|----------|----------|
|
||||
| Cell 1 | Cell 2 | Cell 3 |
|
||||
| Cell 4 | Cell 5 | Cell 6 |
|
||||
```
|
||||
|
||||
### Alignment
|
||||
|
||||
```markdown
|
||||
| Left | Center | Right |
|
||||
|:---------|:--------:|---------:|
|
||||
| Left | Center | Right |
|
||||
```
|
||||
|
||||
### Using Pipes in Tables
|
||||
|
||||
Escape pipes with backslash:
|
||||
```markdown
|
||||
| Column 1 | Column 2 |
|
||||
|----------|----------|
|
||||
| [[Link\|Display]] | ![[Image\|100]] |
|
||||
```
|
||||
|
||||
## Math (LaTeX)
|
||||
|
||||
### Inline Math
|
||||
|
||||
```markdown
|
||||
This is inline math: $e^{i\pi} + 1 = 0$
|
||||
```
|
||||
|
||||
### Block Math
|
||||
|
||||
```markdown
|
||||
$$
|
||||
\begin{vmatrix}
|
||||
a & b \\
|
||||
c & d
|
||||
\end{vmatrix} = ad - bc
|
||||
$$
|
||||
```
|
||||
|
||||
### Common Math Syntax
|
||||
|
||||
```markdown
|
||||
$x^2$ Superscript
|
||||
$x_i$ Subscript
|
||||
$\frac{a}{b}$ Fraction
|
||||
$\sqrt{x}$ Square root
|
||||
$\sum_{i=1}^{n}$ Summation
|
||||
$\int_a^b$ Integral
|
||||
$\alpha, \beta$ Greek letters
|
||||
```
|
||||
|
||||
## Diagrams (Mermaid)
|
||||
|
||||
````markdown
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Start] --> B{Decision}
|
||||
B -->|Yes| C[Do this]
|
||||
B -->|No| D[Do that]
|
||||
C --> E[End]
|
||||
D --> E
|
||||
```
|
||||
````
|
||||
|
||||
### Sequence Diagrams
|
||||
|
||||
````markdown
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
Alice->>Bob: Hello Bob
|
||||
Bob-->>Alice: Hi Alice
|
||||
```
|
||||
````
|
||||
|
||||
### Linking in Diagrams
|
||||
|
||||
````markdown
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Biology]
|
||||
B[Chemistry]
|
||||
A --> B
|
||||
class A,B internal-link;
|
||||
```
|
||||
````
|
||||
|
||||
## Footnotes
|
||||
|
||||
```markdown
|
||||
This sentence has a footnote[^1].
|
||||
|
||||
[^1]: This is the footnote content.
|
||||
|
||||
You can also use named footnotes[^note].
|
||||
|
||||
[^note]: Named footnotes still appear as numbers.
|
||||
|
||||
Inline footnotes are also supported.^[This is an inline footnote.]
|
||||
```
|
||||
|
||||
## Comments
|
||||
|
||||
```markdown
|
||||
This is visible %%but this is hidden%% text.
|
||||
|
||||
%%
|
||||
This entire block is hidden.
|
||||
It won't appear in reading view.
|
||||
%%
|
||||
```
|
||||
|
||||
## Horizontal Rules
|
||||
|
||||
```markdown
|
||||
---
|
||||
***
|
||||
___
|
||||
- - -
|
||||
* * *
|
||||
```
|
||||
|
||||
## Properties (Frontmatter)
|
||||
|
||||
Properties use YAML frontmatter at the start of a note:
|
||||
|
||||
```yaml
|
||||
---
|
||||
title: My Note Title
|
||||
date: 2024-01-15
|
||||
tags:
|
||||
- project
|
||||
- important
|
||||
aliases:
|
||||
- My Note
|
||||
- Alternative Name
|
||||
cssclasses:
|
||||
- custom-class
|
||||
status: in-progress
|
||||
rating: 4.5
|
||||
completed: false
|
||||
due: 2024-02-01T14:30:00
|
||||
---
|
||||
```
|
||||
|
||||
### Property Types
|
||||
|
||||
| Type | Example |
|
||||
|------|---------|
|
||||
| Text | `title: My Title` |
|
||||
| Number | `rating: 4.5` |
|
||||
| Checkbox | `completed: true` |
|
||||
| Date | `date: 2024-01-15` |
|
||||
| Date & Time | `due: 2024-01-15T14:30:00` |
|
||||
| List | `tags: [one, two]` or YAML list |
|
||||
| Links | `related: "[[Other Note]]"` |
|
||||
|
||||
### Default Properties
|
||||
|
||||
- `tags` - Note tags
|
||||
- `aliases` - Alternative names for the note
|
||||
- `cssclasses` - CSS classes applied to the note
|
||||
|
||||
## Tags
|
||||
|
||||
```markdown
|
||||
#tag
|
||||
#nested/tag
|
||||
#tag-with-dashes
|
||||
#tag_with_underscores
|
||||
|
||||
In frontmatter:
|
||||
---
|
||||
tags:
|
||||
- tag1
|
||||
- nested/tag2
|
||||
---
|
||||
```
|
||||
|
||||
Tags can contain:
|
||||
- Letters (any language)
|
||||
- Numbers (not as first character)
|
||||
- Underscores `_`
|
||||
- Hyphens `-`
|
||||
- Forward slashes `/` (for nesting)
|
||||
|
||||
## HTML Content
|
||||
|
||||
Obsidian supports HTML within Markdown:
|
||||
|
||||
```markdown
|
||||
<div class="custom-container">
|
||||
<span style="color: red;">Colored text</span>
|
||||
</div>
|
||||
|
||||
<details>
|
||||
<summary>Click to expand</summary>
|
||||
Hidden content here.
|
||||
</details>
|
||||
|
||||
<kbd>Ctrl</kbd> + <kbd>C</kbd>
|
||||
```
|
||||
|
||||
## Complete Example
|
||||
|
||||
````markdown
|
||||
---
|
||||
title: Project Alpha
|
||||
date: 2024-01-15
|
||||
tags:
|
||||
- project
|
||||
- active
|
||||
status: in-progress
|
||||
priority: high
|
||||
---
|
||||
|
||||
# Project Alpha
|
||||
|
||||
## Overview
|
||||
|
||||
This project aims to [[improve workflow]] using modern techniques.
|
||||
|
||||
> [!important] Key Deadline
|
||||
> The first milestone is due on ==January 30th==.
|
||||
|
||||
## Tasks
|
||||
|
||||
- [x] Initial planning
|
||||
- [x] Resource allocation
|
||||
- [ ] Development phase
|
||||
- [ ] Backend implementation
|
||||
- [ ] Frontend design
|
||||
- [ ] Testing
|
||||
- [ ] Deployment
|
||||
|
||||
## Technical Notes
|
||||
|
||||
The main algorithm uses the formula $O(n \log n)$ for sorting.
|
||||
|
||||
```python
|
||||
def process_data(items):
|
||||
return sorted(items, key=lambda x: x.priority)
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A[Input] --> B[Process]
|
||||
B --> C[Output]
|
||||
B --> D[Cache]
|
||||
```
|
||||
|
||||
## Related Documents
|
||||
|
||||
- ![[Meeting Notes 2024-01-10#Decisions]]
|
||||
- [[Budget Allocation|Budget]]
|
||||
- [[Team Members]]
|
||||
|
||||
## References
|
||||
|
||||
For more details, see the official documentation[^1].
|
||||
|
||||
[^1]: https://example.com/docs
|
||||
|
||||
%%
|
||||
Internal notes:
|
||||
- Review with team on Friday
|
||||
- Consider alternative approaches
|
||||
%%
|
||||
````
|
||||
|
||||
## References
|
||||
|
||||
- [Basic formatting syntax](https://help.obsidian.md/syntax)
|
||||
- [Advanced formatting syntax](https://help.obsidian.md/advanced-syntax)
|
||||
- [Obsidian Flavored Markdown](https://help.obsidian.md/obsidian-flavored-markdown)
|
||||
- [Internal links](https://help.obsidian.md/links)
|
||||
- [Embed files](https://help.obsidian.md/embeds)
|
||||
- [Callouts](https://help.obsidian.md/callouts)
|
||||
- [Properties](https://help.obsidian.md/properties)
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
---
|
||||
name: process-compose
|
||||
description: Inspect a running process-compose project read-only. Use when the user asks what services/processes are running, whether a service is up or healthy, what state a process is in, or to read a service's recent log output — or mentions process-compose (pc) by name.
|
||||
---
|
||||
|
||||
# Inspecting process-compose
|
||||
|
||||
The `process-compose` CLI is a thin **client**. It does not read your config or
|
||||
processes directly — it queries a process-compose **server** that is already
|
||||
running (the one started by `process-compose up`). Every command below talks to
|
||||
that server over TCP, default `localhost:8080`.
|
||||
|
||||
This skill is **read-only**: never start, stop, restart, or scale processes.
|
||||
|
||||
## Connecting
|
||||
|
||||
- Default target is `localhost:8080`. If a project runs on another port, pass
|
||||
`-p PORT` (or set `PC_PORT_NUM`); the port is whatever that project's
|
||||
`process-compose.yaml` / launch command set.
|
||||
- A "connection refused" error means **no server is running on that port**, not
|
||||
a bad command. Report that the project isn't up rather than retrying variants.
|
||||
- `pc` is a fish abbreviation for `process-compose` and exists only in an
|
||||
interactive fish shell. In scripts and Bash calls use the full `process-compose`.
|
||||
|
||||
## Read commands
|
||||
|
||||
List every process with its status (one line each):
|
||||
|
||||
```
|
||||
process-compose process list -o wide
|
||||
```
|
||||
|
||||
Add `-o json` when you need to parse fields (status, health, pid, restarts, exit
|
||||
code) rather than display them.
|
||||
|
||||
Full state of one process:
|
||||
|
||||
```
|
||||
process-compose process get NAME -o json
|
||||
```
|
||||
|
||||
Recent log lines for a process (tail the last N — adjust the number to the need):
|
||||
|
||||
```
|
||||
process-compose process logs NAME -n 100
|
||||
```
|
||||
|
||||
Multiple processes: comma-separate them (`proc1,proc2`). A whole namespace:
|
||||
`-N NAMESPACE`.
|
||||
|
||||
Whole-project state (is everything ready):
|
||||
|
||||
```
|
||||
process-compose project state
|
||||
```
|
||||
|
||||
## Never do
|
||||
|
||||
- **No TUI.** Bare `process-compose`, `up`, and `attach` launch the interactive
|
||||
full-screen TUI and hang a non-interactive shell. Always use a subcommand.
|
||||
- **No `-f` / `--follow`** on `logs` — it streams forever and blocks. Use `-n` to
|
||||
pull a finite tail instead.
|
||||
- **No mutations** — `start`, `stop`, `restart`, `scale`, `down` are out of scope
|
||||
for this skill.
|
||||
@@ -1,4 +1,4 @@
|
||||
#? Config file for btop v.1.4.7
|
||||
#? Config file for btop v.1.4.6
|
||||
|
||||
#* Name of a btop++/bpytop/bashtop formatted ".theme" file, "Default" and "TTY" for builtin themes.
|
||||
#* Themes should be placed in "../share/btop/themes" relative to binary or "$HOME/.config/btop/themes"
|
||||
@@ -14,11 +14,6 @@ truecolor = true
|
||||
#* Will force 16-color mode and TTY theme, set all graph symbols to "tty" and swap out other non tty friendly symbols.
|
||||
force_tty = false
|
||||
|
||||
#* Option to disable presets. Either the default preset, custom presets, or all presets.
|
||||
#* "Off" All presets are enabled.
|
||||
#* "Default" preset is disabled.#* "Custom" presets are disabled.#* "All" presets are disabled.
|
||||
disable_presets = "Off"
|
||||
|
||||
#* Define presets for the layout of the boxes. Preset 0 is always all boxes shown with default settings. Max 9 presets.
|
||||
#* Format: "box_name:P:G,box_name:P:G" P=(0 or 1) for alternate positions, G=graph symbol to use for box.
|
||||
#* Use whitespace " " as separator between different presets.
|
||||
@@ -29,9 +24,6 @@ presets = "cpu:1:default,proc:0:default cpu:0:default,mem:0:default,net:0:defaul
|
||||
#* Conflicting keys for h:"help" and k:"kill" is accessible while holding shift.
|
||||
vim_keys = false
|
||||
|
||||
#* Disable all mouse events.
|
||||
disable_mouse = false
|
||||
|
||||
#* Rounded corners on boxes, is ignored if TTY mode is ON.
|
||||
rounded_corners = true
|
||||
|
||||
@@ -100,9 +92,6 @@ proc_left = false
|
||||
#* (Linux) Filter processes tied to the Linux kernel(similar behavior to htop).
|
||||
proc_filter_kernel = false
|
||||
|
||||
#* Should the process list follow the selected process when detailed view is open.
|
||||
proc_follow_detailed = true
|
||||
|
||||
#* In tree-view, always accumulate child process resources in the parent process.
|
||||
proc_aggregate = false
|
||||
|
||||
@@ -219,9 +208,6 @@ io_graph_combined = false
|
||||
#* Example: "/mnt/media:100 /:20 /boot:1".
|
||||
io_graph_speeds = ""
|
||||
|
||||
#* Swap the positions of the upload and download speed graphs. When true, upload will be on top.
|
||||
swap_upload_download = false
|
||||
|
||||
#* Set fixed values for network graphs in Mebibits. Is only used if net_auto is also set to False.
|
||||
net_download = 100
|
||||
|
||||
@@ -264,7 +250,7 @@ rsmi_measure_pcie_speeds = true
|
||||
#* Horizontally mirror the GPU graph.
|
||||
gpu_mirror_graph = true
|
||||
|
||||
#* Set which GPU vendors to show. Available values are "nvidia amd intel apple"
|
||||
#* Set which GPU vendors to show. Available values are "nvidia amd intel"
|
||||
shown_gpus = "nvidia amd intel"
|
||||
|
||||
#* Custom gpu0 model name, empty string to disable.
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
[delta "catppuccin-latte"]
|
||||
blame-palette = "#eff1f5 #e6e9ef #dce0e8 #ccd0da #bcc0cc"
|
||||
commit-decoration-style = "#9ca0b0" bold box ul
|
||||
light = true
|
||||
file-decoration-style = "#9ca0b0"
|
||||
file-style = "#4c4f69"
|
||||
hunk-header-decoration-style = "#9ca0b0" box ul
|
||||
hunk-header-file-style = bold
|
||||
hunk-header-line-number-style = bold "#6c6f85"
|
||||
hunk-header-style = file line-number syntax
|
||||
line-numbers-left-style = "#9ca0b0"
|
||||
line-numbers-minus-style = bold "#d20f39"
|
||||
line-numbers-plus-style = bold "#40a02b"
|
||||
line-numbers-right-style = "#9ca0b0"
|
||||
line-numbers-zero-style = "#9ca0b0"
|
||||
# 35% red 65% base
|
||||
minus-emph-style = bold syntax "#e5a2b3"
|
||||
# 20% red 80% base
|
||||
minus-style = syntax "#e9c4cf"
|
||||
# 35% green 65% base
|
||||
plus-emph-style = bold syntax "#b2d5ae"
|
||||
# 20% green 80% base
|
||||
plus-style = syntax "#cce1cd"
|
||||
map-styles = \
|
||||
bold purple => syntax "#cbb1f2", \
|
||||
bold blue => syntax "#a6c1f5", \
|
||||
bold cyan => syntax "#9dd7ef", \
|
||||
bold yellow => syntax "#eacfa9"
|
||||
# Should match the name of the bat theme
|
||||
syntax-theme = Catppuccin Latte
|
||||
|
||||
[delta "catppuccin-frappe"]
|
||||
blame-palette = "#303446 #292c3c #232634 #414559 #51576d"
|
||||
commit-decoration-style = "#737994" bold box ul
|
||||
dark = true
|
||||
file-decoration-style = "#737994"
|
||||
file-style = "#c6d0f5"
|
||||
hunk-header-decoration-style = "#737994" box ul
|
||||
hunk-header-file-style = bold
|
||||
hunk-header-line-number-style = bold "#a5adce"
|
||||
hunk-header-style = file line-number syntax
|
||||
line-numbers-left-style = "#737994"
|
||||
line-numbers-minus-style = bold "#e78284"
|
||||
line-numbers-plus-style = bold "#a6d189"
|
||||
line-numbers-right-style = "#737994"
|
||||
line-numbers-zero-style = "#737994"
|
||||
# 35% red 65% base
|
||||
minus-emph-style = bold syntax "#704f5c"
|
||||
# 20% red 80% base
|
||||
minus-style = syntax "#544452"
|
||||
# 35% green 65% base
|
||||
plus-emph-style = bold syntax "#596b5e"
|
||||
# 20% green 80% base
|
||||
plus-style = syntax "#475453"
|
||||
map-styles = \
|
||||
bold purple => syntax "#66597e", \
|
||||
bold blue => syntax "#505d81", \
|
||||
bold cyan => syntax "#546b7a", \
|
||||
bold yellow => syntax "#6f6860"
|
||||
# Should match the name of the bat theme
|
||||
syntax-theme = Catppuccin Frappe
|
||||
|
||||
[delta "catppuccin-macchiato"]
|
||||
blame-palette = "#24273a #1e2030 #181926 #363a4f #494d64"
|
||||
commit-decoration-style = "#6e738d" bold box ul
|
||||
dark = true
|
||||
file-decoration-style = "#6e738d"
|
||||
file-style = "#cad3f5"
|
||||
hunk-header-decoration-style = "#6e738d" box ul
|
||||
hunk-header-file-style = bold
|
||||
hunk-header-line-number-style = bold "#a5adcb"
|
||||
hunk-header-style = file line-number syntax
|
||||
line-numbers-left-style = "#6e738d"
|
||||
line-numbers-minus-style = bold "#ed8796"
|
||||
line-numbers-plus-style = bold "#a6da95"
|
||||
line-numbers-right-style = "#6e738d"
|
||||
line-numbers-zero-style = "#6e738d"
|
||||
# 35% red 65% base
|
||||
minus-emph-style = bold syntax "#6a485a"
|
||||
# 20% red 80% base
|
||||
minus-style = syntax "#4c3a4c"
|
||||
# 35% green 65% base
|
||||
plus-emph-style = bold syntax "#51655a"
|
||||
# 20% green 80% base
|
||||
plus-style = syntax "#3e4b4c"
|
||||
map-styles = \
|
||||
bold purple => syntax "#5c517c", \
|
||||
bold blue => syntax "#47557b", \
|
||||
bold cyan => syntax "#4a6475", \
|
||||
bold yellow => syntax "#6a635d"
|
||||
# Should match the name of the bat theme
|
||||
syntax-theme = Catppuccin Macchiato
|
||||
|
||||
[delta "catppuccin-mocha"]
|
||||
blame-palette = "#1e1e2e #181825 #11111b #313244 #45475a"
|
||||
commit-decoration-style = "#6c7086" bold box ul
|
||||
dark = true
|
||||
file-decoration-style = "#6c7086"
|
||||
file-style = "#cdd6f4"
|
||||
hunk-header-decoration-style = "#6c7086" box ul
|
||||
hunk-header-file-style = bold
|
||||
hunk-header-line-number-style = bold "#a6adc8"
|
||||
hunk-header-style = file line-number syntax
|
||||
line-numbers-left-style = "#6c7086"
|
||||
line-numbers-minus-style = bold "#f38ba8"
|
||||
line-numbers-plus-style = bold "#a6e3a1"
|
||||
line-numbers-right-style = "#6c7086"
|
||||
line-numbers-zero-style = "#6c7086"
|
||||
# 35% red 65% base
|
||||
minus-emph-style = bold syntax "#694559"
|
||||
# 20% red 80% base
|
||||
minus-style = syntax "#493447"
|
||||
# 35% green 65% base
|
||||
plus-emph-style = bold syntax "#4e6356"
|
||||
# 20% green 80% base
|
||||
plus-style = syntax "#394545"
|
||||
map-styles = \
|
||||
bold purple => syntax "#5b4e74", \
|
||||
bold blue => syntax "#445375", \
|
||||
bold cyan => syntax "#446170", \
|
||||
bold yellow => syntax "#6b635b"
|
||||
# Should match the name of the bat theme
|
||||
syntax-theme = Catppuccin Mocha
|
||||
@@ -1 +0,0 @@
|
||||
complete -c dev-switch -f -a '(git worktree list --porcelain 2>/dev/null | string match "worktree *" | string replace "worktree " "" | while read -l p; basename $p; end)'
|
||||
@@ -1,27 +0,0 @@
|
||||
function __dev_wt_completions
|
||||
# Existing worktrees: dev-wt matches by directory basename or branch name,
|
||||
# so offer both. Track branches that already have a worktree to avoid
|
||||
# duplicating them in the "create new" list below.
|
||||
set -l wt_branches
|
||||
set -l path ""
|
||||
for line in (git worktree list --porcelain 2>/dev/null)
|
||||
if string match -q "worktree *" -- $line
|
||||
set path (string replace "worktree " "" -- $line)
|
||||
printf '%s\tworktree\n' (basename $path)
|
||||
else if string match -q "branch *" -- $line
|
||||
set -l branch (string replace "branch refs/heads/" "" -- $line)
|
||||
set -a wt_branches $branch
|
||||
printf '%s\tworktree branch\n' $branch
|
||||
end
|
||||
end
|
||||
|
||||
# Remaining local branches: selecting one creates a new worktree for it.
|
||||
for branch in (git for-each-ref --format='%(refname:short)' refs/heads 2>/dev/null)
|
||||
if not contains -- $branch $wt_branches
|
||||
printf '%s\tbranch\n' $branch
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
complete -c dev-wt -f -n __fish_is_first_arg -a '(__dev_wt_completions)'
|
||||
complete -c dev-wt -s C -l repo -r -a '(__fish_complete_directories)' -d 'Operate on the repo at this path'
|
||||
@@ -1 +0,0 @@
|
||||
complete -c pandoc -f -a '(string match -r ".*\\.md\$" -- (ls))'
|
||||
@@ -1,235 +0,0 @@
|
||||
# fish completion for process-compose -*- shell-script -*-
|
||||
|
||||
function __process_compose_debug
|
||||
set -l file "$BASH_COMP_DEBUG_FILE"
|
||||
if test -n "$file"
|
||||
echo "$argv" >> $file
|
||||
end
|
||||
end
|
||||
|
||||
function __process_compose_perform_completion
|
||||
__process_compose_debug "Starting __process_compose_perform_completion"
|
||||
|
||||
# Extract all args except the last one
|
||||
set -l args (commandline -opc)
|
||||
# Extract the last arg and escape it in case it is a space
|
||||
set -l lastArg (string escape -- (commandline -ct))
|
||||
|
||||
__process_compose_debug "args: $args"
|
||||
__process_compose_debug "last arg: $lastArg"
|
||||
|
||||
# Disable ActiveHelp which is not supported for fish shell
|
||||
set -l requestComp "PROCESS_COMPOSE_ACTIVE_HELP=0 $args[1] __complete $args[2..-1] $lastArg"
|
||||
|
||||
__process_compose_debug "Calling $requestComp"
|
||||
set -l results (eval $requestComp 2> /dev/null)
|
||||
|
||||
# Some programs may output extra empty lines after the directive.
|
||||
# Let's ignore them or else it will break completion.
|
||||
# Ref: https://github.com/spf13/cobra/issues/1279
|
||||
for line in $results[-1..1]
|
||||
if test (string trim -- $line) = ""
|
||||
# Found an empty line, remove it
|
||||
set results $results[1..-2]
|
||||
else
|
||||
# Found non-empty line, we have our proper output
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
set -l comps $results[1..-2]
|
||||
set -l directiveLine $results[-1]
|
||||
|
||||
# For Fish, when completing a flag with an = (e.g., <program> -n=<TAB>)
|
||||
# completions must be prefixed with the flag
|
||||
set -l flagPrefix (string match -r -- '-.*=' "$lastArg")
|
||||
|
||||
__process_compose_debug "Comps: $comps"
|
||||
__process_compose_debug "DirectiveLine: $directiveLine"
|
||||
__process_compose_debug "flagPrefix: $flagPrefix"
|
||||
|
||||
for comp in $comps
|
||||
printf "%s%s\n" "$flagPrefix" "$comp"
|
||||
end
|
||||
|
||||
printf "%s\n" "$directiveLine"
|
||||
end
|
||||
|
||||
# this function limits calls to __process_compose_perform_completion, by caching the result behind $__process_compose_perform_completion_once_result
|
||||
function __process_compose_perform_completion_once
|
||||
__process_compose_debug "Starting __process_compose_perform_completion_once"
|
||||
|
||||
if test -n "$__process_compose_perform_completion_once_result"
|
||||
__process_compose_debug "Seems like a valid result already exists, skipping __process_compose_perform_completion"
|
||||
return 0
|
||||
end
|
||||
|
||||
set --global __process_compose_perform_completion_once_result (__process_compose_perform_completion)
|
||||
if test -z "$__process_compose_perform_completion_once_result"
|
||||
__process_compose_debug "No completions, probably due to a failure"
|
||||
return 1
|
||||
end
|
||||
|
||||
__process_compose_debug "Performed completions and set __process_compose_perform_completion_once_result"
|
||||
return 0
|
||||
end
|
||||
|
||||
# this function is used to clear the $__process_compose_perform_completion_once_result variable after completions are run
|
||||
function __process_compose_clear_perform_completion_once_result
|
||||
__process_compose_debug ""
|
||||
__process_compose_debug "========= clearing previously set __process_compose_perform_completion_once_result variable =========="
|
||||
set --erase __process_compose_perform_completion_once_result
|
||||
__process_compose_debug "Successfully erased the variable __process_compose_perform_completion_once_result"
|
||||
end
|
||||
|
||||
function __process_compose_requires_order_preservation
|
||||
__process_compose_debug ""
|
||||
__process_compose_debug "========= checking if order preservation is required =========="
|
||||
|
||||
__process_compose_perform_completion_once
|
||||
if test -z "$__process_compose_perform_completion_once_result"
|
||||
__process_compose_debug "Error determining if order preservation is required"
|
||||
return 1
|
||||
end
|
||||
|
||||
set -l directive (string sub --start 2 $__process_compose_perform_completion_once_result[-1])
|
||||
__process_compose_debug "Directive is: $directive"
|
||||
|
||||
set -l shellCompDirectiveKeepOrder 32
|
||||
set -l keeporder (math (math --scale 0 $directive / $shellCompDirectiveKeepOrder) % 2)
|
||||
__process_compose_debug "Keeporder is: $keeporder"
|
||||
|
||||
if test $keeporder -ne 0
|
||||
__process_compose_debug "This does require order preservation"
|
||||
return 0
|
||||
end
|
||||
|
||||
__process_compose_debug "This doesn't require order preservation"
|
||||
return 1
|
||||
end
|
||||
|
||||
|
||||
# This function does two things:
|
||||
# - Obtain the completions and store them in the global __process_compose_comp_results
|
||||
# - Return false if file completion should be performed
|
||||
function __process_compose_prepare_completions
|
||||
__process_compose_debug ""
|
||||
__process_compose_debug "========= starting completion logic =========="
|
||||
|
||||
# Start fresh
|
||||
set --erase __process_compose_comp_results
|
||||
|
||||
__process_compose_perform_completion_once
|
||||
__process_compose_debug "Completion results: $__process_compose_perform_completion_once_result"
|
||||
|
||||
if test -z "$__process_compose_perform_completion_once_result"
|
||||
__process_compose_debug "No completion, probably due to a failure"
|
||||
# Might as well do file completion, in case it helps
|
||||
return 1
|
||||
end
|
||||
|
||||
set -l directive (string sub --start 2 $__process_compose_perform_completion_once_result[-1])
|
||||
set --global __process_compose_comp_results $__process_compose_perform_completion_once_result[1..-2]
|
||||
|
||||
__process_compose_debug "Completions are: $__process_compose_comp_results"
|
||||
__process_compose_debug "Directive is: $directive"
|
||||
|
||||
set -l shellCompDirectiveError 1
|
||||
set -l shellCompDirectiveNoSpace 2
|
||||
set -l shellCompDirectiveNoFileComp 4
|
||||
set -l shellCompDirectiveFilterFileExt 8
|
||||
set -l shellCompDirectiveFilterDirs 16
|
||||
|
||||
if test -z "$directive"
|
||||
set directive 0
|
||||
end
|
||||
|
||||
set -l compErr (math (math --scale 0 $directive / $shellCompDirectiveError) % 2)
|
||||
if test $compErr -eq 1
|
||||
__process_compose_debug "Received error directive: aborting."
|
||||
# Might as well do file completion, in case it helps
|
||||
return 1
|
||||
end
|
||||
|
||||
set -l filefilter (math (math --scale 0 $directive / $shellCompDirectiveFilterFileExt) % 2)
|
||||
set -l dirfilter (math (math --scale 0 $directive / $shellCompDirectiveFilterDirs) % 2)
|
||||
if test $filefilter -eq 1; or test $dirfilter -eq 1
|
||||
__process_compose_debug "File extension filtering or directory filtering not supported"
|
||||
# Do full file completion instead
|
||||
return 1
|
||||
end
|
||||
|
||||
set -l nospace (math (math --scale 0 $directive / $shellCompDirectiveNoSpace) % 2)
|
||||
set -l nofiles (math (math --scale 0 $directive / $shellCompDirectiveNoFileComp) % 2)
|
||||
|
||||
__process_compose_debug "nospace: $nospace, nofiles: $nofiles"
|
||||
|
||||
# If we want to prevent a space, or if file completion is NOT disabled,
|
||||
# we need to count the number of valid completions.
|
||||
# To do so, we will filter on prefix as the completions we have received
|
||||
# may not already be filtered so as to allow fish to match on different
|
||||
# criteria than the prefix.
|
||||
if test $nospace -ne 0; or test $nofiles -eq 0
|
||||
set -l prefix (commandline -t | string escape --style=regex)
|
||||
__process_compose_debug "prefix: $prefix"
|
||||
|
||||
set -l completions (string match -r -- "^$prefix.*" $__process_compose_comp_results)
|
||||
set --global __process_compose_comp_results $completions
|
||||
__process_compose_debug "Filtered completions are: $__process_compose_comp_results"
|
||||
|
||||
# Important not to quote the variable for count to work
|
||||
set -l numComps (count $__process_compose_comp_results)
|
||||
__process_compose_debug "numComps: $numComps"
|
||||
|
||||
if test $numComps -eq 1; and test $nospace -ne 0
|
||||
# We must first split on \t to get rid of the descriptions to be
|
||||
# able to check what the actual completion will be.
|
||||
# We don't need descriptions anyway since there is only a single
|
||||
# real completion which the shell will expand immediately.
|
||||
set -l split (string split --max 1 \t $__process_compose_comp_results[1])
|
||||
|
||||
# Fish won't add a space if the completion ends with any
|
||||
# of the following characters: @=/:.,
|
||||
set -l lastChar (string sub -s -1 -- $split)
|
||||
if not string match -r -q "[@=/:.,]" -- "$lastChar"
|
||||
# In other cases, to support the "nospace" directive we trick the shell
|
||||
# by outputting an extra, longer completion.
|
||||
__process_compose_debug "Adding second completion to perform nospace directive"
|
||||
set --global __process_compose_comp_results $split[1] $split[1].
|
||||
__process_compose_debug "Completions are now: $__process_compose_comp_results"
|
||||
end
|
||||
end
|
||||
|
||||
if test $numComps -eq 0; and test $nofiles -eq 0
|
||||
# To be consistent with bash and zsh, we only trigger file
|
||||
# completion when there are no other completions
|
||||
__process_compose_debug "Requesting file completion"
|
||||
return 1
|
||||
end
|
||||
end
|
||||
|
||||
return 0
|
||||
end
|
||||
|
||||
# Since Fish completions are only loaded once the user triggers them, we trigger them ourselves
|
||||
# so we can properly delete any completions provided by another script.
|
||||
# Only do this if the program can be found, or else fish may print some errors; besides,
|
||||
# the existing completions will only be loaded if the program can be found.
|
||||
if type -q "process-compose"
|
||||
# The space after the program name is essential to trigger completion for the program
|
||||
# and not completion of the program name itself.
|
||||
# Also, we use '> /dev/null 2>&1' since '&>' is not supported in older versions of fish.
|
||||
complete --do-complete "process-compose " > /dev/null 2>&1
|
||||
end
|
||||
|
||||
# Remove any pre-existing completions for the program since we will be handling all of them.
|
||||
complete -c process-compose -e
|
||||
|
||||
# this will get called after the two calls below and clear the $__process_compose_perform_completion_once_result global
|
||||
complete -c process-compose -n '__process_compose_clear_perform_completion_once_result'
|
||||
# The call to __process_compose_prepare_completions will setup __process_compose_comp_results
|
||||
# which provides the program's completion choices.
|
||||
# If this doesn't require order preservation, we don't use the -k flag
|
||||
complete -c process-compose -n 'not __process_compose_requires_order_preservation && __process_compose_prepare_completions' -f -a '$__process_compose_comp_results'
|
||||
# otherwise we use the -k flag
|
||||
complete -k -c process-compose -n '__process_compose_requires_order_preservation && __process_compose_prepare_completions' -f -a '$__process_compose_comp_results'
|
||||
@@ -0,0 +1,2 @@
|
||||
# worktrunk completions for fish
|
||||
complete --keep-order --exclusive --command wt --arguments "(test -n \"\$WORKTRUNK_BIN\"; or set -l WORKTRUNK_BIN (type -P wt 2>/dev/null); and COMPLETE=fish \$WORKTRUNK_BIN -- (commandline --current-process --tokenize --cut-at-cursor) (commandline --current-token))"
|
||||
@@ -1,57 +0,0 @@
|
||||
function __dev_repo_name
|
||||
# Use the main worktree (first entry) for consistent naming across worktrees
|
||||
set -l lines (git worktree list --porcelain | string replace -rf "^worktree " "")
|
||||
basename $lines[1]
|
||||
end
|
||||
|
||||
function __dev_clean_shell -d "Build a clean login shell command, unsetting devbox vars so they re-source"
|
||||
# Print one word per line; callers capture with (__dev_clean_shell).
|
||||
echo env
|
||||
for var in (set --names --export | string match 'DEVBOX_*')
|
||||
echo -- -u
|
||||
echo -- $var
|
||||
end
|
||||
echo -- (command -s fish)
|
||||
echo -- --login
|
||||
end
|
||||
|
||||
function __dev_create_session -a session dir
|
||||
if test -z "$dir"
|
||||
set dir (pwd)
|
||||
end
|
||||
if not tmux has-session -t "$session" 2>/dev/null
|
||||
# Clean login shell so the new directory re-sources its environment.
|
||||
set -l shell (__dev_clean_shell)
|
||||
|
||||
tmux new-session -d -s "$session" -c "$dir" -n dev $shell
|
||||
|
||||
# Ensure that new panes in this session also start with the clean environment.
|
||||
tmux set-option -t "$session" default-command (string join ' ' -- $shell)
|
||||
|
||||
set -l left (tmux display-message -t "$session:dev" -p '#{pane_id}')
|
||||
tmux split-window -h -t "$left" -c "$dir" $shell
|
||||
set -l right (tmux display-message -t "$session:dev" -p '#{pane_id}')
|
||||
tmux split-window -v -t "$right" -c "$dir" $shell
|
||||
set -l bottom_right (tmux display-message -t "$session:dev" -p '#{pane_id}')
|
||||
tmux send-keys -t "$left" 'vim' Enter
|
||||
tmux send-keys -t "$right" 'claude' Enter
|
||||
|
||||
tmux select-pane -t "$bottom_right"
|
||||
end
|
||||
end
|
||||
|
||||
function __dev_attach_session -a session
|
||||
if test -z "$TMUX"
|
||||
tmux attach-session -t "$session"
|
||||
else
|
||||
set -l current_session (tmux display-message -p '#S')
|
||||
if test "$current_session" != "$session"
|
||||
tmux switch-client -t "$session"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function __dev_wt_session_name -a repo branch
|
||||
set -l name "$repo-"(string replace -a '/' '-' -- $branch | string replace -a '\\' '-')
|
||||
string replace -a '.' '-' -- $name
|
||||
end
|
||||
@@ -1,7 +1,3 @@
|
||||
# Mise shims so non-interactive shells (scripts, editor spawns) find tools.
|
||||
# Interactive sessions get exact versions from `mise activate` below.
|
||||
fish_add_path ~/.local/share/mise/shims
|
||||
|
||||
if status is-interactive
|
||||
# Commands to run in interactive sessions can go here
|
||||
|
||||
@@ -19,8 +15,12 @@ if status is-interactive
|
||||
# Override globals
|
||||
set -gx EDITOR nvim
|
||||
|
||||
# Enable autoenv
|
||||
source ~/.config/fish/functions/activate.fish
|
||||
|
||||
# Aliases
|
||||
alias vim="nvim"
|
||||
alias dc="podman compose"
|
||||
alias lg="lazygit"
|
||||
alias la="eza --long --header --git --group --time-style long-iso -a"
|
||||
alias record='wf-recorder -g "$(slurp)"'
|
||||
@@ -32,14 +32,12 @@ if status is-interactive
|
||||
|
||||
# Abbreviations
|
||||
# https://fishshell.com/docs/current/cmds/abbr.html
|
||||
abbr --add --position command pc process-compose
|
||||
abbr --add --position command ds devbox services
|
||||
|
||||
# Set Path
|
||||
fish_add_path -p /home/tgrosinger/.dotfiles/bin/linux
|
||||
|
||||
# Per-directory toolchains and global tools.
|
||||
# https://mise.jdx.dev/getting-started.html
|
||||
if type -q mise
|
||||
mise activate fish | source
|
||||
end
|
||||
# Add global devbox packages.
|
||||
# https://www.jetify.com/docs/devbox/devbox-global
|
||||
devbox global shellenv --init-hook | source
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# This file contains fish universal variable definitions.
|
||||
# VERSION: 3.0
|
||||
SETUVAR __fish_initialized:4300
|
||||
SETUVAR fish_user_paths:/home/tgrosinger/\x2elocal/bin\x1e/home/tgrosinger/\x2elocal/share/mise/shims\x1e/home/tgrosinger/go/bin\x1e/home/tgrosinger/\x2edotfiles/bin/linux
|
||||
SETUVAR fish_user_paths:/home/tgrosinger/\x2edotfiles/bin/linux
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
#!/usr/bin/env fish
|
||||
|
||||
#
|
||||
# Autoenv for fish shell.
|
||||
# Based on, but heavily modified from:
|
||||
# https://github.com/loopbit/autoenv_fish
|
||||
#
|
||||
|
||||
set AUTOENV_AUTH_FILE ~/.autoenv_authorized
|
||||
if [ -z "$AUTOENV_ENV_FILENAME" ]
|
||||
set AUTOENV_ENV_FILENAME ".env"
|
||||
end
|
||||
|
||||
# probe to see if we have access to a shasum command, otherwise disable autoenv
|
||||
if which gsha1sum 2>/dev/null >&2
|
||||
# Okay
|
||||
else if which sha1sum 2>/dev/null >&2
|
||||
# Okay
|
||||
else if which shasum 2>/dev/null >&2
|
||||
# Okay
|
||||
else
|
||||
echo "Autoenv cannot locate a compatible shasum binary; not enabling"
|
||||
exit 1
|
||||
end
|
||||
|
||||
# This function will be automatically called on directory change
|
||||
# without needing to override the `cd` command.
|
||||
function autoenv_init --on-variable PWD
|
||||
set defIFS $IFS
|
||||
set IFS (echo -en "\n\b")
|
||||
|
||||
set target $argv[1]
|
||||
set home (dirname $HOME)
|
||||
set search_dir $PWD
|
||||
|
||||
while [ $search_dir != / -a $search_dir != "$home" ]
|
||||
set file "$search_dir/$AUTOENV_ENV_FILENAME"
|
||||
if [ -e $file ]
|
||||
set files $files $file
|
||||
end
|
||||
set search_dir (dirname $search_dir)
|
||||
end
|
||||
|
||||
set numerator (count $files)
|
||||
if [ $numerator -gt 0 ]
|
||||
for x in (seq $numerator)
|
||||
set envfile $files[$x]
|
||||
autoenv_check_authz_and_run "$envfile"
|
||||
end
|
||||
end
|
||||
|
||||
set IFS $defIFS
|
||||
end
|
||||
|
||||
function autoenv_run
|
||||
set file "(realpath "$argv[1]")"
|
||||
autoenv_check_authz_and_run "$file"
|
||||
end
|
||||
|
||||
function autoenv_env
|
||||
builtin echo "autoenv:" "$argv[1]"
|
||||
end
|
||||
|
||||
function autoenv_printf
|
||||
builtin printf "autoenv: "
|
||||
builtin printf "$argv[1]"
|
||||
end
|
||||
|
||||
function autoenv_indent
|
||||
cat -e $argv[1] | sed 's/.*/autoenv: &/'
|
||||
end
|
||||
|
||||
function autoenv_hashline
|
||||
# typeset envfile hash
|
||||
set envfile $argv[1]
|
||||
set hash (shasum "$envfile" | cut -d' ' -f 1)
|
||||
echo "$envfile:$hash"
|
||||
end
|
||||
|
||||
function autoenv_check_authz
|
||||
# typeset envfile hash
|
||||
set envfile $argv[1]
|
||||
set hash (autoenv_hashline "$envfile")
|
||||
touch $AUTOENV_AUTH_FILE
|
||||
grep -Gq "$hash" $AUTOENV_AUTH_FILE
|
||||
end
|
||||
|
||||
function autoenv_check_authz_and_run
|
||||
set envfile $argv[1]
|
||||
if autoenv_check_authz "$envfile"
|
||||
autoenv_source "$envfile"
|
||||
return 0
|
||||
end
|
||||
if [ -z $MC_SID ] #make sure mc is not running
|
||||
autoenv_env
|
||||
autoenv_env "WARNING:"
|
||||
autoenv_env "This is the first time you are about to source $envfile":
|
||||
autoenv_env
|
||||
autoenv_env " --- (begin contents) ---------------------------------------"
|
||||
autoenv_indent "$envfile"
|
||||
autoenv_env " --- (end contents) -----------------------------------------"
|
||||
autoenv_env
|
||||
autoenv_printf "Are you sure you want to allow this? (y/N) \n"
|
||||
read answer
|
||||
if [ $answer = y -o $answer = Y ]
|
||||
autoenv_authorize_env "$envfile"
|
||||
autoenv_source "$envfile"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function autoenv_deauthorize_env
|
||||
#typeset envfile
|
||||
set envfile $argv[1]
|
||||
cp "$AUTOENV_AUTH_FILE" "$AUTOENV_AUTH_FILE.tmp"
|
||||
grep -Gv "$envfile:" "$AUTOENV_AUTH_FILE.tmp" >$AUTOENV_AUTH_FILE
|
||||
end
|
||||
|
||||
function autoenv_authorize_env
|
||||
#typeset envfile
|
||||
set envfile $argv[1]
|
||||
autoenv_deauthorize_env "$envfile"
|
||||
autoenv_hashline "$envfile" >>$AUTOENV_AUTH_FILE
|
||||
end
|
||||
|
||||
function autoenv_source
|
||||
#TODO: Why are global vars not being passed to sourced script?
|
||||
set -g AUTOENV_CUR_FILE $argv[1]
|
||||
set -g AUTOENV_CUR_DIR (dirname $argv[1])
|
||||
source "$argv[1]"
|
||||
#set -e AUTOENV_CUR_FILE
|
||||
#set -e AUTOENV_CUR_DIR
|
||||
end
|
||||
@@ -1,70 +0,0 @@
|
||||
function dev-wt-pr -d "Open a GitHub PR in a worktree in a new tmux window (Octo review + Claude)" -a pr_number branch repo_path
|
||||
if test -z "$pr_number"
|
||||
echo "Usage: dev-wt-pr <pr-number> <branch> [repo-path]"
|
||||
return 1
|
||||
end
|
||||
|
||||
# Opens the PR in a new window of the current tmux session, so it must be run
|
||||
# from inside tmux. Running outside tmux is unsupported for now.
|
||||
if test -z "$TMUX"
|
||||
echo "dev-wt-pr must be run from inside a tmux session"
|
||||
return 1
|
||||
end
|
||||
|
||||
# gh-dash passes the repo path; default to the current directory otherwise.
|
||||
if test -n "$repo_path"
|
||||
cd "$repo_path"; or return 1
|
||||
end
|
||||
|
||||
set -l repo (__dev_repo_name 2>/dev/null)
|
||||
if test -z "$repo"
|
||||
echo "Not in a git repository"
|
||||
return 1
|
||||
end
|
||||
|
||||
set -l main_wt_path (git worktree list --porcelain | head -1 | string replace "worktree " "")
|
||||
set -l parent_dir (dirname "$main_wt_path")
|
||||
# Key the worktree on the PR number to stay unique and avoid slashes in
|
||||
# branch names producing nested directories.
|
||||
set -l wt_path "$parent_dir/$repo-pr-$pr_number"
|
||||
|
||||
# Create the worktree if it doesn't already exist.
|
||||
if not test -d "$wt_path"
|
||||
echo "Creating worktree for PR #$pr_number at $wt_path..."
|
||||
git worktree add --detach "$wt_path"; or return 1
|
||||
|
||||
# gh pr checkout handles both same-repo and fork PRs uniformly.
|
||||
if not fish -c "cd '$wt_path'; and gh pr checkout $pr_number"
|
||||
echo "Failed to check out PR #$pr_number"
|
||||
git worktree remove --force "$wt_path"
|
||||
return 1
|
||||
end
|
||||
|
||||
# Run repo-specific setup hook if present (matches dev-wt).
|
||||
set -l setup_hook "$main_wt_path/.worktree-setup.sh"
|
||||
if test -x "$setup_hook"
|
||||
echo "Running worktree setup hook..."
|
||||
bash "$setup_hook" "$wt_path" "$main_wt_path" "$pr_number"
|
||||
end
|
||||
end
|
||||
|
||||
# Window name like "123-fix-login"; flatten any slashes from the branch name.
|
||||
set -l window_name "$pr_number-"(string replace -a '/' '-' -- $branch)
|
||||
|
||||
# Reuse the window if it already exists (e.g. re-pressing the shortcut).
|
||||
if tmux list-windows -F '#{window_name}' 2>/dev/null | string match -q -- "$window_name"
|
||||
tmux select-window -t "$window_name"
|
||||
return
|
||||
end
|
||||
|
||||
# Clean login shell so the worktree re-sources its environment.
|
||||
set -l shell (__dev_clean_shell)
|
||||
|
||||
set -l win (tmux new-window -P -F '#{window_id}' -n "$window_name" -c "$wt_path" $shell)
|
||||
tmux send-keys -t "$win" 'nvim -c "Octo review start"' Enter
|
||||
|
||||
set -l right (tmux split-window -h -P -F '#{pane_id}' -t "$win" -c "$wt_path" $shell)
|
||||
tmux send-keys -t "$right" 'claude' Enter
|
||||
|
||||
tmux select-window -t "$win"
|
||||
end
|
||||
@@ -1,98 +0,0 @@
|
||||
function dev-wt -d "Switch to a tmux dev session for a worktree, creating it if needed"
|
||||
argparse 'C/repo=' -- $argv
|
||||
or return 1
|
||||
|
||||
set -l name $argv[1]
|
||||
|
||||
# With -C/--repo, behave exactly as if invoked from within that directory by
|
||||
# cd-ing there for the duration and restoring the caller's cwd afterward.
|
||||
if set -q _flag_repo
|
||||
if not test -d "$_flag_repo"
|
||||
echo "Not a directory: $_flag_repo"
|
||||
return 1
|
||||
end
|
||||
set -l prev_pwd $PWD
|
||||
cd "$_flag_repo"; or return 1
|
||||
__dev_wt_impl $name
|
||||
set -l rc $status
|
||||
cd "$prev_pwd"
|
||||
return $rc
|
||||
end
|
||||
|
||||
__dev_wt_impl $name
|
||||
end
|
||||
|
||||
function __dev_wt_impl -a name
|
||||
set -l repo (__dev_repo_name 2>/dev/null)
|
||||
if test -z "$repo"
|
||||
echo "Not in a git repository"
|
||||
return 1
|
||||
end
|
||||
|
||||
# No argument: switch to a session for the main repo (not a worktree)
|
||||
if test -z "$name"
|
||||
set -l main_wt_path (git worktree list --porcelain | head -1 | string replace "worktree " "")
|
||||
set -l session (string replace -a '.' '-' -- $repo)
|
||||
__dev_create_session $session $main_wt_path
|
||||
__dev_attach_session $session
|
||||
return
|
||||
end
|
||||
|
||||
set -l wt_path ""
|
||||
set -l wt_branch ""
|
||||
|
||||
# Search for worktree by directory basename or branch name
|
||||
set -l current_path ""
|
||||
set -l current_branch ""
|
||||
for line in (git worktree list --porcelain)
|
||||
if string match -q "worktree *" -- $line
|
||||
set current_path (string replace "worktree " "" -- $line)
|
||||
set current_branch ""
|
||||
else if string match -q "branch *" -- $line
|
||||
set current_branch (string replace "branch refs/heads/" "" -- $line)
|
||||
if test (basename "$current_path") = "$name"; or test "$current_branch" = "$name"
|
||||
set wt_path $current_path
|
||||
set wt_branch $current_branch
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# If worktree not found, create it
|
||||
if test -z "$wt_path"
|
||||
set -l main_wt_path (git worktree list --porcelain | head -1 | string replace "worktree " "")
|
||||
set -l parent_dir (dirname "$main_wt_path")
|
||||
set wt_path "$parent_dir/$repo-$name"
|
||||
set wt_branch "$name"
|
||||
|
||||
if test -d "$wt_path"
|
||||
echo "Error: directory already exists: $wt_path"
|
||||
return 1
|
||||
end
|
||||
|
||||
# Create branch from main if it doesn't exist
|
||||
if not git rev-parse --verify "$wt_branch" >/dev/null 2>&1
|
||||
echo "Creating branch '$wt_branch' from main..."
|
||||
git branch "$wt_branch" main
|
||||
end
|
||||
|
||||
echo "Creating worktree at $wt_path..."
|
||||
git worktree add "$wt_path" "$wt_branch"
|
||||
|
||||
# Run repo-specific setup hook if present
|
||||
set -l setup_hook "$main_wt_path/.worktree-setup.sh"
|
||||
if test -x "$setup_hook"
|
||||
echo "Running worktree setup hook..."
|
||||
bash "$setup_hook" "$wt_path" "$main_wt_path" "$wt_branch"
|
||||
end
|
||||
end
|
||||
|
||||
if test -z "$wt_branch"
|
||||
echo "Worktree '$name' has no branch (detached HEAD)"
|
||||
return 1
|
||||
end
|
||||
|
||||
set -l session (__dev_wt_session_name $repo $wt_branch)
|
||||
__dev_create_session $session $wt_path
|
||||
__dev_attach_session $session
|
||||
end
|
||||
@@ -1,5 +0,0 @@
|
||||
function dev -d "Create and attach to a tmux dev session for the current directory"
|
||||
set -l session (basename (pwd) | string replace -a '.' '-')
|
||||
__dev_create_session $session
|
||||
__dev_attach_session $session
|
||||
end
|
||||
@@ -1,7 +0,0 @@
|
||||
function pandoc -d "Convert a markdown file to docx using pandoc in podman" -a input
|
||||
set -l output (string replace -r '\.md$' '.docx' $input)
|
||||
|
||||
podman run --rm -v "$(pwd):/data:z" \
|
||||
--userns keep-id:uid=1000,gid=1000 \
|
||||
pandoc/core:3.8-alpine --from markdown --to docx -o "/data/$output" "/data/$input"
|
||||
end
|
||||
@@ -1,4 +0,0 @@
|
||||
function record -d "Capture a screen recording with no audio"
|
||||
wf-recorder -c libopenh264 --no-audio -g "$(slurp)" -f output.mp4
|
||||
end
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
function reencode -d "Re-encode a video to VBR with no audio" -a input
|
||||
set -l name (string replace -r '\.[^.]+$' '' $input)
|
||||
set -l ext (string replace -r '.*\.' '' $input)
|
||||
set -l output "$name-reenc.$ext"
|
||||
|
||||
ffmpeg -i $input -c:v libopenh264 -crf 23 -preset slow -an $output
|
||||
end
|
||||
@@ -1,10 +0,0 @@
|
||||
function togif -d "Convert a video to a high-quality gif" -a input
|
||||
set -l name (string replace -r '\.[^.]+$' '' $input)
|
||||
set -l output "$name.gif"
|
||||
set -l palette (mktemp --suffix .png)
|
||||
|
||||
ffmpeg -i $input -vf "fps=15,palettegen" -y $palette
|
||||
ffmpeg -i $input -i $palette -lavfi "fps=15 [x]; [x][1:v] paletteuse" -y $output
|
||||
|
||||
rm -f $palette
|
||||
end
|
||||
@@ -1,6 +0,0 @@
|
||||
function tomov -d "Convert a video to a GitHub-uploadable .mov" -a input
|
||||
set -l name (string replace -r '\.[^.]+$' '' $input)
|
||||
set -l output "$name.mov"
|
||||
|
||||
ffmpeg -i $input -c:v libopenh264 -crf 23 -preset slow -pix_fmt yuv420p -an $output
|
||||
end
|
||||
@@ -0,0 +1,15 @@
|
||||
# worktrunk shell integration for fish
|
||||
# Sources full integration from binary on first use.
|
||||
# Docs: https://worktrunk.dev/config/#shell-integration
|
||||
# Check: wt config show | Uninstall: wt config shell uninstall
|
||||
|
||||
function wt
|
||||
command wt config shell init fish | source
|
||||
# Check both command exit code ($pipestatus[1]) and source exit code ($pipestatus[2])
|
||||
# If source fails, the function isn't replaced and we'd infinite-loop calling ourselves
|
||||
set -l wt_status $pipestatus[1]
|
||||
set -l source_status $pipestatus[2]
|
||||
test $wt_status -eq 0; or return $wt_status
|
||||
test $source_status -eq 0; or return $source_status
|
||||
wt $argv
|
||||
end
|
||||
@@ -1,122 +0,0 @@
|
||||
prSections:
|
||||
- title: My Pull Requests
|
||||
filters: is:open author:@me
|
||||
- title: Needs My Review
|
||||
filters: is:open review-requested:@me
|
||||
- title: Involved
|
||||
filters: is:open involves:@me -author:@me
|
||||
issuesSections:
|
||||
- title: My Issues
|
||||
filters: is:open author:@me
|
||||
- title: Assigned
|
||||
filters: is:open assignee:@me
|
||||
- title: Involved
|
||||
filters: is:open involves:@me -author:@me
|
||||
notificationsSections:
|
||||
- title: All
|
||||
filters: ""
|
||||
- title: Created
|
||||
filters: reason:author
|
||||
- title: Participating
|
||||
filters: reason:participating
|
||||
- title: Mentioned
|
||||
filters: reason:mention
|
||||
- title: Review Requested
|
||||
filters: reason:review-requested
|
||||
- title: Assigned
|
||||
filters: reason:assign
|
||||
- title: Subscribed
|
||||
filters: reason:subscribed
|
||||
- title: Team Mentioned
|
||||
filters: reason:team-mention
|
||||
repo:
|
||||
branchesRefetchIntervalSeconds: 30
|
||||
prsRefetchIntervalSeconds: 60
|
||||
defaults:
|
||||
preview:
|
||||
open: true
|
||||
width: 0.45
|
||||
height: 0.6
|
||||
position: auto
|
||||
prsLimit: 20
|
||||
prApproveComment: LGTM
|
||||
issuesLimit: 20
|
||||
notificationsLimit: 20
|
||||
view: prs
|
||||
layout:
|
||||
prs:
|
||||
updatedAt:
|
||||
width: 5
|
||||
createdAt:
|
||||
width: 5
|
||||
repo:
|
||||
width: 20
|
||||
author:
|
||||
width: 15
|
||||
authorIcon:
|
||||
hidden: false
|
||||
labels:
|
||||
width: 22
|
||||
hidden: true
|
||||
assignees:
|
||||
width: 20
|
||||
hidden: true
|
||||
base:
|
||||
width: 15
|
||||
hidden: true
|
||||
lines:
|
||||
width: 15
|
||||
issues:
|
||||
updatedAt:
|
||||
width: 5
|
||||
createdAt:
|
||||
width: 5
|
||||
repo:
|
||||
width: 15
|
||||
creator:
|
||||
width: 10
|
||||
creatorIcon:
|
||||
hidden: false
|
||||
assignees:
|
||||
width: 20
|
||||
hidden: true
|
||||
refetchIntervalMinutes: 30
|
||||
keybindings:
|
||||
prs:
|
||||
- key: C
|
||||
name: review in worktree
|
||||
command: >
|
||||
fish -c "dev-wt-pr {{.PrNumber}} {{.HeadRefName}} {{.RepoPath}}"
|
||||
universal:
|
||||
- key: g
|
||||
name: lazygit
|
||||
command: >
|
||||
cd {{.RepoPath}}; lazygit
|
||||
repoPaths: {}
|
||||
theme:
|
||||
colors:
|
||||
text:
|
||||
primary: "#4c4f69"
|
||||
secondary: "#179299"
|
||||
inverted: "#dce0e8"
|
||||
faint: "#5c5f77"
|
||||
warning: "#df8e1d"
|
||||
success: "#40a02b"
|
||||
error: "#d20f39"
|
||||
background:
|
||||
selected: "#ccd0da"
|
||||
border:
|
||||
primary: "#179299"
|
||||
secondary: "#bcc0cc"
|
||||
faint: "#ccd0da"
|
||||
ui:
|
||||
sectionsShowCount: true
|
||||
table:
|
||||
showSeparator: true
|
||||
compact: false
|
||||
pager:
|
||||
diff: hunk
|
||||
confirmQuit: false
|
||||
showAuthorIcons: true
|
||||
smartFilteringAtLaunch: true
|
||||
includeReadNotifications: true
|
||||
@@ -1,5 +0,0 @@
|
||||
theme = Catppuccin Latte
|
||||
font-family = JetBrainsMono Nerd Font Mono
|
||||
|
||||
# https://pi.dev/docs/latest/terminal-setup
|
||||
keybind = alt+backspace=text:\x1b\x7f
|
||||
@@ -1 +0,0 @@
|
||||
theme = "catppuccin-latte"
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"version": 1,
|
||||
"lastSeenCliVersion": "0.14.1"
|
||||
}
|
||||
@@ -7,9 +7,6 @@ promptToReturnFromSubprocess: false # removes "press enter to return to lazygit"
|
||||
notARepository: 'skip'
|
||||
git:
|
||||
autoForwardBranches: "none"
|
||||
pagers:
|
||||
- pager: delta --paging=never --line-numbers --hyperlinks --hyperlinks-file-link-format="lazygit-edit://{path}:{line}"
|
||||
- pager: delta --paging=never --line-numbers --no-gitconfig --light
|
||||
os:
|
||||
editPreset: "nvim"
|
||||
gui:
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
[settings]
|
||||
|
||||
# Pin exact versions, URLs, and checksums in mise.lock so a tampered
|
||||
# re-release of the same version can't slip in silently.
|
||||
lockfile = true
|
||||
|
||||
# Block the asdf plugin backend (arbitrary community shell scripts) in favor
|
||||
# of aqua/ubi/core backends, which verify checksums and signatures. Remove if
|
||||
# a tool you need exists only as an asdf plugin.
|
||||
disable_backends = ["asdf", "ubi"]
|
||||
|
||||
# Never install versions less than 2 weeks old.
|
||||
minimum_release_age = "14d"
|
||||
|
||||
[tools]
|
||||
node = "lts"
|
||||
pnpm = "latest"
|
||||
"npm:@anthropic-ai/sandbox-runtime" = "latest"
|
||||
"npm:typescript-language-server" = "latest"
|
||||
"npm:typescript" = "latest"
|
||||
"github:Satty-org/Satty" = "0.20.1"
|
||||
"github:DarthSim/overmind" = "latest"
|
||||
"github:F1bonacc1/process-compose" = "latest"
|
||||
"github:modem-dev/hunk" = "latest"
|
||||
"npm:@earendil-works/pi-coding-agent" = "latest"
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
// markdownlint-cli2 base config used by nvim-lint (see lua/plugins/markdown.lua).
|
||||
"config": {
|
||||
// Disable line-length errors; long lines are soft-wrapped on purpose.
|
||||
"MD013": false
|
||||
}
|
||||
}
|
||||
@@ -1,44 +1,46 @@
|
||||
{
|
||||
"LazyVim": { "branch": "main", "commit": "c10948c50b18fae7f256433afdef09e432410480" },
|
||||
"SchemaStore.nvim": { "branch": "main", "commit": "6ff1f21b2e2b77ec59f7433ce2d9fbc052d908ac" },
|
||||
"blink.cmp": { "branch": "main", "commit": "78336bc89ee5365633bcf754d93df01678b5c08f" },
|
||||
"CopilotChat.nvim": { "branch": "main", "commit": "21bdecb25aa72119d11d7fc08c7e0ce323f1b540" },
|
||||
"LazyVim": { "branch": "main", "commit": "28db03f958d58dfff3c647ce28fdc1cb88ac158d" },
|
||||
"SchemaStore.nvim": { "branch": "main", "commit": "fb7b9034285a5658c746faa12eff8c1c9d9b11b1" },
|
||||
"blink-copilot": { "branch": "main", "commit": "7ad8209b2f880a2840c94cdcd80ab4dc511d4f39" },
|
||||
"blink.cmp": { "branch": "main", "commit": "b19413d214068f316c78978b08264ed1c41830ec" },
|
||||
"bufferline.nvim": { "branch": "main", "commit": "655133c3b4c3e5e05ec549b9f8cc2894ac6f51b3" },
|
||||
"catppuccin": { "branch": "main", "commit": "e068ab5f8261f23f6f71ffd8791ae40315b77b9c" },
|
||||
"conform.nvim": { "branch": "master", "commit": "619363c30309d29ffa631e67c8183f2a72caa373" },
|
||||
"diffview.nvim": { "branch": "main", "commit": "bcf4b62b4acc36a7c3d19e423713a220c838a668" },
|
||||
"catppuccin": { "branch": "main", "commit": "beaf41a30c26fd7d6c386d383155cbd65dd554cd" },
|
||||
"conform.nvim": { "branch": "master", "commit": "238f542a118984a88124fc915d5b981680418707" },
|
||||
"copilot.lua": { "branch": "master", "commit": "0ab400d547814b04b39a069208ff7b40ab22dfb5" },
|
||||
"flash.nvim": { "branch": "main", "commit": "fcea7ff883235d9024dc41e638f164a450c14ca2" },
|
||||
"friendly-snippets": { "branch": "main", "commit": "6cd7280adead7f586db6fccbd15d2cac7e2188b9" },
|
||||
"focus.nvim": { "branch": "master", "commit": "8732b45ceef77b576e60442e768437bce7915107" },
|
||||
"friendly-snippets": { "branch": "main", "commit": "572f5660cf05f8cd8834e096d7b4c921ba18e175" },
|
||||
"gitsigns.nvim": { "branch": "main", "commit": "42d6aed4e94e0f0bbced16bbdcc42f57673bd75e" },
|
||||
"grug-far.nvim": { "branch": "main", "commit": "c69859c1d5427ab5fc7ed12380ab521b4e336691" },
|
||||
"grug-far.nvim": { "branch": "main", "commit": "794f03c97afc7f4b03fb6ec5111be507df1850cf" },
|
||||
"lazy.nvim": { "branch": "main", "commit": "85c7ff3711b730b4030d03144f6db6375044ae82" },
|
||||
"lazydev.nvim": { "branch": "main", "commit": "ff2cbcba459b637ec3fd165a2be59b7bbaeedf0d" },
|
||||
"lualine.nvim": { "branch": "master", "commit": "221ce6b2d999187044529f49da6554a92f740a96" },
|
||||
"lazydev.nvim": { "branch": "main", "commit": "5231c62aa83c2f8dc8e7ba957aa77098cda1257d" },
|
||||
"lualine.nvim": { "branch": "master", "commit": "47f91c416daef12db467145e16bed5bbfe00add8" },
|
||||
"markdown-preview.nvim": { "branch": "master", "commit": "a923f5fc5ba36a3b17e289dc35dc17f66d0548ee" },
|
||||
"mason-lspconfig.nvim": { "branch": "main", "commit": "47059d71b42d74b0a1e9f61c1d99d301039c3b5b" },
|
||||
"mason.nvim": { "branch": "main", "commit": "2a6940af80375532e5e9e7c1f2fc6319a1b7a69d" },
|
||||
"mini.ai": { "branch": "main", "commit": "cb20f298ebf5ae91924cd0c6c310712de2ef4086" },
|
||||
"mini.diff": { "branch": "main", "commit": "0743d26bd858ebe32efcf5c86a91a422a000f273" },
|
||||
"mini.icons": { "branch": "main", "commit": "24dbea2195c477e57d581215839a6ab915f34b14" },
|
||||
"mini.pairs": { "branch": "main", "commit": "fd150ac39b78e6a2286f5138e472b7dc7eba43b9" },
|
||||
"mini.surround": { "branch": "main", "commit": "a2f644f3759edd3d3f8b6a6d55378408bfe6d290" },
|
||||
"mason-lspconfig.nvim": { "branch": "main", "commit": "80c0130c5f16b551865a69e832f1feadeedb5fbe" },
|
||||
"mason.nvim": { "branch": "main", "commit": "44d1e90e1f66e077268191e3ee9d2ac97cc18e65" },
|
||||
"mini.ai": { "branch": "main", "commit": "bfb26d9072670c3aaefab0f53024b2f3729c8083" },
|
||||
"mini.diff": { "branch": "main", "commit": "6010e588e9ed14724880f244d7fa3df8f0be3f46" },
|
||||
"mini.icons": { "branch": "main", "commit": "efc85e42262cd0c9e1fdbf806c25cb0be6de115c" },
|
||||
"mini.pairs": { "branch": "main", "commit": "d5a29b6254dad07757832db505ea5aeab9aad43a" },
|
||||
"mini.surround": { "branch": "main", "commit": "cc7b9d0a056b5fa6915ffac1cb91f29bf7c96f69" },
|
||||
"noice.nvim": { "branch": "main", "commit": "7bfd942445fb63089b59f97ca487d605e715f155" },
|
||||
"nui.nvim": { "branch": "main", "commit": "de740991c12411b663994b2860f1a4fd0937c130" },
|
||||
"nvim-ansible": { "branch": "main", "commit": "c7f595d568b588942d4d0c37b5cd6cae3764a148" },
|
||||
"nvim-lint": { "branch": "master", "commit": "a219b2c9e5b4765e5c845aba119dad55806fcaf1" },
|
||||
"nvim-lspconfig": { "branch": "master", "commit": "292f44408498103c47996ff5c18fd366293840d8" },
|
||||
"nvim-treesitter": { "branch": "main", "commit": "4916d6592ede8c07973490d9322f187e07dfefac" },
|
||||
"nvim-treesitter-textobjects": { "branch": "main", "commit": "851e865342e5a4cb1ae23d31caf6e991e1c99f1e" },
|
||||
"nvim-ts-autotag": { "branch": "main", "commit": "88c1453db4ba7dd24131086fe51fdf74e587d275" },
|
||||
"octo.nvim": { "branch": "master", "commit": "b9a73e167f851a98d8f29d62658d3640bb8a7314" },
|
||||
"nvim-ansible": { "branch": "main", "commit": "bba61168b7aef735e7f950fdfece5ef6c388eacf" },
|
||||
"nvim-lint": { "branch": "master", "commit": "ca6ea12daf0a4d92dc24c5c9ae22a1f0418ade37" },
|
||||
"nvim-lspconfig": { "branch": "master", "commit": "92ee7d42320edfbb81f3cad851314ab197fa324a" },
|
||||
"nvim-treesitter": { "branch": "main", "commit": "8aada0e3940c573e38b417a226b43bc8675f8958" },
|
||||
"nvim-treesitter-textobjects": { "branch": "main", "commit": "baa6b4ec28c8be5e4a96f9b1b6ae9db85ec422f8" },
|
||||
"nvim-ts-autotag": { "branch": "main", "commit": "c4ca798ab95b316a768d51eaaaee48f64a4a46bc" },
|
||||
"persistence.nvim": { "branch": "main", "commit": "b20b2a7887bd39c1a356980b45e03250f3dce49c" },
|
||||
"plenary.nvim": { "branch": "master", "commit": "74b06c6c75e4eeb3108ec01852001636d85a932b" },
|
||||
"render-markdown.nvim": { "branch": "main", "commit": "f422cb5c6855f150e2ddcfaf44e7157b98b34f6a" },
|
||||
"sidekick.nvim": { "branch": "main", "commit": "208e1c5b8170c01fd1d07df0139322a76479b235" },
|
||||
"snacks.nvim": { "branch": "main", "commit": "882c996cf28183f4d63640de0b4c02ec886d01f2" },
|
||||
"plenary.nvim": { "branch": "master", "commit": "b9fd5226c2f76c951fc8ed5923d85e4de065e509" },
|
||||
"render-markdown.nvim": { "branch": "main", "commit": "c54380dd4d8d1738b9691a7c349ecad7967ac12e" },
|
||||
"sidekick.nvim": { "branch": "main", "commit": "c2bdf8cfcd87a6be5f8b84322c1b5052e78e302e" },
|
||||
"snacks.nvim": { "branch": "main", "commit": "fe7cfe9800a182274d0f868a74b7263b8c0c020b" },
|
||||
"todo-comments.nvim": { "branch": "main", "commit": "31e3c38ce9b29781e4422fc0322eb0a21f4e8668" },
|
||||
"tokyonight.nvim": { "branch": "main", "commit": "cdc07ac78467a233fd62c493de29a17e0cf2b2b6" },
|
||||
"tokyonight.nvim": { "branch": "main", "commit": "5da1b76e64daf4c5d410f06bcb6b9cb640da7dfd" },
|
||||
"trouble.nvim": { "branch": "main", "commit": "bd67efe408d4816e25e8491cc5ad4088e708a69a" },
|
||||
"ts-comments.nvim": { "branch": "main", "commit": "a59d6092213447450191122c9346f309161504cb" },
|
||||
"vim-tmux-navigator": { "branch": "master", "commit": "e41c431a0c7b7388ae7ba341f01a0d217eb3a432" },
|
||||
"ts-comments.nvim": { "branch": "main", "commit": "123a9fb12e7229342f807ec9e6de478b1102b041" },
|
||||
"vim-tmux-navigator": { "branch": "master", "commit": "c45243dc1f32ac6bcf6068e5300f3b2b237e576a" },
|
||||
"which-key.nvim": { "branch": "main", "commit": "3aab2147e74890957785941f0c1ad87d0a44c15a" }
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
{
|
||||
"extras": [
|
||||
"lazyvim.plugins.extras.ai.copilot",
|
||||
"lazyvim.plugins.extras.ai.copilot-chat",
|
||||
"lazyvim.plugins.extras.ai.sidekick",
|
||||
"lazyvim.plugins.extras.coding.mini-surround",
|
||||
"lazyvim.plugins.extras.editor.mini-diff",
|
||||
@@ -11,8 +13,7 @@
|
||||
"lazyvim.plugins.extras.lang.svelte",
|
||||
"lazyvim.plugins.extras.lang.tailwind",
|
||||
"lazyvim.plugins.extras.lang.typescript",
|
||||
"lazyvim.plugins.extras.lang.yaml",
|
||||
"lazyvim.plugins.extras.util.octo"
|
||||
"lazyvim.plugins.extras.lang.yaml"
|
||||
],
|
||||
"install_version": 8,
|
||||
"news": {
|
||||
|
||||
@@ -1,16 +1,3 @@
|
||||
-- Keymaps are automatically loaded on the VeryLazy event
|
||||
-- Default keymaps that are always set: https://github.com/LazyVim/LazyVim/blob/main/lua/lazyvim/config/keymaps.lua
|
||||
-- Add any additional keymaps here
|
||||
|
||||
vim.keymap.set("n", "<leader>aP", function()
|
||||
Snacks.picker.files({
|
||||
cwd = vim.fn.expand("~/.claude/plans"),
|
||||
title = "Claude plans",
|
||||
})
|
||||
end, { desc = "Find Claude plan" })
|
||||
|
||||
vim.keymap.set("n", "<leader>fP", function()
|
||||
local path = vim.fn.expand("%:p")
|
||||
vim.fn.setreg("+", path)
|
||||
vim.notify("Copied: " .. path)
|
||||
end, { desc = "Copy file path (absolute)" })
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
return {
|
||||
{
|
||||
"folke/which-key.nvim",
|
||||
opts = {
|
||||
spec = {
|
||||
{ "<leader>r", group = "review" },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
dir = "/home/tgrosinger/code/claude-review",
|
||||
cmd = "ClaudeReview",
|
||||
dependencies = {
|
||||
"dlyongemallo/diffview.nvim",
|
||||
},
|
||||
opts = {},
|
||||
},
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
-- Cheatsheet
|
||||
--
|
||||
-- Workflow: open a plan/diff from Claude Code, mark it up, copy, paste back.
|
||||
-- Works inside Diffview buffers as well.
|
||||
--
|
||||
-- Keymaps:
|
||||
-- <leader>rc Add comment at cursor or visual selection
|
||||
-- <leader>rp Preview all comments (editable buffer)
|
||||
-- <leader>ry Copy review to clipboard (paste into Claude Code)
|
||||
-- <leader>rs Show comments at cursor
|
||||
-- <leader>rl List all comments (Telescope/fzf/quickfix)
|
||||
-- <leader>rd Delete comment at cursor
|
||||
-- <leader>rx Clear all comments
|
||||
-- <leader>rw Save review to file
|
||||
--
|
||||
-- Inside the comment input window: <C-s> submits, <Esc> or q cancels.
|
||||
-- (Terminals can't distinguish <C-CR> from <CR>, so we bind <C-s> instead.)
|
||||
|
||||
return {}
|
||||
|
||||
-- return {
|
||||
-- {
|
||||
-- "folke/which-key.nvim",
|
||||
-- opts = {
|
||||
-- spec = {
|
||||
-- { "<leader>r", group = "review" },
|
||||
-- },
|
||||
-- },
|
||||
-- },
|
||||
-- {
|
||||
-- "choplin/code-review.nvim",
|
||||
-- cmd = {
|
||||
-- "CodeReviewComment",
|
||||
-- "CodeReviewShowComment",
|
||||
-- "CodeReviewList",
|
||||
-- "CodeReviewPreview",
|
||||
-- "CodeReviewSave",
|
||||
-- "CodeReviewCopy",
|
||||
-- "CodeReviewClear",
|
||||
-- "CodeReviewDeleteComment",
|
||||
-- },
|
||||
-- keys = {
|
||||
-- { "<leader>rc", mode = { "n", "v" }, desc = "Code review: add comment" },
|
||||
-- { "<leader>rp", desc = "Code review: preview" },
|
||||
-- { "<leader>ry", desc = "Code review: copy to clipboard" },
|
||||
-- { "<leader>rs", desc = "Code review: show at cursor" },
|
||||
-- { "<leader>rl", desc = "Code review: list comments" },
|
||||
-- { "<leader>rd", desc = "Code review: delete at cursor" },
|
||||
-- { "<leader>rx", desc = "Code review: clear all" },
|
||||
-- { "<leader>rw", desc = "Code review: save to file" },
|
||||
-- },
|
||||
-- opts = {
|
||||
-- ui = {
|
||||
-- input_window = {
|
||||
-- title = " Add Comment (C-s to submit) ",
|
||||
-- },
|
||||
-- },
|
||||
-- output = {
|
||||
-- -- Flat format optimized for pasting into AI assistants like Claude Code:
|
||||
-- -- path/to/file.lua:L42: comment text
|
||||
-- format = "minimal",
|
||||
-- },
|
||||
-- },
|
||||
-- init = function()
|
||||
-- vim.api.nvim_create_autocmd("User", {
|
||||
-- pattern = "CodeReviewInputEnter",
|
||||
-- callback = function(ev)
|
||||
-- local funcs = require("code-review").get_input_buffer_functions(ev.data.buf)
|
||||
-- vim.keymap.set({ "i", "n" }, "<C-s>", funcs.submit, { buffer = ev.data.buf })
|
||||
-- end,
|
||||
-- })
|
||||
-- end,
|
||||
-- },
|
||||
-- }
|
||||
@@ -1,62 +0,0 @@
|
||||
-- Cheatsheet
|
||||
--
|
||||
-- Keymaps:
|
||||
-- <leader>gv Toggle working-tree diff (uncommitted changes vs HEAD)
|
||||
-- <leader>gV File history for the whole branch
|
||||
-- <leader>gH File history for the current file (archaeology)
|
||||
--
|
||||
-- DiffviewOpen — compare two states, flat list of changed files:
|
||||
-- :DiffviewOpen uncommitted changes
|
||||
-- :DiffviewOpen HEAD~3 last 3 commits + uncommitted
|
||||
-- :DiffviewOpen main...feature PR-style: what feature adds vs main
|
||||
--
|
||||
-- DiffviewFileHistory — browse commits over time, pick one to see its diff:
|
||||
-- :DiffviewFileHistory every commit reachable from HEAD
|
||||
-- :DiffviewFileHistory % only commits touching current file
|
||||
-- :DiffviewFileHistory path/to/dir only commits touching that path
|
||||
-- :DiffviewFileHistory --range=main..HEAD only commits on this branch
|
||||
--
|
||||
-- Inside the view: q closes, <tab>/<s-tab> next/prev file, g? for full help.
|
||||
|
||||
return {
|
||||
{
|
||||
"dlyongemallo/diffview.nvim",
|
||||
cmd = {
|
||||
"DiffviewOpen",
|
||||
"DiffviewClose",
|
||||
"DiffviewToggleFiles",
|
||||
"DiffviewFocusFiles",
|
||||
"DiffviewRefresh",
|
||||
"DiffviewFileHistory",
|
||||
},
|
||||
keys = {
|
||||
{
|
||||
"<leader>gv",
|
||||
function()
|
||||
if next(require("diffview.lib").views) == nil then
|
||||
vim.cmd("DiffviewOpen")
|
||||
else
|
||||
vim.cmd("DiffviewClose")
|
||||
end
|
||||
end,
|
||||
desc = "Diffview toggle (working tree)",
|
||||
},
|
||||
{ "<leader>gV", "<cmd>DiffviewFileHistory<cr>", desc = "Diffview file history (branch)" },
|
||||
{ "<leader>gH", "<cmd>DiffviewFileHistory %<cr>", desc = "Diffview file history (current file)" },
|
||||
},
|
||||
opts = {
|
||||
-- Attach LSP to the working-tree side so gd/references/diagnostics work.
|
||||
default_args = {
|
||||
DiffviewOpen = { "--imply-local" },
|
||||
DiffviewFileHistory = { "--imply-local" },
|
||||
},
|
||||
enhanced_diff_hl = true,
|
||||
view = {
|
||||
merge_tool = {
|
||||
layout = "diff3_mixed",
|
||||
disable_diagnostics = true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
return {
|
||||
-- Point markdownlint-cli2 at a global config so MD013 (line-length) stays
|
||||
-- disabled everywhere. The linter runs over stdin, so it can't reliably
|
||||
-- auto-discover a per-project config; pass --config explicitly instead.
|
||||
{
|
||||
"mfussenegger/nvim-lint",
|
||||
opts = function()
|
||||
local config = vim.fn.stdpath("config") .. "/.markdownlint-cli2.jsonc"
|
||||
require("lint").linters["markdownlint-cli2"].args = { "--config", config, "-" }
|
||||
end,
|
||||
},
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
return {
|
||||
"folke/persistence.nvim",
|
||||
init = function()
|
||||
vim.api.nvim_create_autocmd("VimEnter", {
|
||||
group = vim.api.nvim_create_augroup("PersistenceAutoload", { clear = true }),
|
||||
nested = true,
|
||||
callback = function()
|
||||
if vim.fn.argc() == 0 then
|
||||
require("persistence").load()
|
||||
end
|
||||
end,
|
||||
})
|
||||
vim.api.nvim_create_autocmd({ "BufWritePost", "FocusLost" }, {
|
||||
group = vim.api.nvim_create_augroup("PersistenceCrashSave", { clear = true }),
|
||||
callback = function()
|
||||
require("persistence").save()
|
||||
end,
|
||||
})
|
||||
end,
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
return {
|
||||
"folke/snacks.nvim",
|
||||
opts = {
|
||||
picker = {
|
||||
sources = {
|
||||
explorer = {
|
||||
hidden = true,
|
||||
ignored = true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -11,7 +11,7 @@ set $down j
|
||||
set $up k
|
||||
set $right l
|
||||
# Your preferred terminal emulator
|
||||
set $term ghostty
|
||||
set $term alacritty
|
||||
# Your preferred application launcher
|
||||
# Note: pass the final command to swaymsg so that the resulting window can be opened
|
||||
# on the original workspace that the command was run on.
|
||||
@@ -93,7 +93,7 @@ input "2362:628:PIXA3854:00_093A:0274_Touchpad" {
|
||||
bindsym $mod+Equal exec /home/tgrosinger/.config/rofi/scripts/qalc.sh
|
||||
|
||||
# Lazygit
|
||||
bindsym $mod+g exec $term --title Floating-Lazygit --command /home/linuxbrew/.linuxbrew/bin/lazygit; grab_focus; floating enable
|
||||
bindsym $mod+g exec alacritty --title Floating-Lazygit --command /home/linuxbrew/.linuxbrew/bin/lazygit; grab_focus; floating enable
|
||||
for_window [title="Floating-Lazygit"] floating enable
|
||||
for_window [title="Floating-Lazygit"] resize set 1800 1200
|
||||
|
||||
@@ -243,11 +243,6 @@ bindsym $mod+Shift+s exec grim -c -g "$(slurp)" - | satty --early-exit --output-
|
||||
# Possible fix for using web clipper.
|
||||
for_window [class="obsidian"] focus_on_window_activation focus
|
||||
|
||||
# Handy transcribing
|
||||
|
||||
bindsym $mod+o exec pkill -USR2 -x handy
|
||||
no_focus [class="Handy"]
|
||||
|
||||
#
|
||||
# Theme
|
||||
#
|
||||
|
||||
+3
-13
@@ -1,6 +1,6 @@
|
||||
[user]
|
||||
name = Tony Grosinger
|
||||
email = tony@grosinger.net
|
||||
name = Tony Grosinger
|
||||
email = tony@grosinger.net
|
||||
[color]
|
||||
ui = auto
|
||||
|
||||
@@ -57,12 +57,7 @@
|
||||
mnemonicPrefix = true
|
||||
renames = true
|
||||
|
||||
[include]
|
||||
path = /home/tgrosinger/.config/delta/themes/catppuccin.gitconfig
|
||||
|
||||
[delta]
|
||||
# Does not behave well for comments
|
||||
#features = catppuccin-latte
|
||||
navigate = true
|
||||
light = true
|
||||
side-by-side = true
|
||||
@@ -97,9 +92,4 @@
|
||||
conflictstyle = zdiff3
|
||||
|
||||
[init]
|
||||
defaultBranch = main
|
||||
[filter "lfs"]
|
||||
clean = git-lfs clean -- %f
|
||||
smudge = git-lfs smudge -- %f
|
||||
process = git-lfs filter-process
|
||||
required = true
|
||||
defaultBranch = main
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"bashAllowPatterns": []
|
||||
}
|
||||
@@ -1,393 +0,0 @@
|
||||
/**
|
||||
* Approval Gate Extension
|
||||
*
|
||||
* Prompts before mutating/dangerous tool calls execute.
|
||||
*
|
||||
* For `edit` calls, renders a rich diff preview (with surrounding file
|
||||
* context) by reusing pi's built-in edit tool renderer inside a custom
|
||||
* approval modal. For `bash`/`write`, falls back to a text confirm dialog.
|
||||
*
|
||||
* Config: ~/.pi/agent/approval-gate.json
|
||||
* {
|
||||
* "bashAllowPatterns": ["^pwd$"]
|
||||
* }
|
||||
*/
|
||||
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
||||
import {
|
||||
createEditToolDefinition,
|
||||
getAgentDir,
|
||||
type EditToolInput,
|
||||
} from "@earendil-works/pi-coding-agent";
|
||||
import {
|
||||
Box,
|
||||
type Component,
|
||||
Key,
|
||||
matchesKey,
|
||||
Spacer,
|
||||
Text,
|
||||
} from "@earendil-works/pi-tui";
|
||||
import { DynamicBorder } from "@earendil-works/pi-coding-agent";
|
||||
|
||||
interface ApprovalGateConfig {
|
||||
bashAllowPatterns?: unknown;
|
||||
}
|
||||
|
||||
interface LoadedConfig {
|
||||
bashAllowPatterns: RegExp[];
|
||||
warnings: string[];
|
||||
}
|
||||
|
||||
const BLOCKED_REASON = "Blocked by approval-gate";
|
||||
const STATUS_KEY = "approval-gate";
|
||||
const CONFIG_FILE = "approval-gate.json";
|
||||
|
||||
function truncate(value: string, maxLength = 1200): string {
|
||||
if (value.length <= maxLength) return value;
|
||||
return `${value.slice(0, maxLength)}\n… truncated ${value.length - maxLength} character(s)`;
|
||||
}
|
||||
|
||||
function formatBytes(value: string): string {
|
||||
const bytes = Buffer.byteLength(value, "utf8");
|
||||
if (bytes < 1024) return `${bytes} B`;
|
||||
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
||||
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
||||
}
|
||||
|
||||
function loadConfig(): LoadedConfig {
|
||||
const configPath = join(getAgentDir(), CONFIG_FILE);
|
||||
const warnings: string[] = [];
|
||||
|
||||
if (!existsSync(configPath)) {
|
||||
return { bashAllowPatterns: [], warnings };
|
||||
}
|
||||
|
||||
let parsed: ApprovalGateConfig;
|
||||
try {
|
||||
parsed = JSON.parse(readFileSync(configPath, "utf8")) as ApprovalGateConfig;
|
||||
} catch (error) {
|
||||
warnings.push(`Failed to parse ${configPath}: ${error instanceof Error ? error.message : String(error)}`);
|
||||
return { bashAllowPatterns: [], warnings };
|
||||
}
|
||||
|
||||
if (parsed.bashAllowPatterns === undefined) {
|
||||
return { bashAllowPatterns: [], warnings };
|
||||
}
|
||||
|
||||
if (!Array.isArray(parsed.bashAllowPatterns)) {
|
||||
warnings.push(`${configPath}: bashAllowPatterns must be an array of regex pattern strings`);
|
||||
return { bashAllowPatterns: [], warnings };
|
||||
}
|
||||
|
||||
const bashAllowPatterns: RegExp[] = [];
|
||||
for (const pattern of parsed.bashAllowPatterns) {
|
||||
if (typeof pattern !== "string") {
|
||||
warnings.push(`${configPath}: skipped non-string bash allow pattern`);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
bashAllowPatterns.push(new RegExp(pattern));
|
||||
} catch (error) {
|
||||
warnings.push(
|
||||
`${configPath}: skipped invalid regex ${JSON.stringify(pattern)}: ${
|
||||
error instanceof Error ? error.message : String(error)
|
||||
}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return { bashAllowPatterns, warnings };
|
||||
}
|
||||
|
||||
function updateStatus(ctx: ExtensionContext, enabled: boolean): void {
|
||||
if (!ctx.hasUI) return;
|
||||
ctx.ui.setStatus(STATUS_KEY, `approvals: ${enabled ? "✓" : "✗"}`);
|
||||
}
|
||||
|
||||
function isAllowedBashCommand(command: string, patterns: RegExp[]): boolean {
|
||||
const trimmed = command.trim();
|
||||
return patterns.some((pattern) => pattern.test(trimmed));
|
||||
}
|
||||
|
||||
function summarizeToolCall(toolName: string, input: Record<string, unknown>): string {
|
||||
if (toolName === "bash") {
|
||||
const command = typeof input.command === "string" ? input.command : JSON.stringify(input, null, 2);
|
||||
return `Command:\n\n${truncate(command)}`;
|
||||
}
|
||||
|
||||
if (toolName === "edit") {
|
||||
const path = typeof input.path === "string" ? input.path : "unknown";
|
||||
const edits = Array.isArray(input.edits) ? input.edits : [];
|
||||
const editSummaries = edits.slice(0, 3).map((edit, index) => {
|
||||
if (!edit || typeof edit !== "object") return `Edit ${index + 1}: <unrecognized edit block>`;
|
||||
const block = edit as { oldText?: unknown; newText?: unknown };
|
||||
const oldText = typeof block.oldText === "string" ? block.oldText : "<missing oldText>";
|
||||
const newText = typeof block.newText === "string" ? block.newText : "<missing newText>";
|
||||
return [
|
||||
`Edit ${index + 1}:`,
|
||||
` old (${formatBytes(oldText)}): ${truncate(oldText, 500)}`,
|
||||
` new (${formatBytes(newText)}): ${truncate(newText, 500)}`,
|
||||
].join("\n");
|
||||
});
|
||||
|
||||
const omitted = edits.length > editSummaries.length ? `\n\n… omitted ${edits.length - editSummaries.length} edit block(s)` : "";
|
||||
return [`Path: ${path}`, `Edit blocks: ${edits.length}`, "", ...editSummaries].join("\n") + omitted;
|
||||
}
|
||||
|
||||
if (toolName === "write") {
|
||||
const path = typeof input.path === "string" ? input.path : "unknown";
|
||||
const content = typeof input.content === "string" ? input.content : undefined;
|
||||
if (content === undefined) {
|
||||
return `Path: ${path}\nContent size: unknown`;
|
||||
}
|
||||
|
||||
return `Path: ${path}\nContent size: ${formatBytes(content)}\n\nContent preview:\n${truncate(content)}`;
|
||||
}
|
||||
|
||||
return truncate(JSON.stringify(input, null, 2));
|
||||
}
|
||||
|
||||
interface EditRenderState {
|
||||
callComponent?: Box & {
|
||||
preview?: unknown;
|
||||
previewArgsKey?: string;
|
||||
previewPending?: boolean;
|
||||
settledError?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
interface EditRenderContext {
|
||||
args: EditToolInput;
|
||||
toolCallId: string;
|
||||
invalidate: () => void;
|
||||
lastComponent: Component | undefined;
|
||||
state: EditRenderState;
|
||||
cwd: string;
|
||||
executionStarted: boolean;
|
||||
argsComplete: boolean;
|
||||
isPartial: boolean;
|
||||
expanded: boolean;
|
||||
showImages: boolean;
|
||||
isError: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom approval component for `edit` tool calls.
|
||||
*
|
||||
* Reuses pi's built-in edit tool `renderCall` to render the same contextual
|
||||
* diff preview the user would see in the normal tool-execution row, including
|
||||
* surrounding file context and colored intra-line changes. The preview is
|
||||
* computed asynchronously (reads the target file), so we re-render on
|
||||
* invalidate until it settles.
|
||||
*/
|
||||
class EditApprovalComponent implements Component {
|
||||
private readonly tui: { requestRender: () => void };
|
||||
private readonly theme: { fg: (color: string, text: string) => string; bold: (text: string) => string };
|
||||
private readonly editDef: ReturnType<typeof createEditToolDefinition>;
|
||||
private readonly input: EditToolInput;
|
||||
private readonly cwd: string;
|
||||
private readonly toolCallId: string;
|
||||
private readonly state: EditRenderState = {};
|
||||
private lastComponent: Component | undefined;
|
||||
private readonly header: Text;
|
||||
private readonly footer: Text;
|
||||
private readonly topBorder: DynamicBorder;
|
||||
private readonly bottomBorder: DynamicBorder;
|
||||
private settled = false;
|
||||
|
||||
public onApprove?: () => void;
|
||||
public onReject?: () => void;
|
||||
|
||||
constructor(opts: {
|
||||
tui: { requestRender: () => void };
|
||||
theme: { fg: (color: string, text: string) => string; bold: (text: string) => string };
|
||||
cwd: string;
|
||||
input: EditToolInput;
|
||||
toolCallId: string;
|
||||
}) {
|
||||
this.tui = opts.tui;
|
||||
this.theme = opts.theme;
|
||||
this.cwd = opts.cwd;
|
||||
this.input = opts.input;
|
||||
this.toolCallId = opts.toolCallId;
|
||||
this.editDef = createEditToolDefinition(opts.cwd);
|
||||
|
||||
const editCount = Array.isArray(this.input.edits) ? this.input.edits.length : 0;
|
||||
const headerText = `${this.theme.fg("toolTitle", this.theme.bold("edit"))} ${this.theme.fg("accent", this.input.path)}${this.theme.fg("dim", ` · ${editCount} block(s) · approve?`)}`;
|
||||
this.header = new Text(headerText, 1, 0);
|
||||
this.footer = new Text(this.theme.fg("dim", "y / enter approve · n / esc reject"), 1, 0);
|
||||
const borderFn = (s: string) => this.theme.fg("accent", s);
|
||||
this.topBorder = new DynamicBorder(borderFn);
|
||||
this.bottomBorder = new DynamicBorder(borderFn);
|
||||
}
|
||||
|
||||
private buildContext(): EditRenderContext {
|
||||
return {
|
||||
args: this.input,
|
||||
toolCallId: this.toolCallId,
|
||||
invalidate: () => this.tui.requestRender(),
|
||||
lastComponent: this.lastComponent,
|
||||
state: this.state,
|
||||
cwd: this.cwd,
|
||||
executionStarted: false,
|
||||
argsComplete: true,
|
||||
isPartial: false,
|
||||
expanded: true,
|
||||
showImages: false,
|
||||
isError: false,
|
||||
};
|
||||
}
|
||||
|
||||
private renderEditPreview(width: number): string[] {
|
||||
const renderCall = this.editDef.renderCall;
|
||||
if (!renderCall) return [this.theme.fg("warning", "(diff preview unavailable)")];
|
||||
const component = renderCall(this.input, this.theme as never, this.buildContext() as never);
|
||||
this.lastComponent = component;
|
||||
return component.render(width);
|
||||
}
|
||||
|
||||
render(width: number): string[] {
|
||||
const lines: string[] = [];
|
||||
lines.push(...this.topBorder.render(width));
|
||||
lines.push(...this.header.render(width));
|
||||
lines.push(...this.renderEditPreview(width));
|
||||
lines.push(...new Spacer(1).render(width));
|
||||
lines.push(...this.footer.render(width));
|
||||
lines.push(...this.bottomBorder.render(width));
|
||||
return lines;
|
||||
}
|
||||
|
||||
handleInput(data: string): void {
|
||||
if (this.settled) return;
|
||||
if (matchesKey(data, Key.enter) || data === "y" || data === "Y") {
|
||||
this.settled = true;
|
||||
this.onApprove?.();
|
||||
} else if (matchesKey(data, Key.escape) || data === "n" || data === "N") {
|
||||
this.settled = true;
|
||||
this.onReject?.();
|
||||
}
|
||||
}
|
||||
|
||||
invalidate(): void {
|
||||
this.header.invalidate();
|
||||
this.footer.invalidate();
|
||||
this.topBorder.invalidate();
|
||||
this.bottomBorder.invalidate();
|
||||
this.lastComponent?.invalidate?.();
|
||||
}
|
||||
}
|
||||
|
||||
function isEditInput(value: Record<string, unknown>): value is EditToolInput {
|
||||
return (
|
||||
typeof value.path === "string" &&
|
||||
Array.isArray(value.edits) &&
|
||||
value.edits.length > 0 &&
|
||||
value.edits.every(
|
||||
(e) => e && typeof e === "object" && typeof (e as { oldText?: unknown }).oldText === "string" && typeof (e as { newText?: unknown }).newText === "string",
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
async function approveEditCall(input: Record<string, unknown>, ctx: ExtensionContext, toolCallId: string): Promise<boolean> {
|
||||
if (ctx.mode !== "tui" || !isEditInput(input)) {
|
||||
// Non-TUI or malformed input: fall back to text summary confirm.
|
||||
const message = `${summarizeToolCall("edit", input)}\n\nAllow this tool call?`;
|
||||
return ctx.ui.confirm("Approve edit?", message);
|
||||
}
|
||||
|
||||
return ctx.ui.custom<boolean>((tui, theme, _keybindings, done) => {
|
||||
const component = new EditApprovalComponent({
|
||||
tui: tui as { requestRender: () => void },
|
||||
theme: theme as { fg: (color: string, text: string) => string; bold: (text: string) => string },
|
||||
cwd: ctx.cwd,
|
||||
input,
|
||||
toolCallId,
|
||||
});
|
||||
component.onApprove = () => done(true);
|
||||
component.onReject = () => done(false);
|
||||
return component;
|
||||
});
|
||||
}
|
||||
|
||||
export default function approvalGateExtension(pi: ExtensionAPI) {
|
||||
let enabled = true;
|
||||
const config = loadConfig();
|
||||
|
||||
pi.registerCommand("approval-gate", {
|
||||
description: "Turn mutating/dangerous tool approval prompts on or off",
|
||||
handler: async (args, ctx) => {
|
||||
const action = args.trim().toLowerCase();
|
||||
|
||||
if (action === "on") {
|
||||
enabled = true;
|
||||
updateStatus(ctx, enabled);
|
||||
ctx.ui.notify("approval-gate enabled", "info");
|
||||
return;
|
||||
}
|
||||
|
||||
if (action === "off") {
|
||||
enabled = false;
|
||||
updateStatus(ctx, enabled);
|
||||
ctx.ui.notify("approval-gate disabled", "info");
|
||||
return;
|
||||
}
|
||||
|
||||
if (action === "" || action === "status") {
|
||||
updateStatus(ctx, enabled);
|
||||
ctx.ui.notify(`approval-gate is ${enabled ? "enabled" : "disabled"}`, "info");
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.ui.notify("Usage: /approval-gate [on|off|status]", "warning");
|
||||
},
|
||||
});
|
||||
|
||||
pi.on("session_start", async (_event, ctx) => {
|
||||
updateStatus(ctx, enabled);
|
||||
|
||||
for (const warning of config.warnings) {
|
||||
ctx.ui.notify(`approval-gate: ${warning}`, "warning");
|
||||
}
|
||||
});
|
||||
|
||||
pi.on("tool_call", async (event, ctx) => {
|
||||
if (!enabled) return undefined;
|
||||
|
||||
const input = event.input as Record<string, unknown>;
|
||||
const isBash = event.toolName === "bash";
|
||||
const isEdit = event.toolName === "edit";
|
||||
const isWrite = event.toolName === "write";
|
||||
|
||||
if (!isBash && !isEdit && !isWrite) return undefined;
|
||||
|
||||
if (isBash) {
|
||||
const command = typeof input.command === "string" ? input.command : "";
|
||||
if (command && isAllowedBashCommand(command, config.bashAllowPatterns)) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ctx.hasUI) {
|
||||
return { block: true, reason: `${BLOCKED_REASON} (no UI available for confirmation)` };
|
||||
}
|
||||
|
||||
let approved: boolean;
|
||||
if (isEdit) {
|
||||
approved = await approveEditCall(input, ctx, event.toolCallId);
|
||||
} else {
|
||||
approved = await ctx.ui.confirm(
|
||||
`Approve ${event.toolName}?`,
|
||||
`${summarizeToolCall(event.toolName, input)}\n\nAllow this tool call?`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!approved) {
|
||||
return { block: true, reason: BLOCKED_REASON };
|
||||
}
|
||||
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"lastChangelogVersion": "0.79.8",
|
||||
"theme": "catppuccin-latte",
|
||||
"defaultProvider": "openai-codex",
|
||||
"defaultModel": "gpt-5.5",
|
||||
"defaultThinkingLevel": "high",
|
||||
"skills": [
|
||||
"~/.claude/skills"
|
||||
],
|
||||
"hideThinkingBlock": true,
|
||||
"enableInstallTelemetry": false
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
|
||||
"name": "catppuccin-tui-latte",
|
||||
"vars": {
|
||||
"rosewater": "#dc8a78",
|
||||
"flamingo": "#dd7878",
|
||||
"pink": "#ea76cb",
|
||||
"mauve": "#8839ef",
|
||||
"red": "#d20f39",
|
||||
"maroon": "#e64553",
|
||||
"peach": "#fe640b",
|
||||
"yellow": "#df8e1d",
|
||||
"green": "#40a02b",
|
||||
"teal": "#179299",
|
||||
"sky": "#04a5e5",
|
||||
"sapphire": "#209fb5",
|
||||
"blue": "#1e66f5",
|
||||
"lavender": "#7287fd",
|
||||
"text": "#4c4f69",
|
||||
"subtext1": "#5c5f77",
|
||||
"subtext0": "#6c6f85",
|
||||
"overlay2": "#7c7f93",
|
||||
"overlay1": "#8c8fa1",
|
||||
"overlay0": "#9ca0b0",
|
||||
"surface2": "#acb0be",
|
||||
"surface1": "#bcc0cc",
|
||||
"surface0": "#ccd0da",
|
||||
"base": "#eff1f5",
|
||||
"mantle": "#e6e9ef",
|
||||
"crust": "#dce0e8"
|
||||
},
|
||||
"colors": {
|
||||
"accent": "mauve",
|
||||
"border": "surface2",
|
||||
"borderAccent": "lavender",
|
||||
"borderMuted": "surface1",
|
||||
"success": "green",
|
||||
"error": "red",
|
||||
"warning": "yellow",
|
||||
"muted": "subtext0",
|
||||
"dim": "overlay1",
|
||||
"text": "text",
|
||||
"thinkingText": "subtext1",
|
||||
"selectedBg": "surface0",
|
||||
"userMessageBg": "surface0",
|
||||
"userMessageText": "text",
|
||||
"customMessageBg": "mantle",
|
||||
"customMessageText": "text",
|
||||
"customMessageLabel": "mauve",
|
||||
"toolPendingBg": "mantle",
|
||||
"toolSuccessBg": "surface0",
|
||||
"toolErrorBg": "surface0",
|
||||
"toolTitle": "blue",
|
||||
"toolOutput": "overlay2",
|
||||
"mdHeading": "mauve",
|
||||
"mdLink": "blue",
|
||||
"mdLinkUrl": "sapphire",
|
||||
"mdCode": "peach",
|
||||
"mdCodeBlock": "subtext1",
|
||||
"mdCodeBlockBorder": "surface2",
|
||||
"mdQuote": "subtext0",
|
||||
"mdQuoteBorder": "surface2",
|
||||
"mdHr": "surface2",
|
||||
"mdListBullet": "mauve",
|
||||
"toolDiffAdded": "green",
|
||||
"toolDiffRemoved": "red",
|
||||
"toolDiffContext": "subtext0",
|
||||
"syntaxComment": "overlay2",
|
||||
"syntaxKeyword": "mauve",
|
||||
"syntaxFunction": "blue",
|
||||
"syntaxVariable": "maroon",
|
||||
"syntaxString": "green",
|
||||
"syntaxNumber": "peach",
|
||||
"syntaxType": "yellow",
|
||||
"syntaxOperator": "sky",
|
||||
"syntaxPunctuation": "overlay2",
|
||||
"thinkingOff": "surface1",
|
||||
"thinkingMinimal": "surface2",
|
||||
"thinkingLow": "blue",
|
||||
"thinkingMedium": "sapphire",
|
||||
"thinkingHigh": "mauve",
|
||||
"thinkingXhigh": "red",
|
||||
"bashMode": "peach"
|
||||
},
|
||||
"export": {
|
||||
"pageBg": "#eff1f5",
|
||||
"cardBg": "#e6e9ef",
|
||||
"infoBg": "#ccd0da"
|
||||
}
|
||||
}
|
||||
+2
-24
@@ -6,7 +6,7 @@ set-option -g history-limit 10000
|
||||
set -s escape-time 0
|
||||
|
||||
### Fix supporting italics
|
||||
set -g default-terminal "tmux-256color"
|
||||
set -g default-terminal "tmux"
|
||||
|
||||
### Use the system clipboard
|
||||
set -g set-clipboard on
|
||||
@@ -14,16 +14,11 @@ set -g set-clipboard on
|
||||
### Reload the config with r
|
||||
bind-key r source-file ~/.tmux.conf\; display-message "Reloaded config"
|
||||
|
||||
### Kill the current session
|
||||
bind-key X confirm-before -p "Kill session #S? (y/n)" kill-session
|
||||
|
||||
### Mouse mode on by default; toggle with prefix + m
|
||||
set -g mouse on
|
||||
### Toggle Mouse Mode
|
||||
bind-key m set-window-option mouse\; display-message "mouse support is now #{?mouse,on,off}"
|
||||
|
||||
### Open LazyGit in a popup
|
||||
bind-key g display-popup -E -d '#{pane_current_path}' -w 90% -h 90% lazygit
|
||||
bind-key G new-window -c '#{pane_current_path}' -n "gh-dash" gh dash
|
||||
|
||||
# New panes and windows have same cwd as the one opening it
|
||||
bind c new-window -c "#{pane_current_path}"
|
||||
@@ -44,15 +39,6 @@ bind-key -T copy-mode-vi 'C-j' select-pane -D
|
||||
bind-key -T copy-mode-vi 'C-k' select-pane -U
|
||||
bind-key -T copy-mode-vi 'C-l' select-pane -R
|
||||
|
||||
# Improved support for Claude Code CLI.
|
||||
set -g allow-passthrough on
|
||||
set -as terminal-features 'xterm-ghostty*:RGB'
|
||||
|
||||
# Extended key reporting (requires tmux 3.5+).
|
||||
# Apply with: tmux kill-server && tmux
|
||||
set -g extended-keys on
|
||||
set -g extended-keys-format csi-u
|
||||
|
||||
# change windows
|
||||
bind -n S-Right next-window
|
||||
bind -n S-Left previous-window
|
||||
@@ -87,14 +73,6 @@ set -g @catppuccin_window_status_enable "no"
|
||||
set -g @plugin 'tmux-plugins/tpm'
|
||||
set -g @plugin 'tmux-plugins/tmux-sensible'
|
||||
set -g @plugin 'catppuccin/tmux'
|
||||
set -g @plugin 'tmux-plugins/tmux-resurrect'
|
||||
set -g @plugin 'tmux-plugins/tmux-continuum'
|
||||
|
||||
# Session persistence (resurrect + continuum)
|
||||
set -g @continuum-restore 'on'
|
||||
set -g @continuum-save-interval '10'
|
||||
set -g @resurrect-capture-pane-contents 'on'
|
||||
set -g @resurrect-strategy-nvim 'session'
|
||||
|
||||
# Initialize TMUX plugin manager (keep this line at the very bottom of tmux.conf)
|
||||
run '~/.tmux/plugins/tpm/tpm'
|
||||
|
||||
+26
-30
@@ -5,13 +5,7 @@ echo "Installing homebrew"
|
||||
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
||||
|
||||
# NOTE: Do not brew install anything with a dependency on node.
|
||||
# Instead, use mise.
|
||||
#
|
||||
# mise use --global npm:<package-name>
|
||||
#
|
||||
|
||||
# NOTE: claude was installed using the install script:
|
||||
# curl -fsSL https://claude.ai/install.sh | bash
|
||||
# Instead, install with npm -g.
|
||||
|
||||
brew install \
|
||||
anomalyco/tap/opencode \
|
||||
@@ -30,13 +24,14 @@ brew install \
|
||||
hmans/beans/beans \
|
||||
jq \
|
||||
lazygit \
|
||||
mise \
|
||||
neovim \
|
||||
restic \
|
||||
ripgrep \
|
||||
stow \
|
||||
tmux
|
||||
tmux \
|
||||
worktrunk
|
||||
#crane \
|
||||
#deno \
|
||||
#go \
|
||||
#gron \
|
||||
#hadolint \
|
||||
@@ -45,15 +40,20 @@ brew install \
|
||||
#stripe-cli \
|
||||
#visidata \
|
||||
|
||||
# OpenAI Codex
|
||||
brew install --cask codex
|
||||
|
||||
brew services start atuin
|
||||
|
||||
# Previously these were installed with npm because I didn't want to use brew to
|
||||
# install packages with a node dependency. I'm not sure if that is still required.
|
||||
#npm install -g @devcontainers/cli
|
||||
#npm install -g mjml
|
||||
#npm install -g jsonlint
|
||||
|
||||
# Install ble.sh
|
||||
# https://github.com/akinomyoga/ble.sh
|
||||
|
||||
# Install system packages
|
||||
sudo dnf install \
|
||||
alacritty \
|
||||
bubblewrap \
|
||||
copyq \
|
||||
direnv \
|
||||
distrobox \
|
||||
@@ -63,33 +63,29 @@ sudo dnf install \
|
||||
podman \
|
||||
qalculate qalculate-gtk \ # Homebrew version installs x11 and waylany in brew.
|
||||
slurp \
|
||||
socat \
|
||||
wf-recorder
|
||||
|
||||
sudo dnf copr enable scottames/ghostty
|
||||
sudo dnf install ghostty
|
||||
|
||||
# Install global packages managed by mise.
|
||||
mise install
|
||||
|
||||
# Install tailscale
|
||||
# https://tailscale.com/kb/1511/install-fedora-2
|
||||
|
||||
# Install satty
|
||||
# https://github.com/Satty-org/Satty
|
||||
|
||||
# Install flatpaks
|
||||
flatpak install flathub com.github.jeromerobert.pdfarranger
|
||||
flatpak install flathub fr.handbrake.ghb
|
||||
flatpak install io.dbeaver.DBeaverCommunity
|
||||
flatpak install flathub org.gimp.GIMP
|
||||
flatpak install flathub org.gnucash.GnuCash
|
||||
flatpak install flathub org.kde.digikam
|
||||
flatpak install flathub org.libreoffice.LibreOffice
|
||||
flatpak install flathub org.kde.okular
|
||||
flatpak install flathub org.inkscape.Inkscape
|
||||
flatpak install flathub org.kde.kdenlive
|
||||
flatpak install flathub org.kde.skanpage
|
||||
flatpak install flathub org.gimp.GIMP
|
||||
flatpak install flathub org.gimp.GIMP.Plugin.GMic
|
||||
flatpak install flathub org.gnucash.GnuCash
|
||||
flatpak install flathub org.inkscape.Inkscape
|
||||
flatpak install flathub org.jellyfin.JellyfinDesktop
|
||||
flatpak install flathub org.kde.digikam
|
||||
flatpak install flathub org.kde.kdenlive
|
||||
flatpak install flathub org.kde.okular
|
||||
flatpak install flathub org.kde.skanpage
|
||||
flatpak install flathub org.libreoffice.LibreOffice
|
||||
flatpak install flathub org.musicbrainz.Picard
|
||||
flatpak install flathub fr.handbrake.ghb
|
||||
flatpak install flathub com.github.jeromerobert.pdfarranger
|
||||
|
||||
# Install devbox
|
||||
# https://www.jetify.com/docs/devbox/installing-devbox
|
||||
|
||||
Reference in New Issue
Block a user