getsuperpower
Advanced tools
+1
-1
| { | ||
| "name": "getsuperpower", | ||
| "version": "0.3.4", | ||
| "version": "0.3.5", | ||
| "type": "module", | ||
@@ -5,0 +5,0 @@ "repository": { |
+6
-3
@@ -142,7 +142,8 @@ <img src="/assets/getsupwerpower.jpg" alt="GetSuperpower" width="640" /> | ||
| ## Local Files | ||
| ## Installed Files | ||
| The CLI writes installed workflow records under `.getsuperpower/`: | ||
| By default, the CLI writes installed workflow records under your home directory: | ||
| ```text | ||
| ~/ | ||
| .getsuperpower/ | ||
@@ -152,5 +153,7 @@ workflows/ | ||
| Use `--dir <project>` when you intentionally want a project-local workflow record. | ||
| Pony Trail history, revert, and prehook features are paused while GetSuperpower focuses on bundle skills. | ||
| Keep `.getsuperpower/` out of git unless you intentionally want to share installed workflow records. | ||
| Keep project-local `.getsuperpower/` folders out of git unless you intentionally want to share installed workflow records. | ||
@@ -157,0 +160,0 @@ ## Local Development |
+25
-0
@@ -44,2 +44,27 @@ import pc from "picocolors"; | ||
| export function borderBox(lines: string[]): string { | ||
| const content = lines.flatMap((line) => line.split("\n")); | ||
| const width = Math.max(...content.map((line) => line.length), 1); | ||
| const horizontal = `+${"-".repeat(width + 2)}+`; | ||
| return [horizontal, ...content.map((line) => `| ${line.padEnd(width)} |`), horizontal].join("\n"); | ||
| } | ||
| export function getSuperpowerInstallResultBox(input: { | ||
| workflowName: string; | ||
| workflowVersion: string; | ||
| workflowFile: string; | ||
| skillCount: number; | ||
| }): string { | ||
| return brand( | ||
| borderBox([ | ||
| GETSUPERPOWER_ASCII_LOGO, | ||
| "", | ||
| `GetSuperpower installed: ${input.workflowName}`, | ||
| `Version: ${input.workflowVersion}`, | ||
| `Skills installed: ${input.skillCount}`, | ||
| `GetSuperpower file: ${input.workflowFile}`, | ||
| ]), | ||
| ); | ||
| } | ||
| export function rootHelpBanner(): string { | ||
@@ -46,0 +71,0 @@ return [ |
+1
-1
@@ -24,3 +24,3 @@ #!/usr/bin/env bun | ||
| const CLI_VERSION = "0.3.4"; | ||
| const CLI_VERSION = "0.3.5"; | ||
@@ -27,0 +27,0 @@ interface CommanderVersionInternals { |
+137
-14
@@ -6,4 +6,12 @@ import { existsSync } from "node:fs"; | ||
| import type { Command } from "commander"; | ||
| import { commandText, keyValue, muted, nextStep, success, warning } from "./cli-theme"; | ||
| import { | ||
| commandText, | ||
| getSuperpowerInstallResultBox, | ||
| keyValue, | ||
| muted, | ||
| nextStep, | ||
| success, | ||
| warning, | ||
| } from "./cli-theme"; | ||
| import { | ||
| MissingMattPocockSkillError, | ||
@@ -85,2 +93,20 @@ MissingSuperpowersSkillError, | ||
| export interface GetSuperpowerInstallSkillPlan { | ||
| source: string; | ||
| repo?: string | undefined; | ||
| } | ||
| export interface GetSuperpowerInstallPromptInput { | ||
| workflowName: string; | ||
| workflowVersion: string; | ||
| skills: GetSuperpowerInstallSkillPlan[]; | ||
| targetDir: string; | ||
| homeDir: string; | ||
| agents: ReturnType<typeof parseSkillInstallAgents>; | ||
| } | ||
| export interface GetSuperpowerInstallPrompt { | ||
| confirmInstall(input: GetSuperpowerInstallPromptInput): Promise<boolean>; | ||
| } | ||
| export interface GetSuperpowerOnboardCommand { | ||
@@ -108,2 +134,3 @@ executable: string; | ||
| installExternalSkillDependency?: GetSuperpowerExternalSkillDependencyInstaller; | ||
| installPrompt?: GetSuperpowerInstallPrompt; | ||
| workflowGitCommandRunner?: WorkflowGitCommandRunner; | ||
@@ -115,3 +142,3 @@ onboardPrompt?: GetSuperpowerOnboardPrompt; | ||
| interface GetSuperpowerInstallCommandOptions { | ||
| dir: string; | ||
| dir?: string; | ||
| agents: string; | ||
@@ -206,8 +233,4 @@ home: string; | ||
| ) | ||
| .option("--dir <dir>", "override directory that receives .getsuperpower/workflows") | ||
| .option( | ||
| "--dir <dir>", | ||
| "project directory that receives .getsuperpower/workflows", | ||
| options.rootDir, | ||
| ) | ||
| .option( | ||
| "--agents <agents>", | ||
@@ -217,3 +240,7 @@ "comma-separated skill install targets: codex,claude,cursor,copilot,opencode (aliases: github-copilot,opencodex)", | ||
| ) | ||
| .option("--home <dir>", "home directory that contains agent config folders", homedir()) | ||
| .option( | ||
| "--home <dir>", | ||
| "home directory for global GetSuperpower records and agent config folders", | ||
| homedir(), | ||
| ) | ||
| .action((source: string, commandOptions: GetSuperpowerInstallCommandOptions) => | ||
@@ -229,3 +256,4 @@ runGetSuperpowerInstall(source, commandOptions, options), | ||
| ): Promise<void> { | ||
| const targetDir = resolvePath(options.rootDir, commandOptions.dir); | ||
| const homeDir = resolveHomePath(commandOptions.home); | ||
| const targetDir = commandOptions.dir ? resolvePath(options.rootDir, commandOptions.dir) : homeDir; | ||
| const bundle = await loadWorkflowBundle(source, { | ||
@@ -238,7 +266,35 @@ cwd: options.rootDir, | ||
| const installAgents = parseSkillInstallAgents(commandOptions.agents); | ||
| const homeDir = resolveHomePath(commandOptions.home); | ||
| const installedExternalPackages = new Set<string>(); | ||
| const skillPlans = getWorkflowInstallSkillPlans(bundle); | ||
| const installPrompt = options.installPrompt ?? createDefaultInstallPrompt(); | ||
| try { | ||
| for (const skillDependency of getWorkflowSkillInstallDependencies(bundle)) { | ||
| printGetSuperpowerInstallPlan({ | ||
| workflowName: bundle.manifest.name, | ||
| workflowVersion: bundle.manifest.version, | ||
| skills: skillPlans, | ||
| targetDir, | ||
| homeDir, | ||
| }); | ||
| const approved = await installPrompt.confirmInstall({ | ||
| workflowName: bundle.manifest.name, | ||
| workflowVersion: bundle.manifest.version, | ||
| skills: skillPlans, | ||
| targetDir, | ||
| homeDir, | ||
| agents: installAgents, | ||
| }); | ||
| if (!approved) { | ||
| console.log(warning("GetSuperpower install cancelled.")); | ||
| return; | ||
| } | ||
| console.log(success("Installing skills...")); | ||
| const skillDependencies = getWorkflowSkillInstallDependencies(bundle); | ||
| for (const [index, skillDependency] of skillDependencies.entries()) { | ||
| const displaySkill = skillPlans[index]?.source ?? skillDependency.source; | ||
| console.log(`Processing ${index + 1}/${skillDependencies.length}: ${displaySkill}`); | ||
| const skillResult = await installGetSuperpowerSkillDependency({ | ||
@@ -259,2 +315,3 @@ rootDir: targetDir, | ||
| }); | ||
| console.log(success(`Installed skill: ${skillResult.skillInstall.skillName}`)); | ||
| } | ||
@@ -266,2 +323,10 @@ | ||
| console.log(keyValue("GetSuperpower file", install.path)); | ||
| console.log( | ||
| getSuperpowerInstallResultBox({ | ||
| workflowName: install.workflow.name, | ||
| workflowVersion: install.workflow.version, | ||
| workflowFile: install.path, | ||
| skillCount: skillDependencies.length, | ||
| }), | ||
| ); | ||
| } finally { | ||
@@ -272,2 +337,33 @@ await bundle.cleanup?.(); | ||
| function getWorkflowInstallSkillPlans(bundle: { | ||
| manifest: { skills: GetSuperpowerInstallSkillPlan[] }; | ||
| }): GetSuperpowerInstallSkillPlan[] { | ||
| return bundle.manifest.skills.map((skill) => ({ | ||
| source: skill.source, | ||
| ...(skill.repo ? { repo: skill.repo } : {}), | ||
| })); | ||
| } | ||
| function printGetSuperpowerInstallPlan(input: { | ||
| workflowName: string; | ||
| workflowVersion: string; | ||
| skills: GetSuperpowerInstallSkillPlan[]; | ||
| targetDir: string; | ||
| homeDir: string; | ||
| }): void { | ||
| console.log( | ||
| success(`GetSuperpower install plan: ${input.workflowName}@${input.workflowVersion}`), | ||
| ); | ||
| console.log(keyValue("Workflow records", input.targetDir)); | ||
| console.log(keyValue("Skill home", input.homeDir)); | ||
| console.log("Skills to install:"); | ||
| for (const skill of input.skills) { | ||
| console.log(`- ${formatInstallSkillPlan(skill)}`); | ||
| } | ||
| } | ||
| function formatInstallSkillPlan(skill: GetSuperpowerInstallSkillPlan): string { | ||
| return skill.repo ? `${skill.source} (${skill.repo})` : skill.source; | ||
| } | ||
| async function installGetSuperpowerSkillDependency(input: { | ||
@@ -501,6 +597,10 @@ rootDir: string; | ||
| .description("List installed GetSuperpowers.") | ||
| .option("--dir <dir>", "project directory with .getsuperpower/workflows", rootDir) | ||
| .action(async (commandOptions: { dir: string }) => { | ||
| .option("--dir <dir>", "override directory with .getsuperpower/workflows") | ||
| .option("--home <dir>", "home directory that contains global GetSuperpower records", homedir()) | ||
| .action(async (commandOptions: { dir?: string; home: string }) => { | ||
| const targetDir = commandOptions.dir | ||
| ? resolvePath(rootDir, commandOptions.dir) | ||
| : resolveHomePath(commandOptions.home); | ||
| const workflows = await listInstalledWorkflowBundles({ | ||
| rootDir: resolvePath(rootDir, commandOptions.dir), | ||
| rootDir: targetDir, | ||
| }); | ||
@@ -654,2 +754,25 @@ | ||
| function createDefaultInstallPrompt(): GetSuperpowerInstallPrompt { | ||
| return { | ||
| confirmInstall: async (input) => { | ||
| if (!process.stdin.isTTY) { | ||
| console.log(muted("Non-interactive shell detected; continuing with install.")); | ||
| return true; | ||
| } | ||
| const result = await clackConfirm({ | ||
| message: `Install ${input.skills.length} skills for ${input.workflowName}?`, | ||
| initialValue: true, | ||
| }); | ||
| if (isCancel(result)) { | ||
| clackCancel("GetSuperpower install cancelled"); | ||
| return false; | ||
| } | ||
| return result; | ||
| }, | ||
| }; | ||
| } | ||
| function resolvePath(rootDir: string, path: string): string { | ||
@@ -656,0 +779,0 @@ return isAbsolute(path) ? path : resolve(join(rootDir, path)); |
@@ -203,3 +203,3 @@ import { mkdir, mkdtemp, stat, writeFile } from "node:fs/promises"; | ||
| const workflowRecord = join( | ||
| input.projectDir, | ||
| input.homeDir, | ||
| ".getsuperpower", | ||
@@ -264,3 +264,3 @@ "workflows", | ||
| ["install", input.workflowSourcePath, "--home", input.homeDir, "--agents", "codex"], | ||
| ["list"], | ||
| ["list", "--home", input.homeDir], | ||
| ], | ||
@@ -270,3 +270,3 @@ }); | ||
| const workflowRecord = join( | ||
| input.projectDir, | ||
| input.homeDir, | ||
| ".getsuperpower", | ||
@@ -294,3 +294,3 @@ "workflows", | ||
| passed: await pathExists(workflowRecord), | ||
| detail: ".getsuperpower/workflows/openspec-delivery.json should exist", | ||
| detail: "global .getsuperpower/workflows/openspec-delivery.json should exist", | ||
| }, | ||
@@ -297,0 +297,0 @@ { |
Sorry, the diff of this file is too big to display
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
915485
0.88%23085
0.96%180
1.69%