Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
45fc21f
Prepped for OC install
ryaneggz Feb 12, 2026
d285be0
Install scripts
ryaneggz Feb 12, 2026
9656e3d
Update to upfront prompt
ryaneggz Feb 12, 2026
6afb28e
Add unzip
ryaneggz Feb 12, 2026
426e045
Configure git
ryaneggz Feb 12, 2026
7719cb1
prompt user if install openclaw
ryaneggz Feb 12, 2026
133208e
update docs
ryaneggz Feb 12, 2026
48ffeb0
Final Report
ryaneggz Feb 12, 2026
2565e0f
wget install option
ryaneggz Feb 12, 2026
76049bb
Will generate an uninstall script
ryaneggz Feb 12, 2026
f19b34c
Swithc to nvm
ryaneggz Feb 12, 2026
4d11897
Swithc to nodejs
ryaneggz Feb 12, 2026
ee4d9a2
Swithc to nodejs
ryaneggz Feb 12, 2026
acf03b6
update rg package
ryaneggz Feb 13, 2026
9ea390a
Refactor setup.sh to remove user-specific references
ryaneggz Mar 6, 2026
c29ea36
init
ryaneggz Mar 26, 2026
e73e6dc
Replace OpenClaw with Claude Code, rename ubuntu/ to sandbox/
ryaneggz Mar 26, 2026
77b202f
Move Dockerfile to root, sandbox/ contains only copied-in files
ryaneggz Mar 26, 2026
42dd28e
Add run, shell, stop, and clean targets to Makefile
ryaneggz Mar 26, 2026
ffd5da9
Add sandbox user, install user-level tools under sandbox account
ryaneggz Mar 26, 2026
c82a66d
Mount sandbox/ as shared volume at /home/sandbox, add rebuild target
ryaneggz Mar 26, 2026
4fb7c35
Add .bashrc with claude --dangerously-skip-permissions alias
ryaneggz Mar 26, 2026
f41c639
Restructure to install/ and workspace/, system-wide installs, update …
ryaneggz Mar 26, 2026
cde3a4a
Add standalone install instructions with curl/wget from branch
ryaneggz Mar 26, 2026
2739753
Update tag schema to claude-v* format
ryaneggz Mar 26, 2026
0d3c942
Commit plans
ryaneggz Mar 26, 2026
9f6eea3
Add usage examples to README
ryaneggz Mar 26, 2026
462d359
Add multi-agent support, Docker, tmux, named sandboxes, and AgentMail
ryaneggz Mar 27, 2026
d95e3d5
Rebrand tagging and CI to open-harness
ryaneggz Mar 27, 2026
5b19f4d
Fix Docker socket permissions so sandbox user needs no sudo
ryaneggz Mar 27, 2026
8bea5b0
Make Docker opt-in, require NAME, add error handling
ryaneggz Mar 27, 2026
a3969d7
Add agent builder configs, symlink .codex to .claude
ryaneggz Mar 27, 2026
04fd7c8
Add heartbeat, soul, and memory system (OpenClaw-style)
ryaneggz Mar 27, 2026
e013b4e
Add pi example banner extension
ryaneggz Mar 27, 2026
91bd0b2
docs: restyle README with project intentions, benefits, and emoji sec…
ryaneggz Mar 27, 2026
432b6b7
feat: add quickstart — three commands from clone to coding with AI
ryaneggz Mar 27, 2026
d75ccca
chore: add MIT license and LinkedIn launch post
ryaneggz Mar 27, 2026
ac289c5
fix: update license year to 2026
ryaneggz Mar 27, 2026
7a8c8ec
docs: rework LinkedIn post opener
ryaneggz Mar 27, 2026
485e48f
docs: detail unique architecture in LinkedIn post
ryaneggz Mar 27, 2026
94e673f
docs: shift LinkedIn post focus to shared multi-agent workspace
ryaneggz Mar 27, 2026
5a8f151
docs: add X launch post
ryaneggz Mar 27, 2026
5e737a3
docs: trim X post to 268 chars
ryaneggz Mar 27, 2026
5beba0f
chore: update heartbeat interval to 900s for dev testing
ryaneggz Mar 27, 2026
d096ed6
feat(linkedin-ghostwriter): add skill, drafts, and heartbeat task
ryaneggz Mar 28, 2026
c60f3aa
Can post to post-bridge for remaining days in month
ryaneggz Mar 28, 2026
a50d500
content
ryaneggz Mar 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
174 changes: 174 additions & 0 deletions .claude/plans/custom-banner-extension.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
# Custom Banner Extension

## Context

Pi's interactive mode displays a **startup header** (banner) showing shortcuts, loaded AGENTS.md files, prompt templates, skills, and extensions. The `ctx.ui.setHeader()` API allows extensions to fully replace this built-in header with a custom component. This plan produces a pi extension that lets the user customize the initial banner via a configuration file and a `/banner` command.

The extension will:
1. Replace the default pi startup header with a user-defined banner
2. Support ASCII art, text, and color theming via a `banner.json` config file
3. Provide a `/banner` command to toggle between custom and built-in headers
4. Provide a `/banner-edit` command to interactively edit the banner text

## API Surface

Key pi APIs used:
- `ctx.ui.setHeader(factory | undefined)` — replace or restore the built-in startup header
- `pi.on("session_start", ...)` — load banner config and apply on startup
- `pi.registerCommand(name, ...)` — register `/banner` and `/banner-edit` commands
- `ctx.ui.editor(title, prefill)` — multi-line editor for banner text editing
- `ctx.ui.notify(msg, level)` — feedback notifications
- `theme.fg(color, text)` / `theme.bold(text)` — themed styling

Reference: `examples/extensions/custom-header.ts` demonstrates `setHeader()` with a mascot graphic.

## Files to Create

### 1. `.pi/extensions/custom-banner.ts` (~120 lines)

The extension module. Exports a default function receiving `ExtensionAPI`.

**On load (`session_start`):**
- Read `banner.json` from `.pi/banner.json` (project) falling back to `~/.pi/agent/banner.json` (global)
- If config exists, parse it and call `ctx.ui.setHeader()` with a factory that renders the configured banner
- If no config exists, create a default `banner.json` with a simple ASCII banner and apply it

**Banner config shape (`BannerConfig`):**

```typescript
interface BannerConfig {
enabled: boolean; // Master toggle
lines: string[]; // Raw text lines (supports \n in each)
color: string; // Theme color key: "accent", "success", "warning", "error", "muted", "dim"
bold: boolean; // Apply bold styling
subtitle?: string; // Optional subtitle line below the banner
subtitleColor?: string; // Theme color for subtitle (default: "muted")
}
```

**`setHeader` factory implementation:**
- Receives `(tui, theme)`, returns `{ render(width), invalidate() }`
- `render(width)`:
- Map each `config.lines` entry through `theme.fg(config.color, ...)` and optionally `theme.bold(...)`
- If `config.subtitle` is set, append a styled subtitle line
- Truncate each line to `width` using `truncateToWidth` from `@mariozechner/pi-tui`
- Return the styled string array
- `invalidate()`: no-op (stateless rendering)

**Commands:**

| Command | Description |
|---------|-------------|
| `/banner` | Toggle between custom banner and built-in header. Updates `enabled` in config and persists. |
| `/banner-edit` | Opens `ctx.ui.editor()` pre-filled with current `lines` joined by `\n`. On submit, updates config, persists, and re-applies header. |

**Helper functions:**
- `loadConfig(cwd: string): BannerConfig | null` — Read and parse banner.json from project then global location
- `saveConfig(cwd: string, config: BannerConfig): void` — Write banner.json back to the location it was loaded from (or project default)
- `applyBanner(ctx, config, pi)` — Call `ctx.ui.setHeader()` with the config, or `ctx.ui.setHeader(undefined)` if `!config.enabled`

### 2. `.pi/banner.json` (new)

Default project-level banner configuration:

```json
{
"enabled": true,
"lines": [
"┌─────────────────────────────────┐",
"│ 🚀 Sandboxes Project 🚀 │",
"└─────────────────────────────────┘"
],
"color": "accent",
"bold": true,
"subtitle": "Ruska AI Development Environment",
"subtitleColor": "muted"
}
```

## Implementation Details

### `custom-banner.ts` Structure

```
import type { ExtensionAPI, ExtensionContext, Theme } from "@mariozechner/pi-coding-agent";
import { truncateToWidth } from "@mariozechner/pi-tui";
import { readFileSync, writeFileSync, existsSync } from "node:fs";
import { join } from "node:path";

interface BannerConfig { ... }

const PROJECT_CONFIG = ".pi/banner.json";
const GLOBAL_CONFIG_DIR = process.env.PI_CODING_AGENT_DIR || join(process.env.HOME!, ".pi", "agent");
const GLOBAL_CONFIG = join(GLOBAL_CONFIG_DIR, "banner.json");

function loadConfig(cwd: string): { config: BannerConfig; path: string } | null { ... }
function saveConfig(filePath: string, config: BannerConfig): void { ... }
function applyBanner(ctx: ExtensionContext, config: BannerConfig): void { ... }

export default function (pi: ExtensionAPI) {
let currentConfig: BannerConfig | null = null;
let configPath: string | null = null;

pi.on("session_start", async (_event, ctx) => {
if (!ctx.hasUI) return;
const result = loadConfig(ctx.cwd);
if (result) {
currentConfig = result.config;
configPath = result.path;
} else {
// Create default config in project
currentConfig = { ...DEFAULT_CONFIG };
configPath = join(ctx.cwd, PROJECT_CONFIG);
saveConfig(configPath, currentConfig);
}
if (currentConfig.enabled) {
applyBanner(ctx, currentConfig);
}
});

pi.registerCommand("banner", { ... }); // Toggle enabled
pi.registerCommand("banner-edit", { ... }); // Edit lines via ctx.ui.editor
}
```

### `applyBanner` implementation

```typescript
function applyBanner(ctx: ExtensionContext, config: BannerConfig): void {
ctx.ui.setHeader((_tui, theme) => ({
render(width: number): string[] {
const result: string[] = [""];
for (const line of config.lines) {
let styled = theme.fg(config.color as any, line);
if (config.bold) styled = theme.bold(styled);
result.push(truncateToWidth(styled, width));
}
if (config.subtitle) {
const subColor = (config.subtitleColor || "muted") as any;
result.push(theme.fg(subColor, config.subtitle));
}
result.push("");
return result;
},
invalidate() {},
}));
}
```

## Implementation Order

1. Create `.pi/banner.json` with default project banner config
2. Create `.pi/extensions/custom-banner.ts` with full extension logic
3. Test with `pi -e .pi/extensions/custom-banner.ts` to verify header replacement
4. Test `/banner` toggle and `/banner-edit` editing

## Verification

1. Start pi in the project — custom banner replaces the default startup header
2. `/banner` — toggles back to built-in header, notification confirms
3. `/banner` again — restores custom banner
4. `/banner-edit` — opens editor with current lines, edit and submit → banner updates immediately
5. Restart pi — banner persists from `.pi/banner.json`
6. Delete `.pi/banner.json`, restart — extension creates default config automatically
7. `pi -p "hello"` (print mode) — extension skips UI (`ctx.hasUI` check), no errors
38 changes: 38 additions & 0 deletions .claude/plans/memoized-cuddling-moore.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Plan: Update tag schema to `claude-v*`

## Context

The CI workflow currently triggers on `sandbox-*` tags and parses `sandbox-<type>-<version>`. Since this branch is specifically for the Claude Code sandbox, the tag schema should be simplified to `claude-v*` (e.g. `claude-v1.0.0`). This produces images tagged as `ghcr.io/ruska-ai/sandbox:claude-v1.0.0` and `ghcr.io/ruska-ai/sandbox:claude-latest`.

## Changes

### 1. `.github/workflows/build.yml`

- Tag filter: `"sandbox-*"` → `"claude-v*"`
- Simplify parse step: extract version directly from `claude-v<version>` (no more sandbox/type split)
- Image tags: `ghcr.io/ruska-ai/sandbox:claude-v1.0.0` + `ghcr.io/ruska-ai/sandbox:claude-latest`

### 2. `README.md`

- Add a "Releases" or "Tagging" section documenting the tag schema
- Example: `git tag claude-v1.0.0 && git push origin claude-v1.0.0`

### 3. `Makefile`

- Update IMAGE to align: `ghcr.io/ruska-ai/sandbox:claude-$(TAG)`
- `TAG ?= latest` remains default for local builds

---

## Files to modify

- `.github/workflows/build.yml`
- `README.md`
- `Makefile`

## Verification

1. Workflow parses `claude-v1.0.0` tag correctly
2. Image names: `ghcr.io/ruska-ai/sandbox:claude-v1.0.0` and `ghcr.io/ruska-ai/sandbox:claude-latest`
3. `make build` still works locally with default tag
4. README documents the tag format
Loading