Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions .omp/INSTALL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Installing Superpowers for oh-my-pi (omp)

## Prerequisite

OMP must already be installed. This integration was verified with omp 16.5.2.
This records the tested environment, not a minimum version or compatibility
floor.

## Install from git

Use omp's native plugin installer:

```bash
omp plugin install github:obra/superpowers
```

omp marketplace installation does not load `omp.extensions` modules and
therefore is not the native installation path for this integration. Use the Git
install above instead.

## Local development

Link an absolute path to your checkout:

```bash
omp plugin link /absolute/path/to/superpowers
```

After installing or linking, restart OMP or start a new session.

## Verify

Confirm that omp registered the plugin:

```bash
omp plugin list --json
```

(`omp plugin list` is also available for human-readable output.)

Then start a clean session and send this exact prompt:

> Let's make a react todo list

The Superpowers bootstrap should load, and `brainstorming` should auto-trigger
before any code is written.

If registration or startup fails, inspect omp diagnostics under `~/.omp/logs/`.

## Remove

```bash
omp plugin uninstall superpowers
```
58 changes: 58 additions & 0 deletions .omp/extensions/superpowers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import type { ContextEvent, ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
import { createBootstrapController } from "../../integrations/shared/bootstrap.ts";

const BOOTSTRAP_MARKER = "superpowers:using-superpowers bootstrap for omp";
const LOADED_MESSAGE =
"The using-superpowers skill content is included below and is already loaded for this OMP session. Follow it now. Do not reload using-superpowers.";

const extensionDir = dirname(fileURLToPath(import.meta.url));
const packageRoot = resolve(extensionDir, "../..");
const skillsDir = resolve(packageRoot, "skills");
const bootstrapSkillPath = resolve(skillsDir, "using-superpowers", "SKILL.md");

export default function superpowersOmpExtension(omp: ExtensionAPI) {
const controller = createBootstrapController<
ContextEvent["messages"][number]
>({
harness: "omp",
bootstrapSkillPath,
bootstrapMarker: BOOTSTRAP_MARKER,
loadedMessage: LOADED_MESSAGE,
toolMapping: ompToolMapping(),
reportDiagnostic(diagnostic) {
omp.logger.warn("Superpowers bootstrap unavailable", diagnostic);
},
});

omp.on("session_start", async () => {
controller.arm();
});

omp.on("session_compact", async () => {
controller.arm();
});

omp.on("context", async (event: ContextEvent) =>
controller.inject(event.messages),
);

omp.on("agent_end", async () => {
controller.disarm();
});
}

function ompToolMapping(): string {
return `## OMP tool mapping

Use OMP native skill discovery when a Superpowers instruction says to invoke a skill: read \`skill://<name>/SKILL.md\` when the skill applies; \`/skill:<name>\` is available for explicit human invocation.

OMP's lowercase built-ins are \`read\`, \`write\`, \`edit\`, \`bash\`, \`grep\`, and \`glob\`. Use them for the corresponding file, shell, and search actions.

For Superpowers subagent workflows, use the built-in lowercase \`task\`.

For legacy \`TodoWrite\` task tracking, use the built-in lowercase \`todo\`.

Never invent capitalized \`Skill\`, \`Task\`, or \`TodoWrite\` calls.`;
}
117 changes: 34 additions & 83 deletions .pi/extensions/superpowers.ts
Original file line number Diff line number Diff line change
@@ -1,88 +1,62 @@
import { readFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import type {
ContextEvent,
ExtensionAPI,
} from "@earendil-works/pi-coding-agent";
import { createBootstrapController } from "../../integrations/shared/bootstrap.ts";

const EXTREMELY_IMPORTANT_MARKER = "<EXTREMELY_IMPORTANT>";
const BOOTSTRAP_MARKER = "superpowers:using-superpowers bootstrap for pi";
const LOADED_MESSAGE =
"The using-superpowers skill content is included below and is already loaded for this Pi session. Follow it now. Do not try to load using-superpowers again.";

const extensionDir = dirname(fileURLToPath(import.meta.url));
const packageRoot = resolve(extensionDir, "../..");
const skillsDir = resolve(packageRoot, "skills");
const bootstrapSkillPath = resolve(skillsDir, "using-superpowers", "SKILL.md");

let cachedBootstrap: string | null | undefined;

export default function superpowersPiExtension(pi: ExtensionAPI) {
let injectBootstrap = true;
const logger = (
pi as ExtensionAPI & {
logger?: { warn?: (...args: unknown[]) => void };
}
).logger;
const controller = createBootstrapController<
ContextEvent["messages"][number]
>({
harness: "pi",
bootstrapSkillPath,
bootstrapMarker: BOOTSTRAP_MARKER,
loadedMessage: LOADED_MESSAGE,
toolMapping: piToolMapping(),
reportDiagnostic(diagnostic) {
if (logger?.warn) {
logger.warn("Superpowers bootstrap unavailable", diagnostic);
} else {
console.warn("Superpowers bootstrap unavailable", diagnostic);
}
},
});

pi.on("resources_discover", async () => ({
skillPaths: [skillsDir],
}));

pi.on("session_start", async () => {
injectBootstrap = true;
controller.arm();
});

pi.on("session_compact", async () => {
injectBootstrap = true;
controller.arm();
});

pi.on("agent_end", async () => {
injectBootstrap = false;
});

pi.on("context", async (event) => {
if (!injectBootstrap) return;
if (event.messages.some(messageContainsBootstrap)) return;

const bootstrap = getBootstrapContent();
if (!bootstrap) return;

const bootstrapMessage = {
role: "user" as const,
content: [{ type: "text" as const, text: bootstrap }],
timestamp: Date.now(),
};

const insertAt = firstNonCompactionSummaryIndex(event.messages);
return {
messages: [
...event.messages.slice(0, insertAt),
bootstrapMessage,
...event.messages.slice(insertAt),
],
};
controller.disarm();
});
}

function getBootstrapContent(): string | null {
if (cachedBootstrap !== undefined) return cachedBootstrap;

try {
const skillContent = readFileSync(bootstrapSkillPath, "utf8");
const body = stripFrontmatter(skillContent);
cachedBootstrap = `${EXTREMELY_IMPORTANT_MARKER}
${BOOTSTRAP_MARKER}

You have superpowers.

The using-superpowers skill content is included below and is already loaded for this Pi session. Follow it now. Do not try to load using-superpowers again.

${body}

${piToolMapping()}
</EXTREMELY_IMPORTANT>`;
return cachedBootstrap;
} catch {
cachedBootstrap = null;
return null;
}
}

function stripFrontmatter(content: string): string {
const match = content.match(/^---\n[\s\S]*?\n---\n([\s\S]*)$/);
return (match ? match[1] : content).trim();
pi.on("context", async (event: ContextEvent) =>
controller.inject(event.messages),
);
}

function piToolMapping(): string {
Expand All @@ -96,26 +70,3 @@ Pi does not ship a standard subagent tool. If a subagent tool such as \`subagent

Pi does not ship a standard task-list tool. If an installed todo/task tool is available, use it. Otherwise track work in plan files or a repo-local \`TODO.md\` when task tracking is needed. Treat older \`TodoWrite\` references as this task-tracking action.`;
}

function messageContainsBootstrap(message: unknown): boolean {
const content = (message as { content?: unknown }).content;
if (typeof content === "string") return content.includes(BOOTSTRAP_MARKER);
if (!Array.isArray(content)) return false;
return content.some((part) => {
return (
part &&
typeof part === "object" &&
(part as { type?: unknown }).type === "text" &&
typeof (part as { text?: unknown }).text === "string" &&
(part as { text: string }).text.includes(BOOTSTRAP_MARKER)
);
});
}

function firstNonCompactionSummaryIndex(messages: unknown[]): number {
let index = 0;
while ((messages[index] as { role?: unknown } | undefined)?.role === "compactionSummary") {
index += 1;
}
return index;
}
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ If this sounds like someone you know, definitely send them our way.

## Quickstart

Give your agent Superpowers: [Claude Code](#claude-code), [Antigravity](#antigravity), [Codex App](#codex-app), [Codex CLI](#codex-cli), [Cursor](#cursor), [Factory Droid](#factory-droid), [Gemini CLI](#gemini-cli), [GitHub Copilot CLI](#github-copilot-cli), [Kimi Code](#kimi-code), [OpenCode](#opencode), [Pi](#pi).
Give your agent Superpowers: [Claude Code](#claude-code), [Antigravity](#antigravity), [Codex App](#codex-app), [Codex CLI](#codex-cli), [Cursor](#cursor), [Factory Droid](#factory-droid), [Gemini CLI](#gemini-cli), [GitHub Copilot CLI](#github-copilot-cli), [Kimi Code](#kimi-code), [OpenCode](#opencode), [Pi](#pi), [oh-my-pi](#oh-my-pi-omp).

## How it works

Expand Down Expand Up @@ -199,6 +199,22 @@ pi -e /path/to/superpowers

The Pi package loads the Superpowers skills and a small extension that injects the `using-superpowers` bootstrap at session startup and again after compaction. Pi has native skills, so no compatibility `Skill` tool is required. Subagent and task-list tools remain optional Pi companion packages.

### oh-my-pi (omp)

Install Superpowers from Git through omp's native plugin manager:

```bash
omp plugin install github:obra/superpowers
```

For local development, link an absolute path to this checkout:

```bash
omp plugin link /absolute/path/to/superpowers
```

OMP uses its native manifest plus built-in lowercase `task` subagents and built-in lowercase `todo` tracking. See the [OMP installation guide](.omp/INSTALL.md) for verification, diagnostics, and removal.

## The Basic Workflow

1. **brainstorming** - Activates before writing code. Refines rough ideas through questions, explores alternatives, presents design in sections for validation. Saves design document.
Expand Down
3 changes: 3 additions & 0 deletions docs/porting-to-a-new-harness.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ installer. Factory's Droid, for example, consumes the Claude Code plugin via its
own `plugin install` command and needs no new files here. Before building,
check whether the harness can simply load an existing manifest. A port that adds
nothing to this repo but a paragraph in the README is a perfectly good outcome.
Compatibility manifests may be consumed by child or fork harnesses. For
first-class support, when the child exposes a native manifest or adapter contract
and behavior differs, use and verify that native boundary.

---

Expand Down
Loading