Initial: pi-skill — 68 skills, 43 extensions, 11 themes for Pi

This commit is contained in:
Kunthawat Greethong
2026-05-25 16:38:02 +07:00
commit 69f7d8bdda
1689 changed files with 342427 additions and 0 deletions

View File

@@ -0,0 +1,290 @@
// ABOUTME: OAuth provider extension — uses CLAUDE_CODE_OAUTH_TOKEN env var for Anthropic auth.
// ABOUTME: Supersedes the built-in OAuth flow so no browser login is needed. Set the env var and go.
/**
* OAuth Provider — Environment-Variable-Based Anthropic Authentication
*
* Instead of using pi's built-in OAuth login flow (which requires opening a browser,
* completing PKCE auth, and storing refresh/access tokens in auth.json), this extension
* reads the `CLAUDE_CODE_OAUTH_TOKEN` (or `PI_CLAUDE_OAUTH_TOKEN`) environment variable
* and uses it directly as the API credential.
*
* How it works:
* 1. On load, checks for CLAUDE_CODE_OAUTH_TOKEN or PI_CLAUDE_OAUTH_TOKEN env var
* 2. If found, registers an Anthropic provider override via pi.registerProvider()
* 3. The override's getApiKey() returns the env var token directly
* 4. No browser login, no token refresh, no auth.json management needed
*
* Commands:
* /auth-status — Show which auth method is active and token presence
* /auth-logout — Clear built-in OAuth credentials from auth.json (keeps env var auth)
* /auth-clear — Alias for /auth-logout
*
* Environment Variables:
* CLAUDE_CODE_OAUTH_TOKEN — Primary: Claude Code OAuth token (Claude Max Plan)
* PI_CLAUDE_OAUTH_TOKEN — Alias: Pi-specific OAuth token variable
*
* Setup:
* 1. Get your OAuth token from Claude Code or Anthropic Console
* 2. Add to ~/.zshrc or ~/.bashrc: export CLAUDE_CODE_OAUTH_TOKEN="your-token-here"
* 3. Restart terminal and pi — done. No /login needed.
*
* Usage: Loaded via packages in agent/settings.json
*/
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
import { readFileSync, writeFileSync, existsSync } from "node:fs";
import { join } from "node:path";
// ── Constants ────────────────────────────────────────────────────────
const ENV_PRIMARY = "CLAUDE_CODE_OAUTH_TOKEN";
const ENV_ALIAS = "PI_CLAUDE_OAUTH_TOKEN";
const PROVIDER_NAME = "anthropic";
const FAR_FUTURE_EXPIRY = Date.now() + 365 * 24 * 60 * 60 * 1000; // 1 year from now
// ── Helpers ──────────────────────────────────────────────────────────
/** Bridge our env vars to ANTHROPIC_OAUTH_TOKEN so the pi-ai library picks them up. */
export function bridgeOAuthEnvVar(): void {
if (!process.env.ANTHROPIC_OAUTH_TOKEN) {
const token = process.env[ENV_PRIMARY] || process.env[ENV_ALIAS];
if (token) {
process.env.ANTHROPIC_OAUTH_TOKEN = token;
}
}
}
function getOAuthToken(): string | undefined {
return process.env[ENV_PRIMARY] || process.env[ENV_ALIAS];
}
function getTokenSource(): string | undefined {
if (process.env[ENV_PRIMARY]) return ENV_PRIMARY;
if (process.env[ENV_ALIAS]) return ENV_ALIAS;
return undefined;
}
function getAuthJsonPath(): string {
// auth.json lives in the agent directory (same level as extensions/)
const agentDir = join(import.meta.dirname, "..");
return join(agentDir, "auth.json");
}
function readAuthJson(): Record<string, unknown> | null {
const path = getAuthJsonPath();
if (!existsSync(path)) return null;
try {
return JSON.parse(readFileSync(path, "utf-8"));
} catch {
return null;
}
}
function writeAuthJson(data: Record<string, unknown>): void {
const path = getAuthJsonPath();
writeFileSync(path, JSON.stringify(data, null, 2) + "\n", "utf-8");
}
function maskToken(token: string): string {
if (token.length <= 12) return "***";
return token.slice(0, 8) + "..." + token.slice(-4);
}
// ── Extension Factory ────────────────────────────────────────────────
export default function oauthProvider(pi: ExtensionAPI): void {
// Bridge env vars so the underlying pi-ai library sees ANTHROPIC_OAUTH_TOKEN
bridgeOAuthEnvVar();
const token = getOAuthToken();
const source = getTokenSource();
// ── Register Provider Override ─────────────────────────────────
if (token) {
pi.registerProvider(PROVIDER_NAME, {
oauth: {
name: "Anthropic (OAuth Env Var)",
async login(_callbacks) {
// No browser login needed — return synthetic credentials from env var
const currentToken = getOAuthToken();
if (!currentToken) {
throw new Error(
`OAuth token not found. Set ${ENV_PRIMARY} or ${ENV_ALIAS} in your environment.\n` +
`Add to ~/.zshrc: export ${ENV_PRIMARY}="your-token-here"`
);
}
return {
refresh: "env-var-managed",
access: currentToken,
expires: FAR_FUTURE_EXPIRY,
};
},
async refreshToken(_credentials) {
// Re-read from env var on "refresh" — picks up any changes
const currentToken = getOAuthToken();
if (!currentToken) {
throw new Error(
`OAuth token no longer available in environment. ` +
`Set ${ENV_PRIMARY} or ${ENV_ALIAS} to continue.`
);
}
return {
refresh: "env-var-managed",
access: currentToken,
expires: FAR_FUTURE_EXPIRY,
};
},
getApiKey(_credentials) {
// Always return the live env var value (not the stored credential)
const currentToken = getOAuthToken();
if (!currentToken) {
throw new Error(`OAuth token not found in environment. Set ${ENV_PRIMARY}.`);
}
return currentToken;
},
},
});
}
// ── /auth-status Command ───────────────────────────────────────
pi.registerCommand("auth-status", {
description: "Show current authentication method and status",
async handler(_args, ctx) {
const currentToken = getOAuthToken();
const currentSource = getTokenSource();
const authData = readAuthJson();
const hasAuthJsonEntry = authData && typeof authData[PROVIDER_NAME] === "object";
const lines: string[] = [];
lines.push("═══ Authentication Status ═══");
lines.push("");
if (currentToken) {
lines.push(`✅ Env var auth ACTIVE`);
lines.push(` Source: ${currentSource}`);
lines.push(` Token: ${maskToken(currentToken)}`);
lines.push(` Method: Environment variable (no login required)`);
} else {
lines.push(`⚠️ Env var auth NOT configured`);
lines.push(` Neither ${ENV_PRIMARY} nor ${ENV_ALIAS} is set.`);
lines.push(` Set one in your shell profile to enable env-var auth.`);
}
lines.push("");
if (hasAuthJsonEntry) {
const entry = authData[PROVIDER_NAME] as Record<string, unknown>;
if (entry.type === "oauth") {
const expires = typeof entry.expires === "number" ? entry.expires : 0;
const isExpired = expires < Date.now();
lines.push(`📄 auth.json entry: ${isExpired ? "EXPIRED" : "valid"}`);
if (typeof entry.access === "string") {
lines.push(` Access: ${maskToken(entry.access)}`);
}
lines.push(` Expires: ${new Date(expires).toLocaleString()}`);
if (currentToken) {
lines.push(` Env var takes priority over auth.json.`);
lines.push(` Run /auth-logout to clear auth.json entry.`);
}
} else if (entry.type === "api_key") {
lines.push(`📄 auth.json entry: API key`);
}
} else {
lines.push(`📄 auth.json: No ${PROVIDER_NAME} entry`);
}
lines.push("");
lines.push("─────────────────────────────");
ctx.ui.notify(lines.join("\n"), "info");
},
});
// ── /auth-logout Command ───────────────────────────────────────
pi.registerCommand("auth-logout", {
description: "Clear built-in Anthropic OAuth credentials from auth.json",
async handler(_args, ctx) {
const authData = readAuthJson();
if (!authData || !(PROVIDER_NAME in authData)) {
ctx.ui.notify(
`No ${PROVIDER_NAME} credentials found in auth.json. Nothing to clear.`,
"info"
);
return;
}
const confirmed = await ctx.ui.confirm(
"Clear Anthropic Credentials",
`Remove the "${PROVIDER_NAME}" entry from auth.json?\n` +
`This clears the built-in OAuth credentials.\n` +
`${getOAuthToken() ? "Env var auth will continue to work." : "⚠️ No env var token set — you'll need to set one or /login again."}`
);
if (!confirmed) {
ctx.ui.notify("Cancelled.", "info");
return;
}
// Remove the anthropic entry
const { [PROVIDER_NAME]: _removed, ...rest } = authData;
writeAuthJson(rest);
ctx.ui.notify(
`✅ Cleared "${PROVIDER_NAME}" from auth.json.\n` +
`${getOAuthToken() ? "Env var auth remains active." : "Set " + ENV_PRIMARY + " to continue using Claude."}`,
"info"
);
},
});
// ── /auth-clear Alias ──────────────────────────────────────────
pi.registerCommand("auth-clear", {
description: "Alias for /auth-logout — clear built-in OAuth credentials",
async handler(args, ctx) {
// Delegate to auth-logout
const commands = pi.getCommands();
const logoutCmd = commands.find(c => c.name === "auth-logout");
if (logoutCmd) {
// Can't invoke commands directly, so duplicate the logic
const authData = readAuthJson();
if (!authData || !(PROVIDER_NAME in authData)) {
ctx.ui.notify(
`No ${PROVIDER_NAME} credentials found in auth.json. Nothing to clear.`,
"info"
);
return;
}
const confirmed = await ctx.ui.confirm(
"Clear Anthropic Credentials",
`Remove the "${PROVIDER_NAME}" entry from auth.json?\n` +
`This clears the built-in OAuth credentials.\n` +
`${getOAuthToken() ? "Env var auth will continue to work." : "⚠️ No env var token set — you'll need to set one or /login again."}`
);
if (!confirmed) {
ctx.ui.notify("Cancelled.", "info");
return;
}
const { [PROVIDER_NAME]: _removed, ...rest } = authData;
writeAuthJson(rest);
ctx.ui.notify(
`✅ Cleared "${PROVIDER_NAME}" from auth.json.\n` +
`${getOAuthToken() ? "Env var auth remains active." : "Set " + ENV_PRIMARY + " to continue using Claude."}`,
"info"
);
}
},
});
}