Skip to content
Merged
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ dist
.DS_Store
package-lock.json
.local/
docs/
docs/
plans/
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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 <tool> set up a specific tool
scalekit setup --skip-skills stacks only, skip skills

scalekit extension install <id> install by id or alias (alias: ext i)
scalekit extension uninstall <id> uninstall by id or alias (alias: ext rm)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@scalekit-inc/cli",
"version": "0.3.13",
"version": "0.3.16",
"description": "Scalekit CLI",
"type": "module",
"bin": {
Expand Down
27 changes: 13 additions & 14 deletions src/commands/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,12 @@ async function runSkillsInstall(): Promise<boolean> {
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;
}
}
Expand All @@ -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());

Expand All @@ -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)",
},
];

Expand Down Expand Up @@ -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",
Expand All @@ -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("");
}
}
Expand Down Expand Up @@ -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?`,
});
Expand Down Expand Up @@ -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",
Expand All @@ -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) => {
Expand Down
30 changes: 30 additions & 0 deletions src/core/authstack.ts
Original file line number Diff line number Diff line change
@@ -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}`,
];
}
7 changes: 4 additions & 3 deletions src/core/downloader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> {
const sourceDir = process.env.AUTHSTACK_SOURCE_DIR;
Expand All @@ -19,7 +20,7 @@ export async function downloadAuthstack(tmpDir: string): Promise<string> {

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 {
Expand Down
8 changes: 7 additions & 1 deletion src/core/skills.ts
Original file line number Diff line number Diff line change
@@ -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`;

Expand Down
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ function showBanner() {
return;
}

cfonts.say("ScaleKit", {
cfonts.say("Scalekit", {
font: "chrome",
align: "left",
colors: ["cyan", "white", "blue"],
Expand All @@ -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")
Expand Down
22 changes: 9 additions & 13 deletions src/stacks/claude.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -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"',
Expand Down
52 changes: 27 additions & 25 deletions src/stacks/codex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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,
Expand Down Expand Up @@ -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}`,
Expand All @@ -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(
Expand All @@ -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 {
Expand All @@ -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 });
}
},
Expand Down
Loading
Loading