@prajwolkc/stk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +114 -0
  3. package/dist/commands/deploy.d.ts +2 -0
  4. package/dist/commands/deploy.js +152 -0
  5. package/dist/commands/env.d.ts +2 -0
  6. package/dist/commands/env.js +136 -0
  7. package/dist/commands/health.d.ts +2 -0
  8. package/dist/commands/health.js +77 -0
  9. package/dist/commands/init.d.ts +2 -0
  10. package/dist/commands/init.js +111 -0
  11. package/dist/commands/logs.d.ts +2 -0
  12. package/dist/commands/logs.js +151 -0
  13. package/dist/commands/status.d.ts +2 -0
  14. package/dist/commands/status.js +130 -0
  15. package/dist/commands/todo.d.ts +2 -0
  16. package/dist/commands/todo.js +187 -0
  17. package/dist/index.d.ts +2 -0
  18. package/dist/index.js +22 -0
  19. package/dist/lib/config.d.ts +36 -0
  20. package/dist/lib/config.js +102 -0
  21. package/dist/services/aws.d.ts +2 -0
  22. package/dist/services/aws.js +32 -0
  23. package/dist/services/checker.d.ts +10 -0
  24. package/dist/services/checker.js +20 -0
  25. package/dist/services/database.d.ts +2 -0
  26. package/dist/services/database.js +27 -0
  27. package/dist/services/fly.d.ts +2 -0
  28. package/dist/services/fly.js +17 -0
  29. package/dist/services/mongodb.d.ts +2 -0
  30. package/dist/services/mongodb.js +25 -0
  31. package/dist/services/r2.d.ts +2 -0
  32. package/dist/services/r2.js +20 -0
  33. package/dist/services/railway.d.ts +2 -0
  34. package/dist/services/railway.js +26 -0
  35. package/dist/services/redis.d.ts +2 -0
  36. package/dist/services/redis.js +34 -0
  37. package/dist/services/registry.d.ts +4 -0
  38. package/dist/services/registry.js +37 -0
  39. package/dist/services/render.d.ts +2 -0
  40. package/dist/services/render.js +19 -0
  41. package/dist/services/stripe.d.ts +2 -0
  42. package/dist/services/stripe.js +21 -0
  43. package/dist/services/supabase.d.ts +2 -0
  44. package/dist/services/supabase.js +24 -0
  45. package/dist/services/vercel.d.ts +2 -0
  46. package/dist/services/vercel.js +35 -0
  47. package/dist/templates/index.d.ts +8 -0
  48. package/dist/templates/index.js +105 -0
  49. package/package.json +55 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 prajwolkc
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,114 @@
1
+ # stk
2
+
3
+ One CLI to deploy, monitor, and debug your entire stack.
4
+
5
+ Stop opening 5 dashboards. `stk` checks your services, watches your deploys, syncs your env vars, tails your logs, and manages your issues — all from one command.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install -g stk-cli
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```bash
16
+ cd my-project
17
+ stk init # auto-detect your services
18
+ stk init --template saas # or use a starter template
19
+ stk health # check everything
20
+ stk status # one-line summary of your whole stack
21
+ ```
22
+
23
+ ## Commands
24
+
25
+ | Command | Description |
26
+ |---------|-------------|
27
+ | `stk init` | Initialize config (auto-detect or `--template saas\|api\|fullstack\|static\|fly\|aws`) |
28
+ | `stk status` | One-line summary: git, services, deploys, issues |
29
+ | `stk health` | Health check all configured services |
30
+ | `stk deploy` | Git push + watch Railway/Vercel/Fly deploys |
31
+ | `stk env pull` | Pull env vars from Vercel + Railway into `.env.pulled` |
32
+ | `stk env diff` | Show what's in your local `.env` |
33
+ | `stk logs` | Tail Railway deployment logs |
34
+ | `stk todo ls` | List open GitHub issues |
35
+ | `stk todo add "title"` | Create a GitHub issue |
36
+ | `stk todo close 42` | Close an issue |
37
+
38
+ ## Supported Services
39
+
40
+ **Deploy providers:** Railway, Vercel, Fly.io, Render, AWS
41
+ **Databases:** PostgreSQL, MongoDB, Redis, Supabase
42
+ **Storage & billing:** Cloudflare R2, Stripe
43
+
44
+ ## Configuration
45
+
46
+ `stk init` creates a `stk.config.json` in your project root:
47
+
48
+ ```json
49
+ {
50
+ "name": "my-saas",
51
+ "services": {
52
+ "vercel": true,
53
+ "railway": true,
54
+ "database": true,
55
+ "redis": true,
56
+ "stripe": true,
57
+ "r2": true
58
+ },
59
+ "deploy": {
60
+ "branch": "main",
61
+ "providers": ["vercel", "railway"]
62
+ }
63
+ }
64
+ ```
65
+
66
+ Only configured services are checked — no noise from services you don't use.
67
+
68
+ If no config file exists, `stk` auto-detects services from your environment variables.
69
+
70
+ ## Templates
71
+
72
+ ```bash
73
+ stk init --list-templates
74
+ ```
75
+
76
+ | Template | Stack |
77
+ |----------|-------|
78
+ | `saas` | Vercel + Railway + PostgreSQL + Redis + Stripe + R2 |
79
+ | `api` | Railway + PostgreSQL + Redis |
80
+ | `fullstack` | Vercel + Railway + Supabase + Stripe |
81
+ | `static` | Vercel only |
82
+ | `fly` | Fly.io + PostgreSQL + Redis |
83
+ | `aws` | AWS + PostgreSQL + Redis |
84
+
85
+ ## Environment Variables
86
+
87
+ Set the tokens for your services:
88
+
89
+ ```bash
90
+ # Deploy providers
91
+ RAILWAY_API_TOKEN=
92
+ VERCEL_TOKEN=
93
+ FLY_API_TOKEN=
94
+ RENDER_API_KEY=
95
+ AWS_ACCESS_KEY_ID= / AWS_SECRET_ACCESS_KEY=
96
+
97
+ # Databases
98
+ DATABASE_URL=
99
+ MONGODB_URL=
100
+ REDIS_URL=
101
+ SUPABASE_URL= / SUPABASE_SERVICE_KEY=
102
+
103
+ # Storage & billing
104
+ CLOUDFLARE_ACCOUNT_ID= / CLOUDFLARE_API_TOKEN=
105
+ STRIPE_SECRET_KEY=
106
+
107
+ # GitHub (for stk todo)
108
+ GITHUB_TOKEN=
109
+ GITHUB_REPO=owner/repo # or auto-detected from git remote
110
+ ```
111
+
112
+ ## License
113
+
114
+ MIT
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare const deployCommand: Command;
@@ -0,0 +1,152 @@
1
+ import { Command } from "commander";
2
+ import chalk from "chalk";
3
+ import ora from "ora";
4
+ import { execSync } from "child_process";
5
+ import { loadConfig, enabledServices } from "../lib/config.js";
6
+ const DEPLOY_PROVIDERS = {
7
+ vercel: watchVercel,
8
+ railway: watchRailway,
9
+ };
10
+ export const deployCommand = new Command("deploy")
11
+ .description("Push to branch and watch deploy providers")
12
+ .option("-b, --branch <branch>", "branch to push (default: from config or main)")
13
+ .option("--skip-push", "skip git push, just watch deploys")
14
+ .action(async (opts) => {
15
+ const config = loadConfig();
16
+ const branch = opts.branch ?? config.deploy?.branch ?? "main";
17
+ // Step 1: Git push
18
+ if (!opts.skipPush) {
19
+ const pushSpinner = ora("Pushing to remote...").start();
20
+ try {
21
+ const currentBranch = execSync("git rev-parse --abbrev-ref HEAD", {
22
+ encoding: "utf-8",
23
+ }).trim();
24
+ if (currentBranch !== branch) {
25
+ pushSpinner.warn(`You're on ${chalk.yellow(currentBranch)}, not ${chalk.green(branch)}`);
26
+ console.log(chalk.dim(` Run: git checkout ${branch} && git merge ${currentBranch}`));
27
+ return;
28
+ }
29
+ execSync(`git push origin ${branch}`, {
30
+ encoding: "utf-8",
31
+ stdio: "pipe",
32
+ });
33
+ pushSpinner.succeed(`Pushed to ${chalk.green(`origin/${branch}`)}`);
34
+ }
35
+ catch (err) {
36
+ pushSpinner.fail("Git push failed");
37
+ console.error(chalk.red(` ${err.message}`));
38
+ process.exitCode = 1;
39
+ return;
40
+ }
41
+ }
42
+ // Step 2: Watch deploys — only providers enabled in config
43
+ console.log();
44
+ console.log(chalk.bold(` ${config.name} — Watching deploys...`));
45
+ console.log(chalk.dim(" ─────────────────────────────────────────"));
46
+ const providers = config.deploy?.providers ?? enabledServices(config).filter((s) => s in DEPLOY_PROVIDERS);
47
+ const watchers = [];
48
+ for (const provider of providers) {
49
+ const watcher = DEPLOY_PROVIDERS[provider];
50
+ if (watcher) {
51
+ watchers.push(watcher());
52
+ }
53
+ else {
54
+ console.log(chalk.dim(` ○ ${provider} no deploy watcher available`));
55
+ }
56
+ }
57
+ if (watchers.length === 0) {
58
+ console.log(chalk.yellow(" No deploy providers configured."));
59
+ console.log(chalk.dim(` Add "deploy.providers" to stk.config.json`));
60
+ }
61
+ else {
62
+ await Promise.all(watchers);
63
+ }
64
+ console.log();
65
+ });
66
+ async function watchVercel() {
67
+ const token = process.env.VERCEL_TOKEN;
68
+ const spinner = ora({ text: "Vercel deploying...", indent: 2 }).start();
69
+ for (let i = 0; i < 60; i++) {
70
+ await sleep(5000);
71
+ try {
72
+ const res = await fetch("https://api.vercel.com/v6/deployments?limit=1", {
73
+ headers: { Authorization: `Bearer ${token}` },
74
+ });
75
+ const data = (await res.json());
76
+ const dep = data.deployments?.[0];
77
+ if (!dep)
78
+ continue;
79
+ const state = dep.readyState ?? dep.state;
80
+ if (state === "READY") {
81
+ spinner.succeed(`Vercel ${chalk.green("ready")} — ${chalk.dim(dep.url)}`);
82
+ return;
83
+ }
84
+ else if (state === "ERROR") {
85
+ spinner.fail(`Vercel deploy ${chalk.red("failed")}`);
86
+ process.exitCode = 1;
87
+ return;
88
+ }
89
+ spinner.text = `Vercel: ${state.toLowerCase()}...`;
90
+ }
91
+ catch {
92
+ // retry
93
+ }
94
+ }
95
+ spinner.warn("Vercel: timed out waiting (5 min)");
96
+ }
97
+ async function watchRailway() {
98
+ const token = process.env.RAILWAY_API_TOKEN;
99
+ const spinner = ora({ text: "Railway deploying...", indent: 2 }).start();
100
+ for (let i = 0; i < 60; i++) {
101
+ await sleep(5000);
102
+ try {
103
+ const res = await fetch("https://backboard.railway.com/graphql/v2", {
104
+ method: "POST",
105
+ headers: {
106
+ Authorization: `Bearer ${token}`,
107
+ "Content-Type": "application/json",
108
+ },
109
+ body: JSON.stringify({
110
+ query: `{
111
+ me {
112
+ projects(first: 1) {
113
+ edges {
114
+ node {
115
+ deployments(first: 1) {
116
+ edges {
117
+ node { status }
118
+ }
119
+ }
120
+ }
121
+ }
122
+ }
123
+ }
124
+ }`,
125
+ }),
126
+ });
127
+ const data = (await res.json());
128
+ const deployment = data.data?.me?.projects?.edges?.[0]?.node?.deployments?.edges?.[0]
129
+ ?.node;
130
+ if (!deployment)
131
+ continue;
132
+ const status = deployment.status;
133
+ if (status === "SUCCESS") {
134
+ spinner.succeed(`Railway ${chalk.green("deployed")}`);
135
+ return;
136
+ }
137
+ else if (status === "FAILED" || status === "CRASHED") {
138
+ spinner.fail(`Railway deploy ${chalk.red(status.toLowerCase())}`);
139
+ process.exitCode = 1;
140
+ return;
141
+ }
142
+ spinner.text = `Railway: ${status.toLowerCase()}...`;
143
+ }
144
+ catch {
145
+ // retry
146
+ }
147
+ }
148
+ spinner.warn("Railway: timed out waiting (5 min)");
149
+ }
150
+ function sleep(ms) {
151
+ return new Promise((resolve) => setTimeout(resolve, ms));
152
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare const envCommand: Command;
@@ -0,0 +1,136 @@
1
+ import { Command } from "commander";
2
+ import chalk from "chalk";
3
+ import ora from "ora";
4
+ import { writeFileSync, readFileSync, existsSync } from "fs";
5
+ export const envCommand = new Command("env")
6
+ .description("Sync environment variables across services");
7
+ envCommand
8
+ .command("pull")
9
+ .description("Pull env vars from Vercel and Railway into a local .env file")
10
+ .option("-o, --output <file>", "output file", ".env.pulled")
11
+ .action(async (opts) => {
12
+ const vars = {};
13
+ const spinner = ora("Pulling env vars...").start();
14
+ // Pull from Vercel
15
+ if (process.env.VERCEL_TOKEN) {
16
+ spinner.text = "Pulling from Vercel...";
17
+ try {
18
+ const projectId = process.env.VERCEL_PROJECT_ID;
19
+ const url = projectId
20
+ ? `https://api.vercel.com/v9/projects/${projectId}/env`
21
+ : "https://api.vercel.com/v9/projects";
22
+ if (!projectId) {
23
+ spinner.warn("Vercel: set VERCEL_PROJECT_ID to pull env vars (skipping)");
24
+ }
25
+ else {
26
+ const res = await fetch(url, {
27
+ headers: { Authorization: `Bearer ${process.env.VERCEL_TOKEN}` },
28
+ });
29
+ if (!res.ok)
30
+ throw new Error(`HTTP ${res.status}`);
31
+ const data = (await res.json());
32
+ const envVars = data.envs ?? [];
33
+ for (const env of envVars) {
34
+ if (env.value) {
35
+ vars[env.key] = { value: env.value, source: "vercel" };
36
+ }
37
+ }
38
+ console.log(chalk.dim(` Vercel: pulled ${envVars.length} variables`));
39
+ }
40
+ }
41
+ catch (err) {
42
+ console.log(chalk.yellow(` Vercel: ${err.message}`));
43
+ }
44
+ }
45
+ else {
46
+ console.log(chalk.dim(" ○ Vercel skipped (VERCEL_TOKEN not set)"));
47
+ }
48
+ // Pull from Railway
49
+ if (process.env.RAILWAY_API_TOKEN) {
50
+ spinner.text = "Pulling from Railway...";
51
+ try {
52
+ const projectId = process.env.RAILWAY_PROJECT_ID;
53
+ const environmentId = process.env.RAILWAY_ENVIRONMENT_ID;
54
+ const serviceId = process.env.RAILWAY_SERVICE_ID;
55
+ if (!projectId || !environmentId || !serviceId) {
56
+ console.log(chalk.dim(" ○ Railway: set RAILWAY_PROJECT_ID, RAILWAY_ENVIRONMENT_ID, RAILWAY_SERVICE_ID to pull env vars"));
57
+ }
58
+ else {
59
+ const res = await fetch("https://backboard.railway.com/graphql/v2", {
60
+ method: "POST",
61
+ headers: {
62
+ Authorization: `Bearer ${process.env.RAILWAY_API_TOKEN}`,
63
+ "Content-Type": "application/json",
64
+ },
65
+ body: JSON.stringify({
66
+ query: `query {
67
+ variables(
68
+ projectId: "${projectId}",
69
+ environmentId: "${environmentId}",
70
+ serviceId: "${serviceId}"
71
+ )
72
+ }`,
73
+ }),
74
+ });
75
+ if (!res.ok)
76
+ throw new Error(`HTTP ${res.status}`);
77
+ const data = (await res.json());
78
+ const railwayVars = data.data?.variables ?? {};
79
+ for (const [key, value] of Object.entries(railwayVars)) {
80
+ if (!vars[key]) {
81
+ vars[key] = { value: value, source: "railway" };
82
+ }
83
+ }
84
+ console.log(chalk.dim(` Railway: pulled ${Object.keys(railwayVars).length} variables`));
85
+ }
86
+ }
87
+ catch (err) {
88
+ console.log(chalk.yellow(` Railway: ${err.message}`));
89
+ }
90
+ }
91
+ else {
92
+ console.log(chalk.dim(" ○ Railway skipped (RAILWAY_API_TOKEN not set)"));
93
+ }
94
+ spinner.stop();
95
+ // Write to file
96
+ const lines = Object.entries(vars)
97
+ .sort(([a], [b]) => a.localeCompare(b))
98
+ .map(([key, { value, source }]) => `# source: ${source}\n${key}=${value}`);
99
+ if (lines.length === 0) {
100
+ console.log(chalk.yellow("\n No env vars pulled."));
101
+ return;
102
+ }
103
+ writeFileSync(opts.output, lines.join("\n\n") + "\n");
104
+ console.log(`\n ${chalk.green("✓")} Wrote ${Object.keys(vars).length} variables to ${chalk.bold(opts.output)}`);
105
+ });
106
+ envCommand
107
+ .command("diff")
108
+ .description("Compare local .env with remote services")
109
+ .option("-f, --file <file>", "local env file to compare", ".env")
110
+ .action(async (opts) => {
111
+ if (!existsSync(opts.file)) {
112
+ console.log(chalk.red(` File not found: ${opts.file}`));
113
+ process.exitCode = 1;
114
+ return;
115
+ }
116
+ const local = parseEnvFile(readFileSync(opts.file, "utf-8"));
117
+ console.log(`\n Local ${chalk.bold(opts.file)}: ${Object.keys(local).length} variables`);
118
+ console.log(chalk.dim(" ─────────────────────────────────────────"));
119
+ for (const [key] of Object.entries(local).sort(([a], [b]) => a.localeCompare(b))) {
120
+ console.log(` ${chalk.dim("•")} ${key}`);
121
+ }
122
+ console.log();
123
+ });
124
+ function parseEnvFile(content) {
125
+ const vars = {};
126
+ for (const line of content.split("\n")) {
127
+ const trimmed = line.trim();
128
+ if (!trimmed || trimmed.startsWith("#"))
129
+ continue;
130
+ const eqIdx = trimmed.indexOf("=");
131
+ if (eqIdx === -1)
132
+ continue;
133
+ vars[trimmed.slice(0, eqIdx)] = trimmed.slice(eqIdx + 1);
134
+ }
135
+ return vars;
136
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare const healthCommand: Command;
@@ -0,0 +1,77 @@
1
+ import { Command } from "commander";
2
+ import chalk from "chalk";
3
+ import ora from "ora";
4
+ import { loadConfig, enabledServices } from "../lib/config.js";
5
+ import { getChecker, allCheckerNames } from "../services/registry.js";
6
+ const STATUS_ICON = {
7
+ healthy: chalk.green("✓"),
8
+ degraded: chalk.yellow("~"),
9
+ down: chalk.red("✗"),
10
+ skipped: chalk.dim("○"),
11
+ };
12
+ const STATUS_COLOR = {
13
+ healthy: chalk.green,
14
+ degraded: chalk.yellow,
15
+ down: chalk.red,
16
+ skipped: chalk.dim,
17
+ };
18
+ export const healthCommand = new Command("health")
19
+ .description("Check the health of all connected services")
20
+ .option("-v, --verbose", "Show latency and extra detail")
21
+ .option("-a, --all", "Check all known services, not just configured ones")
22
+ .action(async (opts) => {
23
+ const config = loadConfig();
24
+ const spinner = ora("Checking services...").start();
25
+ const serviceList = opts.all
26
+ ? allCheckerNames()
27
+ : enabledServices(config);
28
+ if (serviceList.length === 0) {
29
+ spinner.stop();
30
+ console.log();
31
+ console.log(chalk.yellow(" No services configured."));
32
+ console.log(chalk.dim(` Run ${chalk.white("stk init")} to set up your project, or ${chalk.white("stk health --all")} to check everything.`));
33
+ console.log();
34
+ return;
35
+ }
36
+ const checks = serviceList.map((name) => {
37
+ const checker = getChecker(name);
38
+ if (!checker) {
39
+ return Promise.resolve({
40
+ name,
41
+ status: "skipped",
42
+ detail: `unknown service "${name}"`,
43
+ });
44
+ }
45
+ return checker();
46
+ });
47
+ const results = await Promise.all(checks);
48
+ spinner.stop();
49
+ console.log();
50
+ console.log(chalk.bold(` ${config.name} — Service Health`));
51
+ console.log(chalk.dim(" ─────────────────────────────────────────"));
52
+ for (const r of results) {
53
+ const icon = STATUS_ICON[r.status];
54
+ const name = r.name.padEnd(16);
55
+ const latencyStr = opts.verbose && r.latency != null ? chalk.dim(` ${r.latency}ms`) : "";
56
+ const detail = r.detail ? chalk.dim(` ${r.detail}`) : "";
57
+ console.log(` ${icon} ${STATUS_COLOR[r.status](name)}${latencyStr}${detail}`);
58
+ }
59
+ console.log();
60
+ const down = results.filter((r) => r.status === "down");
61
+ const healthy = results.filter((r) => r.status === "healthy");
62
+ const skipped = results.filter((r) => r.status === "skipped");
63
+ if (down.length > 0) {
64
+ console.log(chalk.red(` ${down.length} service${down.length > 1 ? "s" : ""} down`));
65
+ process.exitCode = 1;
66
+ }
67
+ else if (healthy.length === 0) {
68
+ console.log(chalk.yellow(" No services reachable — check your env vars"));
69
+ }
70
+ else {
71
+ console.log(chalk.green(` All ${healthy.length} configured service${healthy.length > 1 ? "s" : ""} healthy`));
72
+ }
73
+ if (skipped.length > 0 && opts.verbose) {
74
+ console.log(chalk.dim(` ${skipped.length} skipped (missing env vars)`));
75
+ }
76
+ console.log();
77
+ });
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare const initCommand: Command;
@@ -0,0 +1,111 @@
1
+ import { Command } from "commander";
2
+ import chalk from "chalk";
3
+ import { writeFileSync, existsSync } from "fs";
4
+ import { basename } from "path";
5
+ import { CONFIG_FILE, KNOWN_SERVICES } from "../lib/config.js";
6
+ import { templates, listTemplates } from "../templates/index.js";
7
+ const DEPLOY_PROVIDERS = ["railway", "vercel", "fly", "render", "aws"];
8
+ export const initCommand = new Command("init")
9
+ .description("Initialize stk config for the current project")
10
+ .option("--force", "overwrite existing config")
11
+ .option("-t, --template <name>", `use a starter template (${listTemplates().join(", ")})`)
12
+ .option("--list-templates", "show available templates")
13
+ .action(async (opts) => {
14
+ // List templates
15
+ if (opts.listTemplates) {
16
+ console.log();
17
+ console.log(chalk.bold(" Available Templates"));
18
+ console.log(chalk.dim(" ─────────────────────────────────────────"));
19
+ for (const [key, tpl] of Object.entries(templates)) {
20
+ console.log(` ${chalk.green(key.padEnd(12))} ${tpl.name}`);
21
+ console.log(` ${" ".repeat(12)} ${chalk.dim(tpl.description)}`);
22
+ }
23
+ console.log();
24
+ console.log(chalk.dim(` Usage: stk init --template saas`));
25
+ console.log();
26
+ return;
27
+ }
28
+ if (existsSync(CONFIG_FILE) && !opts.force) {
29
+ console.log(chalk.yellow(` ${CONFIG_FILE} already exists. Use --force to overwrite.`));
30
+ return;
31
+ }
32
+ const projectName = basename(process.cwd());
33
+ // Template-based init
34
+ if (opts.template) {
35
+ const tpl = templates[opts.template];
36
+ if (!tpl) {
37
+ console.log(chalk.red(` Unknown template: "${opts.template}"`));
38
+ console.log(chalk.dim(` Available: ${listTemplates().join(", ")}`));
39
+ return;
40
+ }
41
+ const config = {
42
+ ...tpl.config,
43
+ name: projectName,
44
+ };
45
+ writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n");
46
+ console.log();
47
+ console.log(` ${chalk.green("✓")} Created ${chalk.bold(CONFIG_FILE)} from ${chalk.cyan(tpl.name)} template`);
48
+ console.log();
49
+ console.log(chalk.bold(" Services:"));
50
+ for (const [name, enabled] of Object.entries(config.services)) {
51
+ const icon = enabled ? chalk.green("✓") : chalk.dim("○");
52
+ console.log(` ${icon} ${enabled ? chalk.white(name) : chalk.dim(name)}`);
53
+ }
54
+ console.log();
55
+ console.log(chalk.dim(` Deploy branch: ${config.deploy?.branch ?? "main"}`));
56
+ console.log(chalk.dim(` Deploy providers: ${config.deploy?.providers?.join(", ") || "none"}`));
57
+ console.log();
58
+ console.log(chalk.dim(` Set your env vars, then run ${chalk.white("stk health")} to verify.`));
59
+ console.log();
60
+ return;
61
+ }
62
+ // Auto-detect init
63
+ const detected = {};
64
+ const envChecks = {
65
+ railway: ["RAILWAY_API_TOKEN"],
66
+ vercel: ["VERCEL_TOKEN"],
67
+ fly: ["FLY_API_TOKEN"],
68
+ render: ["RENDER_API_KEY"],
69
+ aws: ["AWS_ACCESS_KEY_ID"],
70
+ database: ["DATABASE_URL"],
71
+ mongodb: ["MONGODB_URL", "MONGO_URL"],
72
+ redis: ["REDIS_URL"],
73
+ supabase: ["SUPABASE_URL"],
74
+ r2: ["CLOUDFLARE_ACCOUNT_ID"],
75
+ stripe: ["STRIPE_SECRET_KEY"],
76
+ };
77
+ for (const [service, vars] of Object.entries(envChecks)) {
78
+ if (vars.some((v) => process.env[v])) {
79
+ detected[service] = true;
80
+ }
81
+ }
82
+ const config = {
83
+ name: projectName,
84
+ services: {
85
+ ...Object.fromEntries(KNOWN_SERVICES.map((s) => [s, false])),
86
+ ...detected,
87
+ },
88
+ deploy: {
89
+ branch: "main",
90
+ providers: Object.keys(detected).filter((s) => DEPLOY_PROVIDERS.includes(s)),
91
+ },
92
+ };
93
+ writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n");
94
+ console.log();
95
+ console.log(` ${chalk.green("✓")} Created ${chalk.bold(CONFIG_FILE)}`);
96
+ console.log();
97
+ console.log(chalk.bold(" Detected services:"));
98
+ const serviceNames = Object.entries(config.services);
99
+ for (const [name, enabled] of serviceNames) {
100
+ const icon = enabled ? chalk.green("✓") : chalk.dim("○");
101
+ const label = enabled
102
+ ? chalk.white(name)
103
+ : chalk.dim(`${name} (not detected — enable in config)`);
104
+ console.log(` ${icon} ${label}`);
105
+ }
106
+ console.log();
107
+ console.log(chalk.dim(` Edit ${CONFIG_FILE} to enable/disable services or add config.`));
108
+ console.log(chalk.dim(` Or try: ${chalk.white("stk init --template saas")} for a pre-configured stack.`));
109
+ console.log(chalk.dim(` Then run ${chalk.white("stk health")} to verify.`));
110
+ console.log();
111
+ });
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare const logsCommand: Command;