getsuperpower
Advanced tools
@@ -11,24 +11,32 @@ { | ||
| { | ||
| "source": "superpowers:brainstorming" | ||
| "source": "superpowers:brainstorming", | ||
| "repo": "obra/superpowers" | ||
| }, | ||
| { | ||
| "source": "mattpocock:design-an-interface" | ||
| "source": "mattpocock:design-an-interface", | ||
| "repo": "mattpocock/skills" | ||
| }, | ||
| { | ||
| "source": "mattpocock:grill-with-docs" | ||
| "source": "mattpocock:grill-with-docs", | ||
| "repo": "mattpocock/skills" | ||
| }, | ||
| { | ||
| "source": "superpowers:writing-plans" | ||
| "source": "superpowers:writing-plans", | ||
| "repo": "obra/superpowers" | ||
| }, | ||
| { | ||
| "source": "mattpocock:codebase-design" | ||
| "source": "mattpocock:codebase-design", | ||
| "repo": "mattpocock/skills" | ||
| }, | ||
| { | ||
| "source": "mattpocock:tdd" | ||
| "source": "mattpocock:tdd", | ||
| "repo": "mattpocock/skills" | ||
| }, | ||
| { | ||
| "source": "mattpocock:diagnosing-bugs" | ||
| "source": "mattpocock:diagnosing-bugs", | ||
| "repo": "mattpocock/skills" | ||
| }, | ||
| { | ||
| "source": "mattpocock:review" | ||
| "source": "mattpocock:review", | ||
| "repo": "mattpocock/skills" | ||
| }, | ||
@@ -35,0 +43,0 @@ { |
@@ -11,9 +11,12 @@ { | ||
| { | ||
| "source": "mattpocock:grilling" | ||
| "source": "mattpocock:grilling", | ||
| "repo": "mattpocock/skills" | ||
| }, | ||
| { | ||
| "source": "superpowers:brainstorming" | ||
| "source": "superpowers:brainstorming", | ||
| "repo": "obra/superpowers" | ||
| }, | ||
| { | ||
| "source": "superpowers:writing-plans" | ||
| "source": "superpowers:writing-plans", | ||
| "repo": "obra/superpowers" | ||
| } | ||
@@ -20,0 +23,0 @@ ], |
@@ -14,9 +14,12 @@ { | ||
| { | ||
| "source": "superpowers:brainstorming" | ||
| "source": "superpowers:brainstorming", | ||
| "repo": "obra/superpowers" | ||
| }, | ||
| { | ||
| "source": "superpowers:writing-plans" | ||
| "source": "superpowers:writing-plans", | ||
| "repo": "obra/superpowers" | ||
| }, | ||
| { | ||
| "source": "mattpocock:tdd" | ||
| "source": "mattpocock:tdd", | ||
| "repo": "mattpocock/skills" | ||
| }, | ||
@@ -23,0 +26,0 @@ { |
@@ -14,18 +14,24 @@ { | ||
| { | ||
| "source": "superpowers:brainstorming" | ||
| "source": "superpowers:brainstorming", | ||
| "repo": "obra/superpowers" | ||
| }, | ||
| { | ||
| "source": "superpowers:writing-plans" | ||
| "source": "superpowers:writing-plans", | ||
| "repo": "obra/superpowers" | ||
| }, | ||
| { | ||
| "source": "mattpocock:grill-with-docs" | ||
| "source": "mattpocock:grill-with-docs", | ||
| "repo": "mattpocock/skills" | ||
| }, | ||
| { | ||
| "source": "mattpocock:tdd" | ||
| "source": "mattpocock:tdd", | ||
| "repo": "mattpocock/skills" | ||
| }, | ||
| { | ||
| "source": "mattpocock:codebase-design" | ||
| "source": "mattpocock:codebase-design", | ||
| "repo": "mattpocock/skills" | ||
| }, | ||
| { | ||
| "source": "mattpocock:diagnosing-bugs" | ||
| "source": "mattpocock:diagnosing-bugs", | ||
| "repo": "mattpocock/skills" | ||
| } | ||
@@ -32,0 +38,0 @@ ], |
@@ -8,3 +8,4 @@ { | ||
| { | ||
| "source": "superpowers:brainstorming" | ||
| "source": "superpowers:brainstorming", | ||
| "repo": "obra/superpowers" | ||
| }, | ||
@@ -15,3 +16,4 @@ { | ||
| { | ||
| "source": "superpowers:writing-plans" | ||
| "source": "superpowers:writing-plans", | ||
| "repo": "obra/superpowers" | ||
| }, | ||
@@ -18,0 +20,0 @@ { |
+1
-1
| { | ||
| "name": "getsuperpower", | ||
| "version": "0.3.3", | ||
| "version": "0.3.4", | ||
| "type": "module", | ||
@@ -5,0 +5,0 @@ "repository": { |
+22
-10
@@ -14,5 +14,11 @@ <img src="/assets/getsupwerpower.jpg" alt="GetSuperpower" width="640" /> | ||
| Install a workflow example from the GetSuperpower git repository: | ||
| Install a workflow example by alias: | ||
| ```bash | ||
| npx getsuperpower@latest install openspec-superpowers | ||
| ``` | ||
| The alias is shorthand for the checked-in workflow path: | ||
| ```bash | ||
| npx getsuperpower@latest install 'https://github.com/0xroylee/getsuperpower.git#examples/workflows/openspec-superpowers' | ||
@@ -24,5 +30,5 @@ ``` | ||
| ```bash | ||
| npx getsuperpower@latest install 'https://github.com/0xroylee/getsuperpower.git#examples/workflows/release-review' | ||
| npx getsuperpower@latest clone 'https://github.com/0xroylee/getsuperpower.git#examples/workflows/real-engineering' | ||
| npx getsuperpower@latest install 'https://github.com/0xroylee/getsuperpower.git#examples/workflows/development-design-delivery' | ||
| npx getsuperpower@latest install release-review | ||
| npx getsuperpower@latest install real-engineering | ||
| npx getsuperpower@latest install development-design-delivery | ||
| ``` | ||
@@ -93,4 +99,4 @@ | ||
| ```bash | ||
| npx getsuperpower@latest validate release-review | ||
| npx getsuperpower@latest deps release-review | ||
| npx getsuperpower@latest validate ./release-review | ||
| npx getsuperpower@latest deps ./release-review | ||
| ``` | ||
@@ -109,4 +115,11 @@ | ||
| GetSuperpower install automatically uses the Skills CLI to fetch missing `mattpocock:*` dependencies. If that automatic bootstrap fails, run the same package install through the CLI and retry: | ||
| GetSuperpower install automatically uses each workflow skill's `repo` metadata | ||
| to fetch missing external skills through the Skills CLI. For example, | ||
| `{ "source": "superpowers:brainstorming", "repo": "obra/superpowers" }` | ||
| keeps the original skill name in `source` and installs it with | ||
| `npx skills add obra/superpowers --skill brainstorming`. | ||
| If automatic bootstrap fails, run the same package install through the CLI and | ||
| retry: | ||
| ```bash | ||
@@ -120,8 +133,7 @@ npx getsuperpower@latest skills install mattpocock/skills | ||
| - `npx getsuperpower@latest install <path-or-git-url>` | ||
| - `npx getsuperpower@latest clone <path-or-git-url>` | ||
| - `npx getsuperpower@latest install <alias-or-path-or-git-url>` | ||
| - `npx getsuperpower@latest deps <source>` | ||
| - `npx getsuperpower@latest list` | ||
| - `npx getsuperpower@latest init <name>` | ||
| - `npx getsuperpower@latest validate <path>` | ||
| - `npx getsuperpower@latest validate <source>` | ||
| - `npx getsuperpower@latest skills install` | ||
@@ -128,0 +140,0 @@ - `npx getsuperpower@latest skills install mattpocock/skills` |
+1
-1
@@ -54,3 +54,3 @@ import pc from "picocolors"; | ||
| ` ${commandText("getsuperpower validate ./release-review")}`, | ||
| ` ${commandText("getsuperpower clone https://github.com/acme/release-review.git")}`, | ||
| ` ${commandText("getsuperpower install openspec-superpowers")}`, | ||
| ` ${commandText("getsuperpower install ./release-review")}`, | ||
@@ -57,0 +57,0 @@ "", |
+1
-1
@@ -24,3 +24,3 @@ #!/usr/bin/env bun | ||
| const CLI_VERSION = "0.3.3"; | ||
| const CLI_VERSION = "0.3.4"; | ||
@@ -27,0 +27,0 @@ interface CommanderVersionInternals { |
+90
-27
@@ -15,3 +15,3 @@ import { existsSync } from "node:fs"; | ||
| createWorkflowBundleScaffold, | ||
| getWorkflowSkillInstallSources, | ||
| getWorkflowSkillInstallDependencies, | ||
| installWorkflowBundle, | ||
@@ -55,2 +55,3 @@ listInstalledWorkflowBundles, | ||
| source: string; | ||
| repo?: string; | ||
| homeDir: string; | ||
@@ -112,4 +113,2 @@ runCommand?: GetSuperpowerExternalSkillCommandRunner; | ||
| type GetSuperpowerInstallVerb = "install" | "clone"; | ||
| interface GetSuperpowerInstallCommandOptions { | ||
@@ -135,3 +134,3 @@ dir: string; | ||
| .description("Compatibility alias for GetSuperpower install and list commands."); | ||
| configureInstallCommand(workflowCommand, options, { includeClone: false }); | ||
| configureInstallCommand(workflowCommand, options); | ||
| configureListCommand(workflowCommand, options.rootDir); | ||
@@ -199,19 +198,10 @@ | ||
| options: ConfigureGetSuperpowerCommandOptions, | ||
| commandOptions: { includeClone?: boolean } = {}, | ||
| ): void { | ||
| configureInstallLikeCommand(command, options, "install"); | ||
| if (commandOptions.includeClone !== false) { | ||
| configureInstallLikeCommand(command, options, "clone"); | ||
| } | ||
| } | ||
| function configureInstallLikeCommand( | ||
| command: Command, | ||
| options: ConfigureGetSuperpowerCommandOptions, | ||
| verb: GetSuperpowerInstallVerb, | ||
| ): void { | ||
| command | ||
| .command(verb) | ||
| .command("install") | ||
| .description("Install a GetSuperpower and its skills.") | ||
| .argument("<source>", "local GetSuperpower path, workflow.json path, or public git source") | ||
| .argument( | ||
| "<source>", | ||
| "workflow alias, local GetSuperpower path, workflow.json path, or public git source", | ||
| ) | ||
| .option( | ||
@@ -250,6 +240,7 @@ "--dir <dir>", | ||
| try { | ||
| for (const skillSource of getWorkflowSkillInstallSources(bundle)) { | ||
| for (const skillDependency of getWorkflowSkillInstallDependencies(bundle)) { | ||
| const skillResult = await installGetSuperpowerSkillDependency({ | ||
| rootDir: targetDir, | ||
| source: skillSource, | ||
| source: skillDependency.source, | ||
| ...(skillDependency.repo ? { repo: skillDependency.repo } : {}), | ||
| homeDir, | ||
@@ -280,2 +271,3 @@ agents: installAgents, | ||
| source: string; | ||
| repo?: string; | ||
| homeDir: string; | ||
@@ -290,3 +282,7 @@ agents: ReturnType<typeof parseSkillInstallAgents>; | ||
| } catch (error) { | ||
| const externalPackage = getSkillsCliPackageForMissingDependency(input.source, error); | ||
| const externalPackage = getSkillsCliPackageForMissingDependency( | ||
| input.source, | ||
| input.repo, | ||
| error, | ||
| ); | ||
| if (!externalPackage) { | ||
@@ -296,9 +292,13 @@ throw error; | ||
| if (!input.installedExternalPackages.has(externalPackage)) { | ||
| const externalInstallKey = | ||
| getSkillsCliInstallKeyForSource(input.source, input.repo) ?? externalPackage; | ||
| if (!input.installedExternalPackages.has(externalInstallKey)) { | ||
| console.log(keyValue("Installing external skill dependency", externalPackage)); | ||
| await input.installExternalSkillDependency({ | ||
| source: input.source, | ||
| ...(input.repo ? { repo: input.repo } : {}), | ||
| homeDir: input.homeDir, | ||
| }); | ||
| input.installedExternalPackages.add(externalPackage); | ||
| input.installedExternalPackages.add(externalInstallKey); | ||
| } | ||
@@ -339,3 +339,7 @@ | ||
| function getSkillsCliPackageForMissingDependency(source: string, error: unknown): string | null { | ||
| function getSkillsCliPackageForMissingDependency( | ||
| source: string, | ||
| repo: string | undefined, | ||
| error: unknown, | ||
| ): string | null { | ||
| if (!isMissingBootstrappableSkillError(error)) { | ||
@@ -345,3 +349,3 @@ return null; | ||
| return getSkillsCliPackageForSource(source); | ||
| return getSkillsCliPackageForDependency(source, repo); | ||
| } | ||
@@ -383,2 +387,55 @@ | ||
| function getSkillsCliPackageForDependency(source: string, repo: string | undefined): string | null { | ||
| return normalizeSkillsCliRepoSource(repo) ?? getSkillsCliPackageForSource(source); | ||
| } | ||
| function normalizeSkillsCliRepoSource(repo: string | undefined): string | null { | ||
| const trimmed = repo?.trim(); | ||
| if (!trimmed) { | ||
| return null; | ||
| } | ||
| const markdownLinkMatch = /^\[([^\]]+)\]\(([^)]+)\)$/.exec(trimmed); | ||
| if (!markdownLinkMatch) { | ||
| return trimmed; | ||
| } | ||
| const label = markdownLinkMatch[1]?.trim(); | ||
| const url = markdownLinkMatch[2]?.trim(); | ||
| if (label && isBareSkillsCliPackage(label)) { | ||
| return label; | ||
| } | ||
| return url || null; | ||
| } | ||
| function getSkillsCliInstallKeyForSource(source: string, repo?: string): string | null { | ||
| const skillName = getSkillsCliSkillNameForSource(source); | ||
| if (!skillName) { | ||
| return getSkillsCliPackageForDependency(source, repo); | ||
| } | ||
| const packageName = getSkillsCliPackageForDependency(source, repo); | ||
| return packageName ? `${packageName}:${skillName}` : null; | ||
| } | ||
| function getSkillsCliSkillNameForSource(source: string): string | null { | ||
| if (source.startsWith("superpowers:")) { | ||
| return source.slice("superpowers:".length).trim() || null; | ||
| } | ||
| if (source.startsWith("mattpocock:")) { | ||
| return source.slice("mattpocock:".length).trim() || null; | ||
| } | ||
| const mattPocockGithubPrefix = "github:mattpocock/skills/"; | ||
| if (source.startsWith(mattPocockGithubPrefix)) { | ||
| const suffix = source.slice(mattPocockGithubPrefix.length); | ||
| const skillPath = suffix.startsWith("skills/") ? suffix.slice("skills/".length) : suffix; | ||
| return skillPath.trim() || null; | ||
| } | ||
| return null; | ||
| } | ||
| function isBareSkillsCliPackage(source: string): boolean { | ||
@@ -391,3 +448,3 @@ return /^[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$/.test(source); | ||
| ): Promise<void> { | ||
| const packageName = getSkillsCliPackageForSource(input.source); | ||
| const packageName = getSkillsCliPackageForDependency(input.source, input.repo); | ||
| if (!packageName) { | ||
@@ -397,5 +454,11 @@ throw new Error(`No skills CLI package is known for dependency: ${input.source}`); | ||
| const args = ["--yes", "skills@latest", "add", packageName, "--yes", "--global"]; | ||
| const skillName = getSkillsCliSkillNameForSource(input.source); | ||
| if (skillName) { | ||
| args.push("--skill", skillName, "--agent", "codex"); | ||
| } | ||
| const result = await (input.runCommand ?? runExternalSkillCommand)({ | ||
| executable: "npx", | ||
| args: ["--yes", "skills@latest", "add", packageName, "--yes", "--global"], | ||
| args, | ||
| cwd: input.homeDir, | ||
@@ -402,0 +465,0 @@ env: { |
@@ -9,5 +9,9 @@ import { existsSync } from "node:fs"; | ||
| const workflowStoreDir = ".getsuperpower/workflows"; | ||
| const canonicalExamplesGitUrl = "https://github.com/0xroylee/getsuperpower.git"; | ||
| const canonicalExamplesWorkflowPath = "examples/workflows"; | ||
| const workflowAliasPattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/; | ||
| const WorkflowSkillSchema = z.object({ | ||
| source: z.string().min(1), | ||
| repo: z.string().min(1).optional(), | ||
| optional: z.boolean().optional(), | ||
@@ -103,2 +107,7 @@ }); | ||
| export interface WorkflowSkillInstallDependency { | ||
| source: string; | ||
| repo?: string; | ||
| } | ||
| export interface InstalledWorkflowBundle extends WorkflowBundleManifest { | ||
@@ -146,2 +155,7 @@ source: WorkflowBundleSource; | ||
| await resolvedSource.cleanup?.(); | ||
| if (resolvedSource.alias && resolvedSource.source.kind === "git" && isMissingFileError(error)) { | ||
| throw new Error( | ||
| `GetSuperpower workflow alias not found: ${resolvedSource.alias}\nChecked: ${resolvedSource.source.url}`, | ||
| ); | ||
| } | ||
| if (resolvedSource.source.kind === "git" && isMissingFileError(error)) { | ||
@@ -230,7 +244,17 @@ throw new Error( | ||
| export function getWorkflowSkillInstallSources(bundle: WorkflowBundle): string[] { | ||
| return getWorkflowSkillInstallDependencies(bundle).map((skill) => skill.source); | ||
| } | ||
| export function getWorkflowSkillInstallDependencies( | ||
| bundle: WorkflowBundle, | ||
| ): WorkflowSkillInstallDependency[] { | ||
| return bundle.manifest.skills.map((skill) => { | ||
| if (skill.source.startsWith("./") || skill.source.startsWith("../")) { | ||
| return resolve(bundle.sourceDir, skill.source); | ||
| } | ||
| return skill.source; | ||
| const source = | ||
| skill.source.startsWith("./") || skill.source.startsWith("../") | ||
| ? resolve(bundle.sourceDir, skill.source) | ||
| : skill.source; | ||
| return { | ||
| source, | ||
| ...(skill.repo ? { repo: skill.repo } : {}), | ||
| }; | ||
| }); | ||
@@ -254,5 +278,5 @@ } | ||
| { source: `./skills/${name}` }, | ||
| { source: "superpowers:brainstorming" }, | ||
| { source: "superpowers:brainstorming", repo: "obra/superpowers" }, | ||
| { source: "./skills/custom-review" }, | ||
| { source: "superpowers:writing-plans" }, | ||
| { source: "superpowers:writing-plans", repo: "obra/superpowers" }, | ||
| ], | ||
@@ -332,2 +356,3 @@ steps: [ | ||
| cleanup?: () => Promise<void>; | ||
| alias?: string; | ||
| } | ||
@@ -343,2 +368,7 @@ | ||
| ): Promise<ResolvedWorkflowBundleSource> { | ||
| const aliasSource = parseWorkflowAliasSource(source); | ||
| if (aliasSource) { | ||
| return cloneGitWorkflowSource(aliasSource, options); | ||
| } | ||
| const gitSource = parseGitWorkflowSource(source); | ||
@@ -355,6 +385,20 @@ if (gitSource) { | ||
| throw new Error( | ||
| `Unsupported GetSuperpower source: ${source}. Use a local path, workflow.json path, or public git URL.`, | ||
| `Unsupported GetSuperpower source: ${source}. Use a local path, workflow.json path, public git URL, or lowercase workflow alias.`, | ||
| ); | ||
| } | ||
| function parseWorkflowAliasSource(source: string): GitWorkflowSource | null { | ||
| if (!workflowAliasPattern.test(source)) { | ||
| return null; | ||
| } | ||
| const url = `${canonicalExamplesGitUrl}#${canonicalExamplesWorkflowPath}/${source}`; | ||
| const gitSource = parseGitWorkflowSource(url); | ||
| if (!gitSource) { | ||
| throw new Error(`Could not build canonical GetSuperpower workflow alias URL: ${source}`); | ||
| } | ||
| return { ...gitSource, alias: source }; | ||
| } | ||
| function isLocalWorkflowSource(source: string): boolean { | ||
@@ -376,2 +420,3 @@ return ( | ||
| subdirectory?: string; | ||
| alias?: string; | ||
| } | ||
@@ -477,2 +522,3 @@ | ||
| cleanup, | ||
| ...(source.alias ? { alias: source.alias } : {}), | ||
| }; | ||
@@ -479,0 +525,0 @@ } catch (error) { |
@@ -237,3 +237,3 @@ import { mkdir, mkdtemp, stat, writeFile } from "node:fs/promises"; | ||
| passed: !(await pathExists(workflowRecord)), | ||
| detail: ".getsuperpower workflow records should not exist before install/clone", | ||
| detail: ".getsuperpower workflow records should not exist before install", | ||
| }, | ||
@@ -263,3 +263,3 @@ { | ||
| commands: [ | ||
| ["clone", input.workflowSourcePath, "--home", input.homeDir, "--agents", "codex"], | ||
| ["install", input.workflowSourcePath, "--home", input.homeDir, "--agents", "codex"], | ||
| ["list"], | ||
@@ -283,3 +283,3 @@ ], | ||
| mode: "workflow", | ||
| label: "After workflow clone/use", | ||
| label: "After workflow install/use", | ||
| projectDir: input.projectDir, | ||
@@ -306,3 +306,3 @@ homeDir: input.homeDir, | ||
| passed: logs.some((line) => stripAnsi(line).includes("openspec-delivery 0.1.0")), | ||
| detail: "list should show the workflow installed by clone", | ||
| detail: "list should show the workflow installed by install", | ||
| }, | ||
@@ -309,0 +309,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.
907502
0.8%22865
0.81%177
7.27%