diff --git a/.gitignore b/.gitignore index cda52e3..afcbece 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ dist .DS_Store package-lock.json .local/ -docs/ \ No newline at end of file +docs/ +plans/ diff --git a/README.md b/README.md index a393c20..89f3481 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ --- -Install Scalekit auth extensions into your AI coding tools — Cursor, Claude Code, Codex, and GitHub Copilot — with auto-detection, interactive prompts, and dry-run previews. +Install Scalekit auth extensions (and skills) into your AI coding tools — Cursor, Claude Code, Codex, GitHub Copilot, and others — with auto-detection, interactive prompts, and dry-run previews. ## Quick start @@ -44,6 +44,13 @@ scalekit setup Auto-detects installed tools, lets you pick which ones to set up, and installs the auth extensions. +You can also install Scalekit skills (for Cline, Windsurf, Aider, and other agents) from the unified Authstack: + +```bash +scalekit setup # choose "Scalekit skills" in the wizard +scalekit setup --skip-skills # stacks only +``` + ### Direct install Skip the wizard and target a specific tool: @@ -90,8 +97,9 @@ scalekit extension status # check all versions ``` scalekit show help -scalekit setup interactive setup wizard +scalekit setup interactive setup wizard (stacks + skills) scalekit setup set up a specific tool +scalekit setup --skip-skills stacks only, skip skills scalekit extension install install by id or alias (alias: ext i) scalekit extension uninstall uninstall by id or alias (alias: ext rm) diff --git a/package.json b/package.json index 91476ad..3aa4000 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@scalekit-inc/cli", - "version": "0.3.13", + "version": "0.3.16", "description": "Scalekit CLI", "type": "module", "bin": { diff --git a/src/commands/setup.ts b/src/commands/setup.ts index 44085fb..d77bc6a 100644 --- a/src/commands/setup.ts +++ b/src/commands/setup.ts @@ -70,14 +70,12 @@ async function runSkillsInstall(): Promise { log.step("Installing skills..."); try { await installSkills(); - log.success("Skills installed."); + log.success("Skills installed from Authstack."); return true; } catch (err) { const message = err instanceof Error ? err.message : String(err); log.error(`Skills installation failed: ${message}`); - log.info( - `You can install later: ${pc.cyan("npx skills add scalekit-inc/skills")}`, - ); + log.info(`You can install later: ${pc.cyan(SKILLS_CMD)}`); return false; } } @@ -86,7 +84,7 @@ async function interactiveSetup(opts: SetupOpts, cmd: Command) { const json = isJson(cmd); const nonInteractive = isNonInteractive(cmd); - if (!json) intro("ScaleKit Setup"); + if (!json) intro("Scalekit Setup"); const detected = stacks.filter((s) => s.detect()); @@ -107,8 +105,8 @@ async function interactiveSetup(opts: SetupOpts, cmd: Command) { : [ { value: SKILLS_ID, - label: "Other agents", - hint: "Cline, Windsurf, Aider & more", + label: "Scalekit skills", + hint: "for Cline, Windsurf, Aider & more (via Authstack)", }, ]; @@ -155,7 +153,7 @@ async function interactiveSetup(opts: SetupOpts, cmd: Command) { skillsInstalled = await runSkillsInstall(); } else { const action = await select({ - message: "How do you want to install skills?", + message: "How do you want to install Scalekit skills (from Authstack)?", options: [ { value: "auto", @@ -174,8 +172,9 @@ async function interactiveSetup(opts: SetupOpts, cmd: Command) { skillsInstalled = await runSkillsInstall(); } else { log.info(""); - log.info("Run this to install skills with the interactive wizard:"); - log.info(` ${pc.cyan("npx skills add scalekit-inc/skills")}`); + log.info("Run this to install Scalekit skills from Authstack:"); + log.info(` ${pc.cyan(SKILLS_CMD)}`); + log.info(" (This pulls skills like setup guidance; the content is maintained in the Authstack repo.)"); log.info(""); } } @@ -245,7 +244,7 @@ async function directSetup(stackId: string, opts: SetupOpts, cmd: Command) { } if (!isNonInteractive(cmd) && !opts.dryRun) { - intro("ScaleKit Setup"); + intro("Scalekit Setup"); const ok = await confirm({ message: `Install ${stack.name} auth stack?`, }); @@ -303,11 +302,11 @@ const setupExtensionShortcut = styledCommand("extension") }); export const setupCommand = styledCommand("setup") - .description("set up ScaleKit auth stacks for your coding agents") + .description("set up Scalekit auth stacks and skills for your coding agents") .argument("[stack]", "cursor, claude, codex, copilot (or any alias)") .option("-y, --yes", "skip confirmation prompts") .option("--dry-run", "show commands without executing") - .option("--skip-skills", "skip Scalekit skills installation") + .option("--skip-skills", "skip Scalekit skills (from Authstack)") .addCommand(setupExtensionShortcut) .addHelpText( "after", @@ -318,7 +317,7 @@ Examples: $ scalekit setup extension cc shortcut → extension install claude $ scalekit setup codex -y skip confirmation $ scalekit setup --dry-run preview commands without running them - $ scalekit setup --skip-skills set up agents only, skip skills`, + $ scalekit setup --skip-skills stacks only (skip Scalekit skills)`, ) .action( async (stackId: string | undefined, opts: SetupOpts, cmd: Command) => { diff --git a/src/core/authstack.ts b/src/core/authstack.ts new file mode 100644 index 0000000..fbdda68 --- /dev/null +++ b/src/core/authstack.ts @@ -0,0 +1,30 @@ +export const AUTHSTACK_REPO = "scalekit-inc/authstack"; +export const AUTHSTACK_MARKETPLACE = "authstack"; +export const AUTHSTACK_KITS = ["agentkit", "saaskit"] as const; + +export const AUTHSTACK_URL = + `https://github.com/${AUTHSTACK_REPO}/archive/refs/heads/main.tar.gz`; + +export const AUTHSTACK_ARCHIVE_DIR = `${AUTHSTACK_REPO.split("/").pop()}-main`; + +export const CLI_PACKAGE = "@scalekit-inc/cli"; + +export function getSetupCommand(stack: string) { + return `npx ${CLI_PACKAGE} setup ${stack}`; +} + +export function getPluginMarketplaceCommands(tool: "claude" | "copilot") { + return [ + `${tool} plugin marketplace add ${AUTHSTACK_REPO}`, + `${tool} plugin install agentkit@${AUTHSTACK_MARKETPLACE}`, + `${tool} plugin install saaskit@${AUTHSTACK_MARKETPLACE}`, + ]; +} + +export function getPluginUninstallCommands(tool: "claude" | "copilot") { + return [ + `${tool} plugin uninstall agentkit@${AUTHSTACK_MARKETPLACE}`, + `${tool} plugin uninstall saaskit@${AUTHSTACK_MARKETPLACE}`, + `${tool} plugin marketplace remove ${AUTHSTACK_MARKETPLACE}`, + ]; +} diff --git a/src/core/downloader.ts b/src/core/downloader.ts index 7f77691..9da72cd 100644 --- a/src/core/downloader.ts +++ b/src/core/downloader.ts @@ -2,8 +2,9 @@ import { execFileSync } from "node:child_process"; import { access, writeFile } from "node:fs/promises"; import { join } from "node:path"; -export const AUTHSTACK_URL = - "https://github.com/scalekit-inc/authstack/archive/refs/heads/main.tar.gz"; +import { AUTHSTACK_ARCHIVE_DIR, AUTHSTACK_URL } from "./authstack.js"; + +export { AUTHSTACK_URL }; export async function downloadAuthstack(tmpDir: string): Promise { const sourceDir = process.env.AUTHSTACK_SOURCE_DIR; @@ -19,7 +20,7 @@ export async function downloadAuthstack(tmpDir: string): Promise { execFileSync("tar", ["-xzf", archivePath, "-C", tmpDir]); - const root = join(tmpDir, "authstack-main"); + const root = join(tmpDir, AUTHSTACK_ARCHIVE_DIR); try { await access(join(root, "kits")); } catch { diff --git a/src/core/skills.ts b/src/core/skills.ts index bba84a4..df08535 100644 --- a/src/core/skills.ts +++ b/src/core/skills.ts @@ -1,6 +1,12 @@ import { spawn } from "node:child_process"; -const SKILLS_REPO = "scalekit-inc/skills"; +import { AUTHSTACK_REPO } from "./authstack.js"; + +// Skills (including setup guidance) are served from the same unified +// scalekit-inc/authstack repo used for the editor stacks/plugins. +// If the installed guidance references legacy split repos, that content +// lives in the authstack repo itself and should be updated there. +const SKILLS_REPO = AUTHSTACK_REPO; export const SKILLS_CMD = `npx skills add ${SKILLS_REPO} --all`; diff --git a/src/index.ts b/src/index.ts index cf8e6c1..310a1f8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,7 +12,7 @@ function showBanner() { return; } - cfonts.say("ScaleKit", { + cfonts.say("Scalekit", { font: "chrome", align: "left", colors: ["cyan", "white", "blue"], @@ -22,7 +22,7 @@ function showBanner() { } const program = styledCommand("scalekit") - .description("Auth stack for the agentic era") + .description("Authstack for devs building AI agents, MCP servers and SaaS apps") .version(version) .option("--plain", "disable colors and styling (also respects NO_COLOR env)") .option("--json", "output machine-readable JSON") diff --git a/src/stacks/claude.ts b/src/stacks/claude.ts index 8c3b17f..40abece 100644 --- a/src/stacks/claude.ts +++ b/src/stacks/claude.ts @@ -4,21 +4,17 @@ import { homedir } from "node:os"; import { join } from "node:path"; import semver from "semver"; import { runShellCommands } from "../core/shell.js"; +import { + AUTHSTACK_MARKETPLACE, + getPluginMarketplaceCommands, + getPluginUninstallCommands, +} from "../core/authstack.js"; import type { Stack, VersionStatus } from "./registry.js"; -const CMDS = [ - "claude plugin marketplace add scalekit-inc/authstack", - "claude plugin install agentkit@authstack", - "claude plugin install saaskit@authstack", -]; +const CMDS = getPluginMarketplaceCommands("claude"); +const UNINSTALL_CMDS = getPluginUninstallCommands("claude"); -const UNINSTALL_CMDS = [ - "claude plugin uninstall agentkit@authstack", - "claude plugin uninstall saaskit@authstack", - "claude plugin marketplace remove authstack", -]; - -const MARKETPLACE_ID = "authstack"; +const MARKETPLACE_ID = AUTHSTACK_MARKETPLACE; const PLUGIN_NAME = "agentkit"; const PLUGIN_DIR = join( homedir(), @@ -88,7 +84,7 @@ export const claudeStack: Stack = { uninstallCommands: UNINSTALL_CMDS, nextSteps: [ "Run `claude` to start a session", - "Enable auto-update: /plugins → Marketplace → authstack → Enable auto-update", + `Enable auto-update: /plugins → Marketplace → ${AUTHSTACK_MARKETPLACE} → Enable auto-update`, 'Try: "Connect my Gmail account using Scalekit"', ], tryItNow: 'claude "Analyze my project and suggest how Scalekit can power it"', diff --git a/src/stacks/codex.ts b/src/stacks/codex.ts index ea7bca5..a4fa50b 100644 --- a/src/stacks/codex.ts +++ b/src/stacks/codex.ts @@ -3,9 +3,14 @@ import { cp, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { homedir, tmpdir } from "node:os"; import { join } from "node:path"; import { downloadAuthstack } from "../core/downloader.js"; +import { + AUTHSTACK_KITS, + AUTHSTACK_MARKETPLACE, + getSetupCommand, +} from "../core/authstack.js"; import type { Stack } from "./registry.js"; -const MARKETPLACE_DIR = join(homedir(), ".codex", "marketplaces", "authstack"); +const MARKETPLACE_DIR = join(homedir(), ".codex", "marketplaces", AUTHSTACK_MARKETPLACE); const PERSONAL_MARKETPLACE = join( homedir(), ".agents", @@ -16,28 +21,17 @@ const PERSONAL_MARKETPLACE = join( function buildMarketplaceJson(marketplaceDir: string): string { return JSON.stringify( { - name: "authstack", - interface: { displayName: "Scalekit Auth Stack" }, - plugins: [ - { - name: "agentkit", - source: { - source: "local", - path: join(marketplaceDir, "kits", "agentkit"), - }, - policy: { installation: "AVAILABLE", authentication: "ON_INSTALL" }, - category: "Agent Auth", + name: AUTHSTACK_MARKETPLACE, + interface: { displayName: "Scalekit Authstack" }, + plugins: AUTHSTACK_KITS.map((kit) => ({ + name: kit, + source: { + source: "local", + path: join(marketplaceDir, "kits", kit), }, - { - name: "saaskit", - source: { - source: "local", - path: join(marketplaceDir, "kits", "saaskit"), - }, - policy: { installation: "AVAILABLE", authentication: "ON_INSTALL" }, - category: "Application Auth", - }, - ], + policy: { installation: "AVAILABLE", authentication: "ON_INSTALL" }, + category: kit === "agentkit" ? "Agent Auth" : "Application Auth", + })), }, null, 2, @@ -68,7 +62,7 @@ export const codexStack: Stack = { name: "Codex", description: "Scalekit auth plugins for Codex / OpenCode", aliases: ["opencode"], - commands: ["npx @scalekit-inc/cli setup codex"], + commands: [getSetupCommand("codex")], uninstallCommands: [ `rm -rf ${MARKETPLACE_DIR}`, `rm -f ${PERSONAL_MARKETPLACE}`, @@ -77,6 +71,14 @@ export const codexStack: Stack = { nextSteps: ["Run `codex mcp login scalekit` to authenticate"], tryItNow: 'codex "Analyze my project and suggest how Scalekit can power it"', + async checkVersion() { + const detected = this.detect ? this.detect() : false; + if (!detected) { + return { installed: false, status: "not_installed" as const }; + } + return { installed: true, status: "unknown" as const }; + }, + detect() { try { execFileSync( @@ -102,7 +104,7 @@ export const codexStack: Stack = { await cp(autostackRoot, MARKETPLACE_DIR, { recursive: true }); const existingName = await readMarketplaceName(); - if (existingName === null || existingName === "authstack") { + if (existingName === null || existingName === AUTHSTACK_MARKETPLACE) { await writePersonalMarketplace(); } } finally { @@ -113,7 +115,7 @@ export const codexStack: Stack = { async uninstall() { await rm(MARKETPLACE_DIR, { recursive: true, force: true }); const existingName = await readMarketplaceName(); - if (existingName === "authstack") { + if (existingName === AUTHSTACK_MARKETPLACE) { await rm(PERSONAL_MARKETPLACE, { force: true }); } }, diff --git a/src/stacks/copilot.ts b/src/stacks/copilot.ts index 7323a2b..918133d 100644 --- a/src/stacks/copilot.ts +++ b/src/stacks/copilot.ts @@ -1,18 +1,14 @@ import { execFileSync } from "node:child_process"; import { runShellCommands } from "../core/shell.js"; +import { + AUTHSTACK_MARKETPLACE, + getPluginMarketplaceCommands, + getPluginUninstallCommands, +} from "../core/authstack.js"; import type { Stack } from "./registry.js"; -const CMDS = [ - "copilot plugin marketplace add scalekit-inc/authstack", - "copilot plugin install agentkit@authstack", - "copilot plugin install saaskit@authstack", -]; - -const UNINSTALL_CMDS = [ - "copilot plugin uninstall agentkit@authstack", - "copilot plugin uninstall saaskit@authstack", - "copilot plugin marketplace remove authstack", -]; +const CMDS = getPluginMarketplaceCommands("copilot"); +const UNINSTALL_CMDS = getPluginUninstallCommands("copilot"); export const copilotStack: Stack = { id: "copilot", @@ -23,12 +19,20 @@ export const copilotStack: Stack = { uninstallCommands: UNINSTALL_CMDS, nextSteps: [ "Run `copilot` to start a session", - "To update later: `copilot plugin update agentkit@authstack`", + `To update later: \`copilot plugin update agentkit@${AUTHSTACK_MARKETPLACE}\``, 'Try: "Connect my Gmail account using Scalekit"', ], tryItNow: 'copilot -i "Analyze my project and suggest how Scalekit can power it"', + async checkVersion() { + const detected = this.detect ? this.detect() : false; + if (!detected) { + return { installed: false, status: "not_installed" as const }; + } + return { installed: true, status: "unknown" as const }; + }, + detect() { try { execFileSync( diff --git a/src/stacks/cursor.ts b/src/stacks/cursor.ts index e02a09d..5c88f09 100644 --- a/src/stacks/cursor.ts +++ b/src/stacks/cursor.ts @@ -4,16 +4,17 @@ import { cp, mkdir, mkdtemp, rm } from "node:fs/promises"; import { homedir, tmpdir } from "node:os"; import { join } from "node:path"; import { downloadAuthstack } from "../core/downloader.js"; +import { AUTHSTACK_KITS, getSetupCommand } from "../core/authstack.js"; import type { Stack } from "./registry.js"; const PLUGIN_DIR = join(homedir(), ".cursor", "plugins", "local"); -const KIT_NAMES = ["agentkit", "saaskit"] as const; +const KIT_NAMES = AUTHSTACK_KITS; export const cursorStack: Stack = { id: "cursor", name: "Cursor", description: "Scalekit auth plugins for Cursor", - commands: ["npx @scalekit-inc/cli setup cursor"], + commands: [getSetupCommand("cursor")], uninstallCommands: KIT_NAMES.map( (name) => `rm -rf ${join(PLUGIN_DIR, name)}`, ), @@ -64,6 +65,14 @@ export const cursorStack: Stack = { tryItNow: 'Open Cursor → ⌘L → Ask: "Analyze my project and suggest how Scalekit can power it"', + async checkVersion() { + const detected = this.detect ? this.detect() : false; + if (!detected) { + return { installed: false, status: "not_installed" as const }; + } + return { installed: true, status: "unknown" as const }; + }, + async uninstall() { for (const name of KIT_NAMES) { await rm(join(PLUGIN_DIR, name), { recursive: true, force: true }); diff --git a/test/commands/setup.test.ts b/test/commands/setup.test.ts index 1a206f2..c5e8fcf 100644 --- a/test/commands/setup.test.ts +++ b/test/commands/setup.test.ts @@ -1,5 +1,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; +import { AUTHSTACK_REPO } from "../../src/core/authstack.js"; + vi.mock("@clack/prompts", () => ({ intro: vi.fn(), outro: vi.fn(), @@ -13,7 +15,7 @@ vi.mock("@clack/prompts", () => ({ vi.mock("../../src/core/skills.js", () => ({ installSkills: vi.fn(() => Promise.resolve()), - SKILLS_CMD: "npx skills add scalekit-inc/skills --all", + SKILLS_CMD: `npx skills add ${AUTHSTACK_REPO} --all`, })); import { @@ -275,7 +277,7 @@ describe("skills installation", () => { expect(mockInstallSkills).not.toHaveBeenCalled(); expect(mockLog.info).toHaveBeenCalledWith( - "Would run: npx skills add scalekit-inc/skills --all", + `Would run: npx skills add ${AUTHSTACK_REPO} --all`, ); }); @@ -287,7 +289,7 @@ describe("skills installation", () => { await run([]); expect(mockInstallSkills).toHaveBeenCalled(); - expect(mockLog.success).toHaveBeenCalledWith("Skills installed."); + expect(mockLog.success).toHaveBeenCalledWith("Skills installed from authstack."); }); it("interactive: 'I'll do it myself' shows the command", async () => { @@ -300,7 +302,7 @@ describe("skills installation", () => { expect(mockInstallSkills).not.toHaveBeenCalled(); const calls = mockLog.info.mock.calls.map((c) => c[0] as string); expect( - calls.some((c) => c.includes("npx skills add scalekit-inc/skills")), + calls.some((c) => c.includes(`npx skills add ${AUTHSTACK_REPO}`)), ).toBe(true); }); @@ -313,7 +315,7 @@ describe("skills installation", () => { expect(mockInstallSkills).not.toHaveBeenCalled(); }); - it("multiselect includes 'Other agents' option", async () => { + it("multiselect includes the skills option", async () => { stubStacks(); mockMultiselect.mockResolvedValue(["cursor"] as never); @@ -324,7 +326,7 @@ describe("skills installation", () => { }; const skillsOpt = call.options.find((o) => o.value === "skills"); expect(skillsOpt).toBeDefined(); - expect(skillsOpt?.label).toBe("Other agents"); + expect(skillsOpt?.label).toBe("Scalekit skills"); }); it("--skip-skills hides skills from multiselect", async () => { @@ -351,7 +353,7 @@ describe("skills installation", () => { ); const infoCalls = mockLog.info.mock.calls.map((c) => c[0] as string); expect( - infoCalls.some((c) => c.includes("npx skills add scalekit-inc/skills")), + infoCalls.some((c) => c.includes(`npx skills add ${AUTHSTACK_REPO}`)), ).toBe(true); }); diff --git a/test/core/__snapshots__/help.test.ts.snap b/test/core/__snapshots__/help.test.ts.snap index 2f14143..20fe2fc 100644 --- a/test/core/__snapshots__/help.test.ts.snap +++ b/test/core/__snapshots__/help.test.ts.snap @@ -16,20 +16,3 @@ OPTIONS --dry-run preview without executing -h, --help display help for command" `; - -exports[`scalekitHelp formatter formats help with custom sections 1`] = ` -"testcli — A test CLI tool - -USAGE - $ testcli [options] [command] - -COMMANDS - deploy deploy the app - config manage configuration - help display help for command - -OPTIONS - -v, --verbose enable verbose output - --dry-run preview without executing - -h, --help display help for command" -`; diff --git a/test/core/downloader.test.ts b/test/core/downloader.test.ts index 56fa7f7..cb87416 100644 --- a/test/core/downloader.test.ts +++ b/test/core/downloader.test.ts @@ -12,6 +12,7 @@ vi.mock("node:fs/promises", () => ({ import { execFileSync } from "node:child_process"; import { access, writeFile } from "node:fs/promises"; import { AUTHSTACK_URL, downloadAuthstack } from "../../src/core/downloader.js"; +import { AUTHSTACK_ARCHIVE_DIR, AUTHSTACK_REPO } from "../../src/core/authstack.js"; const mockExecFileSync = vi.mocked(execFileSync); const mockWriteFile = vi.mocked(writeFile); @@ -26,7 +27,7 @@ beforeEach(() => { describe("AUTHSTACK_URL", () => { it("points to the unified authstack repo main branch", () => { expect(AUTHSTACK_URL).toBe( - "https://github.com/scalekit-inc/authstack/archive/refs/heads/main.tar.gz", + `https://github.com/${AUTHSTACK_REPO}/archive/refs/heads/main.tar.gz`, ); }); }); @@ -65,7 +66,7 @@ describe("downloadAuthstack", () => { "-C", "/tmp/test-dir", ]); - expect(result).toBe("/tmp/test-dir/authstack-main"); + expect(result).toBe(`/tmp/test-dir/${AUTHSTACK_ARCHIVE_DIR}`); }); it("throws when fetch fails", async () => { diff --git a/test/e2e/__snapshots__/cli.test.ts.snap b/test/e2e/__snapshots__/cli.test.ts.snap index 46ed342..be5a0e3 100644 --- a/test/e2e/__snapshots__/cli.test.ts.snap +++ b/test/e2e/__snapshots__/cli.test.ts.snap @@ -1,14 +1,14 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`CLI E2E > shows help and exits 0 1`] = ` -"scalekit — Auth stack for the agentic era +"scalekit — Authstack for devs building AI agents, MCP servers and SaaS apps USAGE $ scalekit [options] [command] COMMANDS extension|ext manage Scalekit extensions for coding tools - setup set up ScaleKit auth stacks for your coding agents + setup set up Scalekit auth stacks and skills for your coding agents OPTIONS -V, --version output the version number @@ -19,7 +19,7 @@ OPTIONS `; exports[`CLI E2E > shows setup help 1`] = ` -"scalekit setup — set up ScaleKit auth stacks for your coding agents +"scalekit setup — set up Scalekit auth stacks and skills for your coding agents USAGE $ scalekit setup [options] [command] [stack] @@ -30,7 +30,7 @@ COMMANDS OPTIONS -y, --yes skip confirmation prompts --dry-run show commands without executing - --skip-skills skip Scalekit skills installation + --skip-skills skip Scalekit skills (from Authstack) -h, --help display help for command Examples: @@ -39,5 +39,5 @@ Examples: $ scalekit setup extension cc shortcut → extension install claude $ scalekit setup codex -y skip confirmation $ scalekit setup --dry-run preview commands without running them - $ scalekit setup --skip-skills set up agents only, skip skills" + $ scalekit setup --skip-skills stacks only (skip Scalekit skills)" `; diff --git a/test/stacks/claude.test.ts b/test/stacks/claude.test.ts index 342dd70..854490a 100644 --- a/test/stacks/claude.test.ts +++ b/test/stacks/claude.test.ts @@ -15,6 +15,7 @@ vi.mock("node:fs/promises", () => ({ import { execFileSync, spawn } from "node:child_process"; import { readdir, readFile } from "node:fs/promises"; import { claudeStack } from "../../src/stacks/claude.js"; +import { AUTHSTACK_MARKETPLACE, AUTHSTACK_REPO } from "../../src/core/authstack.js"; const mockReaddir = vi.mocked(readdir); const mockReadFile = vi.mocked(readFile); @@ -71,8 +72,8 @@ describe("claudeStack.install", () => { describe("claudeStack.checkVersion", () => { const settingsWithRepo = JSON.stringify({ extraKnownMarketplaces: { - authstack: { - source: { repo: "scalekit-inc/authstack" }, + [AUTHSTACK_MARKETPLACE]: { + source: { repo: AUTHSTACK_REPO }, }, }, }); diff --git a/test/stacks/codex.test.ts b/test/stacks/codex.test.ts index 65dec81..02b739e 100644 --- a/test/stacks/codex.test.ts +++ b/test/stacks/codex.test.ts @@ -25,6 +25,7 @@ vi.mock("../../src/core/downloader.js", () => ({ import { execFileSync } from "node:child_process"; import { cp, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { downloadAuthstack } from "../../src/core/downloader.js"; +import { AUTHSTACK_ARCHIVE_DIR, AUTHSTACK_MARKETPLACE } from "../../src/core/authstack.js"; import { codexStack } from "../../src/stacks/codex.js"; const mockExecFileSync = vi.mocked(execFileSync); @@ -36,7 +37,7 @@ const mockRm = vi.mocked(rm); const mockWriteFile = vi.mocked(writeFile); const mockDownload = vi.mocked(downloadAuthstack); -const MARKETPLACE_DIR = "/home/user/.codex/marketplaces/authstack"; +const MARKETPLACE_DIR = `/home/user/.codex/marketplaces/${AUTHSTACK_MARKETPLACE}`; const PERSONAL_MARKETPLACE = "/home/user/.agents/plugins/marketplace.json"; beforeEach(() => { @@ -46,7 +47,7 @@ beforeEach(() => { mockCp.mockResolvedValue(undefined); mockRm.mockResolvedValue(undefined); mockWriteFile.mockResolvedValue(undefined); - mockDownload.mockResolvedValue("/tmp/scalekit-codex-abc/authstack-main"); + mockDownload.mockResolvedValue(`/tmp/scalekit-codex-abc/${AUTHSTACK_ARCHIVE_DIR}`); }); describe("codexStack.detect", () => { @@ -73,13 +74,13 @@ describe("codexStack.install", () => { expect(mockDownload).toHaveBeenCalledWith("/tmp/scalekit-codex-abc"); expect(mockCp).toHaveBeenCalledWith( - "/tmp/scalekit-codex-abc/authstack-main", + `/tmp/scalekit-codex-abc/${AUTHSTACK_ARCHIVE_DIR}`, MARKETPLACE_DIR, { recursive: true }, ); expect(mockWriteFile).toHaveBeenCalledWith( PERSONAL_MARKETPLACE, - expect.stringContaining('"authstack"'), + expect.stringContaining(`"${AUTHSTACK_MARKETPLACE}"`), "utf-8", ); expect(mockWriteFile).toHaveBeenCalledWith( @@ -91,14 +92,14 @@ describe("codexStack.install", () => { it("overwrites personal marketplace when it already belongs to scalekit", async () => { mockReadFile.mockResolvedValue( - JSON.stringify({ name: "authstack" }) as unknown as Buffer, + JSON.stringify({ name: AUTHSTACK_MARKETPLACE }) as unknown as Buffer, ); await codexStack.install(); expect(mockWriteFile).toHaveBeenCalledWith( PERSONAL_MARKETPLACE, - expect.stringContaining('"authstack"'), + expect.stringContaining(`"${AUTHSTACK_MARKETPLACE}"`), "utf-8", ); }); @@ -138,7 +139,7 @@ describe("codexStack.uninstall", () => { it("removes personal marketplace file when it belongs to scalekit", async () => { mockReadFile.mockResolvedValue( - JSON.stringify({ name: "authstack" }) as unknown as Buffer, + JSON.stringify({ name: AUTHSTACK_MARKETPLACE }) as unknown as Buffer, ); await codexStack.uninstall?.(); diff --git a/test/stacks/cursor.test.ts b/test/stacks/cursor.test.ts index 69c9127..b2c9fae 100644 --- a/test/stacks/cursor.test.ts +++ b/test/stacks/cursor.test.ts @@ -28,6 +28,7 @@ import { execFileSync } from "node:child_process"; import { accessSync } from "node:fs"; import { cp, mkdir, mkdtemp, rm } from "node:fs/promises"; import { downloadAuthstack } from "../../src/core/downloader.js"; +import { AUTHSTACK_ARCHIVE_DIR } from "../../src/core/authstack.js"; import { cursorStack } from "../../src/stacks/cursor.js"; const mockExecFileSync = vi.mocked(execFileSync); @@ -44,7 +45,7 @@ beforeEach(() => { mockCp.mockResolvedValue(undefined); mockRm.mockResolvedValue(undefined); mockMkdtemp.mockResolvedValue("/tmp/scalekit-cursor-abc"); - mockDownload.mockResolvedValue("/tmp/scalekit-cursor-abc/authstack-main"); + mockDownload.mockResolvedValue(`/tmp/scalekit-cursor-abc/${AUTHSTACK_ARCHIVE_DIR}`); }); describe("cursorStack.detect", () => { @@ -79,12 +80,12 @@ describe("cursorStack.install", () => { expect(mockDownload).toHaveBeenCalledWith("/tmp/scalekit-cursor-abc"); expect(mockCp).toHaveBeenCalledWith( - "/tmp/scalekit-cursor-abc/authstack-main/kits/agentkit", + `/tmp/scalekit-cursor-abc/${AUTHSTACK_ARCHIVE_DIR}/kits/agentkit`, "/home/user/.cursor/plugins/local/agentkit", { recursive: true }, ); expect(mockCp).toHaveBeenCalledWith( - "/tmp/scalekit-cursor-abc/authstack-main/kits/saaskit", + `/tmp/scalekit-cursor-abc/${AUTHSTACK_ARCHIVE_DIR}/kits/saaskit`, "/home/user/.cursor/plugins/local/saaskit", { recursive: true }, );