Expert guidance for developing extensions for Bike Outliner, including API usage, debugging, and architecture patterns
Expert guidance for developing extensions for Bike Outliner using the Bike Extension Kit.
This skill helps you build extensions for Bike Outliner, a powerful outlining application. Extensions enhance Bike's functionality through three contexts: App (commands, keybindings), DOM (React UI), and Style (visual enhancements).
Extensions are located in `src/` with each `.bkext` folder containing:
1. **Build extensions for production**
```bash
npm run build
```
2. **Build and watch for changes during development**
```bash
npm run watch
```
3. **Build internal API components**
```bash
npm run build-internals
```
4. **Create a new extension from template**
```bash
npm run new
```
Access to `bike` global object with capabilities:
**API documentation**: `api/app/*.d.ts`
React components with access to:
**API documentation**: `api/app/*.d.ts`
Custom styling and visual modifications.
**API documentation**: `api/style/*.d.ts`
Extension code runs inside Bike.app's JSContexts. Use the AppleScript `evaluate` command to test APIs:
```bash
osascript -l JavaScript -e '
Application("Bike").evaluate({ script: "bike.version" })
'
osascript -l JavaScript -e '
Application("Bike").evaluate({
input: "Hello from Claude!",
script: "(input) => { return bike.version + \" says: \" + input }"
})
'
osascript -l JavaScript -e '
Application("Bike").evaluate({
input: "{\"value\":42}",
script: "(input) => { const obj = JSON.parse(input); return JSON.stringify({ result: obj.value * 2 }) }"
})
'
```
1. **Use `var` for persistence** - `const`/`let` do NOT persist between calls
```javascript
var handle = ... // Persists
const handle = ... // Does NOT persist
```
2. **Promises resolve between calls** - Store results in `var`
```javascript
var p = bike.frontmostWindow.presentSheet(...);
p.then(function(h) { myHandle = h; });
```
3. **DOM scripts use `extensionExports` pattern**
```javascript
var domCode = "var extensionExports = { activate: async function(context) { context.element.textContent = 'Hello'; } };";
bike.frontmostWindow.presentSheet(domCode, { width: 400, height: 300 })
```
4. **Require classes** - Use `require()` for Outline, URL, etc.
```javascript
const { Outline } = require("bike/app");
```
```javascript
bike.frontmostOutlineEditor // Current editor
editor.outline // The outline
editor.selection // Current selection
outline.root // Root row
outline.root.descendants // All rows
```
**IMPORTANT**: Bike uses similar model to XPath but DIFFERENT syntax. Do NOT use XPath syntax.
```javascript
// Get all rows
outline.query('//*')
// By type
outline.query('//task')
outline.query('//heading')
// By attributes
outline.query('//@done')
outline.query('//@priority=high')
// Combined (space optional but preferred)
outline.query('//task @done')
outline.query('//task @done and @priority=high')
// Text matching (case-insensitive by default)
outline.query('//@text contains "hello"')
outline.query('//@text contains[s] "hello"') // case-sensitive
outline.query('//@text beginswith "Task"')
// Slicing (1-based indexing)
outline.query('//task[1]') // First task
outline.query('//task[-1]') // Last task
// Set operations
outline.query('//task union //heading')
outline.query('//task except //@done')
```
**Query results** return `{type, value}` - access rows via `.value`:
```javascript
var result = outline.query('//task')
var tasks = result.value // Array of Row objects
```
**Debug queries** with `explainQuery()`:
```javascript
console.log(outline.explainQuery('//task @done'))
```
```javascript
row.text.string // Plain text (NOT bodyText!)
row.text // AttributedString with formatting
row.type // 'body', 'heading', 'task', etc.
row.children // Direct children
row.descendants // All descendants
row.parent // Parent row
row.getAttribute(name)
row.setAttribute(name, value)
```
```javascript
selection.type // 'caret', 'text', or 'block'
selection.row // Head row
selection.rows // All selected rows
selection.word // Word at cursor
selection.sentence // Sentence at cursor
```
**Use transactions** for all modifications:
```javascript
editor.transaction({}, () => {
outline.insertRows([
{ text: 'Item 1' },
{ text: 'Item 2', type: 'heading' },
{ text: 'Item 3', attributes: { done: 'true' } }
], parent)
outline.moveRows(rows, parent, before)
outline.removeRows(rows)
})
```
```javascript
// Runtime metadata (not saved)
outline.runtimeMetadata.set('tempState', { foo: 'bar' })
outline.runtimeMetadata.get('tempState')
// Persistent metadata (saved to file)
outline.persistentMetadata.set('author', 'Claude')
outline.persistentMetadata.set('version', 1)
outline.persistentMetadata.delete('key')
```
1. **Query syntax is NOT XPath** - Use Bike's native query syntax
2. **Access query results via `.value`** - All queries return `{type, value}`
3. **Use `row.text.string`** - NOT `bodyText`
4. **Wrap modifications in transactions** - Required for outline changes
5. **Use `var` in `evaluate` for persistence** - `const`/`let` don't persist
6. **Require classes in evaluate** - `const { Outline } = require("bike/app")`
7. **Consult API definitions first** - Don't guess property names
8. **Report API mismatches** - If TypeScript definitions don't match runtime behavior
```javascript
bike.commands.addCommands({
'myext:hello': {
title: 'Say Hello',
perform: (context) => {
const editor = context.editor
editor.transaction({}, () => {
editor.outline.insertRows([
{ text: 'Hello from my extension!' }
], editor.outline.root)
})
}
}
})
```
```javascript
// Get all done tasks
const result = editor.outline.query('//task @done')
const doneTasks = result.value
// Export to new outline
const { Outline } = require('bike/app')
const temp = new Outline(result)
const text = temp.archive('plaintext')
bike.clipboard.writeText(text.data)
```
```javascript
// App context
var handle = await bike.frontmostWindow.presentSheet(domScript, { width: 400 })
handle.postMessage({ type: 'init', data: 'Hello DOM' })
handle.onmessage = (msg) => console.log('From DOM:', msg)
// DOM context
context.postMessage({ type: 'response', data: 'Hello App' })
context.onmessage = (msg) => console.log('From App:', msg)
```
Leave a review
No reviews yet. Be the first to review this skill!
# Download SKILL.md from killerskills.ai/api/skills/bike-extension-development/raw