@prajwolkc/stk 0.5.0 → 0.5.1

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 (2) hide show
  1. package/dist/commands/init.js +246 -30
  2. package/package.json +1 -1
@@ -1,15 +1,165 @@
1
1
  import { Command } from "commander";
2
2
  import chalk from "chalk";
3
- import { writeFileSync, existsSync } from "fs";
3
+ import ora from "ora";
4
+ import { writeFileSync, readFileSync, existsSync } from "fs";
4
5
  import { basename } from "path";
5
6
  import { CONFIG_FILE, KNOWN_SERVICES } from "../lib/config.js";
6
7
  import { templates, listTemplates } from "../templates/index.js";
8
+ import { ingestProject, loadBrainStore, saveBrainStore, pullFromCloud, pushToCloud } from "../services/brain.js";
7
9
  const DEPLOY_PROVIDERS = ["railway", "vercel", "fly", "render", "aws"];
10
+ /** Scan project files to detect what stack is being used */
11
+ function detectStackFromFiles() {
12
+ const detected = {};
13
+ const stack = [];
14
+ let projectType = "unknown";
15
+ // 1. Check env vars (existing behavior)
16
+ const envChecks = {
17
+ railway: ["RAILWAY_API_TOKEN"],
18
+ vercel: ["VERCEL_TOKEN"],
19
+ fly: ["FLY_API_TOKEN"],
20
+ render: ["RENDER_API_KEY"],
21
+ aws: ["AWS_ACCESS_KEY_ID"],
22
+ database: ["DATABASE_URL"],
23
+ mongodb: ["MONGODB_URL", "MONGO_URL"],
24
+ redis: ["REDIS_URL"],
25
+ supabase: ["SUPABASE_URL"],
26
+ r2: ["CLOUDFLARE_ACCOUNT_ID"],
27
+ stripe: ["STRIPE_SECRET_KEY"],
28
+ };
29
+ for (const [service, vars] of Object.entries(envChecks)) {
30
+ if (vars.some((v) => process.env[v])) {
31
+ detected[service] = true;
32
+ }
33
+ }
34
+ // 2. Scan package.json files for dependencies
35
+ const pkgPaths = [
36
+ "package.json",
37
+ "node-backend/package.json",
38
+ "backend/package.json",
39
+ "server/package.json",
40
+ "api/package.json",
41
+ "frontend/package.json",
42
+ "web/package.json",
43
+ "client/package.json",
44
+ ];
45
+ const allDeps = {};
46
+ for (const p of pkgPaths) {
47
+ if (existsSync(p)) {
48
+ try {
49
+ const pkg = JSON.parse(readFileSync(p, "utf-8"));
50
+ Object.assign(allDeps, pkg.dependencies ?? {}, pkg.devDependencies ?? {});
51
+ }
52
+ catch { /* skip */ }
53
+ }
54
+ }
55
+ // Detect framework
56
+ if (allDeps["next"]) {
57
+ stack.push("Next.js");
58
+ projectType = "fullstack";
59
+ }
60
+ else if (allDeps["react"]) {
61
+ stack.push("React");
62
+ }
63
+ if (allDeps["vue"]) {
64
+ stack.push("Vue");
65
+ }
66
+ if (allDeps["angular"] || allDeps["@angular/core"]) {
67
+ stack.push("Angular");
68
+ }
69
+ if (allDeps["express"]) {
70
+ stack.push("Express");
71
+ projectType = projectType === "unknown" ? "api" : projectType;
72
+ }
73
+ if (allDeps["fastify"]) {
74
+ stack.push("Fastify");
75
+ projectType = projectType === "unknown" ? "api" : projectType;
76
+ }
77
+ if (allDeps["hono"]) {
78
+ stack.push("Hono");
79
+ }
80
+ // Detect ORM/DB
81
+ if (allDeps["prisma"] || allDeps["@prisma/client"]) {
82
+ stack.push("Prisma");
83
+ detected.database = true;
84
+ }
85
+ if (allDeps["mongoose"]) {
86
+ stack.push("Mongoose");
87
+ detected.mongodb = true;
88
+ }
89
+ if (allDeps["typeorm"]) {
90
+ stack.push("TypeORM");
91
+ detected.database = true;
92
+ }
93
+ if (allDeps["drizzle-orm"]) {
94
+ stack.push("Drizzle");
95
+ detected.database = true;
96
+ }
97
+ if (allDeps["@supabase/supabase-js"]) {
98
+ stack.push("Supabase SDK");
99
+ detected.supabase = true;
100
+ }
101
+ // Detect billing/payments
102
+ if (allDeps["stripe"] || allDeps["@stripe/stripe-js"]) {
103
+ stack.push("Stripe");
104
+ detected.stripe = true;
105
+ }
106
+ // Detect queue/cache
107
+ if (allDeps["bullmq"] || allDeps["bull"] || allDeps["ioredis"]) {
108
+ stack.push("Redis/BullMQ");
109
+ detected.redis = true;
110
+ }
111
+ // Detect auth
112
+ if (allDeps["jsonwebtoken"])
113
+ stack.push("JWT Auth");
114
+ if (allDeps["passport"])
115
+ stack.push("Passport.js");
116
+ if (allDeps["next-auth"] || allDeps["@auth/core"])
117
+ stack.push("Auth.js");
118
+ // 3. Scan for config files that indicate deploy providers
119
+ if (existsSync("railway.json") || existsSync("railway.toml"))
120
+ detected.railway = true;
121
+ if (existsSync("vercel.json") || existsSync(".vercel"))
122
+ detected.vercel = true;
123
+ if (existsSync("fly.toml"))
124
+ detected.fly = true;
125
+ if (existsSync("render.yaml"))
126
+ detected.render = true;
127
+ if (existsSync("Dockerfile"))
128
+ stack.push("Docker");
129
+ // 4. Scan for Prisma schema
130
+ const prismaPaths = [
131
+ "prisma/schema.prisma",
132
+ "node-backend/prisma/schema.prisma",
133
+ "backend/prisma/schema.prisma",
134
+ "src/prisma/schema.prisma",
135
+ ];
136
+ for (const p of prismaPaths) {
137
+ if (existsSync(p)) {
138
+ detected.database = true;
139
+ if (!stack.includes("Prisma"))
140
+ stack.push("Prisma");
141
+ break;
142
+ }
143
+ }
144
+ // 5. Detect monorepo vs single
145
+ const hasMultiplePkgs = pkgPaths.filter(p => existsSync(p)).length > 1;
146
+ if (hasMultiplePkgs)
147
+ projectType = "fullstack";
148
+ // 6. Detect if frontend-only (static)
149
+ if (stack.length > 0 && !allDeps["express"] && !allDeps["fastify"] && !allDeps["hono"] && !allDeps["next"]) {
150
+ if (allDeps["react"] || allDeps["vue"] || allDeps["angular"]) {
151
+ if (projectType === "unknown")
152
+ projectType = "static";
153
+ }
154
+ }
155
+ return { detected, stack, projectType };
156
+ }
8
157
  export const initCommand = new Command("init")
9
- .description("Initialize stk config for the current project")
158
+ .description("Initialize stk config auto-detects stack, ingests knowledge, syncs brain")
10
159
  .option("--force", "overwrite existing config")
11
160
  .option("-t, --template <name>", `use a starter template (${listTemplates().join(", ")})`)
12
161
  .option("--list-templates", "show available templates")
162
+ .option("--skip-brain", "skip brain ingest and sync")
13
163
  .action(async (opts) => {
14
164
  // List templates
15
165
  if (opts.listTemplates) {
@@ -52,31 +202,24 @@ export const initCommand = new Command("init")
52
202
  console.log(` ${icon} ${enabled ? chalk.white(name) : chalk.dim(name)}`);
53
203
  }
54
204
  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();
205
+ // Run brain steps even for template init
206
+ if (!opts.skipBrain) {
207
+ await runBrainSteps(projectName);
208
+ }
60
209
  return;
61
210
  }
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;
211
+ // ─── Smart Auto-detect Init ───
212
+ console.log();
213
+ const detectSpinner = ora(" Scanning project...").start();
214
+ const { detected, stack, projectType } = detectStackFromFiles();
215
+ detectSpinner.succeed(" Project scanned");
216
+ // Show detected stack
217
+ if (stack.length > 0) {
218
+ console.log();
219
+ console.log(chalk.bold(" Detected stack:"));
220
+ console.log(` ${chalk.cyan(stack.join(" + "))}`);
221
+ if (projectType !== "unknown") {
222
+ console.log(` ${chalk.dim(`Project type: ${projectType}`)}`);
80
223
  }
81
224
  }
82
225
  const config = {
@@ -94,18 +237,91 @@ export const initCommand = new Command("init")
94
237
  console.log();
95
238
  console.log(` ${chalk.green("✓")} Created ${chalk.bold(CONFIG_FILE)}`);
96
239
  console.log();
97
- console.log(chalk.bold(" Detected services:"));
240
+ console.log(chalk.bold(" Services:"));
98
241
  const serviceNames = Object.entries(config.services);
99
242
  for (const [name, enabled] of serviceNames) {
100
243
  const icon = enabled ? chalk.green("✓") : chalk.dim("○");
101
244
  const label = enabled
102
245
  ? chalk.white(name)
103
- : chalk.dim(`${name} (not detected — enable in config)`);
246
+ : chalk.dim(name);
104
247
  console.log(` ${icon} ${label}`);
105
248
  }
249
+ // ─── Brain Steps ───
250
+ if (!opts.skipBrain) {
251
+ await runBrainSteps(projectName);
252
+ }
106
253
  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.`));
254
+ console.log(chalk.dim(` Run ${chalk.white("stk health")} to verify services.`));
110
255
  console.log();
111
256
  });
257
+ /** Ingest project, pull cloud knowledge, push new knowledge */
258
+ async function runBrainSteps(projectName) {
259
+ console.log();
260
+ console.log(chalk.bold(" Brain setup:"));
261
+ // Step 1: Ingest project
262
+ const ingestSpinner = ora(" Scanning project architecture...").start();
263
+ try {
264
+ const { entries, filesScanned } = ingestProject(process.cwd());
265
+ if (entries.length > 0) {
266
+ const store = loadBrainStore();
267
+ store.projects[projectName] = {
268
+ ingestedAt: new Date().toISOString(),
269
+ projectPath: process.cwd(),
270
+ entries,
271
+ };
272
+ saveBrainStore(store);
273
+ ingestSpinner.succeed(` Ingested ${chalk.white(entries.length)} knowledge entries from ${filesScanned.length} files`);
274
+ }
275
+ else {
276
+ ingestSpinner.warn(" No project files found to ingest");
277
+ }
278
+ }
279
+ catch {
280
+ ingestSpinner.warn(" Ingest skipped (no recognizable files)");
281
+ }
282
+ // Step 2: Pull cloud brain (learn from other projects)
283
+ const pullSpinner = ora(" Pulling knowledge from cloud brain...").start();
284
+ try {
285
+ const pullResult = await pullFromCloud();
286
+ if (pullResult.errors.length > 0) {
287
+ pullSpinner.warn(" Cloud brain not available (set SUPABASE_URL + SUPABASE_SERVICE_KEY to enable)");
288
+ }
289
+ else if (pullResult.pulled > 0) {
290
+ pullSpinner.succeed(` Pulled ${chalk.white(pullResult.pulled)} entries from cloud (learned from other projects)`);
291
+ }
292
+ else {
293
+ pullSpinner.succeed(" Cloud brain in sync");
294
+ }
295
+ }
296
+ catch {
297
+ pullSpinner.warn(" Cloud sync skipped (no connection)");
298
+ }
299
+ // Step 3: Push new knowledge to cloud
300
+ const pushSpinner = ora(" Sharing knowledge to cloud brain...").start();
301
+ try {
302
+ const pushResult = await pushToCloud();
303
+ if (pushResult.errors.length > 0) {
304
+ pushSpinner.warn(" Push skipped");
305
+ }
306
+ else if (pushResult.pushed > 0) {
307
+ pushSpinner.succeed(` Pushed ${chalk.white(pushResult.pushed)} entries to cloud`);
308
+ }
309
+ else {
310
+ pushSpinner.succeed(" All knowledge already shared");
311
+ }
312
+ }
313
+ catch {
314
+ pushSpinner.warn(" Push skipped (no connection)");
315
+ }
316
+ // Show what we know from other projects
317
+ const store = loadBrainStore();
318
+ const otherProjects = Object.keys(store.projects).filter(p => p !== projectName);
319
+ if (otherProjects.length > 0) {
320
+ console.log();
321
+ console.log(chalk.bold(" Knowledge available from other projects:"));
322
+ for (const proj of otherProjects) {
323
+ const p = store.projects[proj];
324
+ console.log(` ${chalk.green("●")} ${chalk.white(proj)} — ${p.entries.length} entries`);
325
+ }
326
+ }
327
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prajwolkc/stk",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "One CLI to deploy, monitor, debug, and learn about your entire stack. Infrastructure monitoring, knowledge base brain, deploy watching, and GitHub issues — all from one command.",
5
5
  "type": "module",
6
6
  "license": "MIT",