algocoach 0.1.0 → 0.1.2

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.
package/README.md CHANGED
@@ -21,8 +21,8 @@ algocoach start
21
21
 
22
22
  ## Setup
23
23
 
24
- 1. Run `algocoach start` — creates `.env` in the current directory
25
- 2. Edit `.env` — uncomment and set at least one API key:
24
+ 1. Run `algocoach start` — creates `~/.algocoach/.env`
25
+ 2. Edit `~/.algocoach/.env` — uncomment and set at least one API key:
26
26
 
27
27
  ```env
28
28
  AI_PROVIDER=google
@@ -88,7 +88,7 @@ src/ — React frontend (Vite + Tailwind)
88
88
  ## Data
89
89
 
90
90
  - **Database**: `~/.algocoach/data.db` — SQLite, created on first use
91
- - **Config**: `.env` in the working directory
91
+ - **Config**: `~/.algocoach/.env` API keys and settings
92
92
 
93
93
  ## AI Providers
94
94
 
package/cli/index.ts CHANGED
@@ -2,7 +2,8 @@
2
2
  import path from "path"
3
3
  import fs from "fs"
4
4
 
5
- const ENV_PATH = path.resolve(".env")
5
+ const CONFIG_DIR = path.join(process.env.HOME || process.env.USERPROFILE || ".", ".algocoach")
6
+ const ENV_PATH = path.join(CONFIG_DIR, ".env")
6
7
 
7
8
  function envTemplate() {
8
9
  return [
@@ -23,11 +24,27 @@ function envTemplate() {
23
24
  ].join("\n") + "\n"
24
25
  }
25
26
 
27
+ function loadEnv() {
28
+ if (!fs.existsSync(ENV_PATH)) return
29
+ const lines = fs.readFileSync(ENV_PATH, "utf-8").split("\n")
30
+ for (const line of lines) {
31
+ const trimmed = line.trim()
32
+ if (!trimmed || trimmed.startsWith("#")) continue
33
+ const eqIdx = trimmed.indexOf("=")
34
+ if (eqIdx === -1) continue
35
+ const key = trimmed.slice(0, eqIdx).trim()
36
+ const val = trimmed.slice(eqIdx + 1).trim()
37
+ if (key && val) process.env[key] = val
38
+ }
39
+ }
40
+
26
41
  function hasAnyKey(): boolean {
27
42
  return !!(process.env.GEMINI_API_KEY || process.env.GROQ_API_KEY || process.env.NVIDIA_API_KEY)
28
43
  }
29
44
 
30
45
  async function cmdInit() {
46
+ fs.mkdirSync(CONFIG_DIR, { recursive: true })
47
+
31
48
  if (!fs.existsSync(ENV_PATH)) {
32
49
  fs.writeFileSync(ENV_PATH, envTemplate())
33
50
  console.log(`Created ${ENV_PATH}`)
@@ -47,31 +64,45 @@ Then run: algocoach start
47
64
  }
48
65
 
49
66
  async function cmdStart() {
67
+ fs.mkdirSync(CONFIG_DIR, { recursive: true })
68
+
50
69
  if (!fs.existsSync(ENV_PATH)) {
51
- console.log("No .env found. Creating one...")
70
+ console.log("No config found. Creating one...")
52
71
  fs.writeFileSync(ENV_PATH, envTemplate())
53
72
  console.log(`Created ${ENV_PATH}`)
54
73
  console.log("Edit it with your API keys, then run 'algocoach start' again.\n")
55
74
  return
56
75
  }
57
76
 
77
+ loadEnv()
78
+
58
79
  if (!hasAnyKey()) {
59
- console.error("No AI API key configured in .env. Add at least one key and try again.")
80
+ console.error("No AI API key configured. Add at least one key to " + ENV_PATH)
60
81
  process.exit(1)
61
82
  }
62
83
 
63
- const port = parseInt(process.env.PORT || "3000", 10)
84
+ const preferredPort = parseInt(process.env.PORT || "3000", 10)
64
85
 
65
86
  process.env.LOCAL_DEV = "true"
66
87
  process.env.LOCAL_USER_ID = "local-user"
67
- process.env.BETTER_AUTH_URL = `http://localhost:${port}`
68
88
  process.env.BETTER_AUTH_SECRET = "local-dev-secret-min-32-chars-long-for-better-auth"
69
- process.env.CORS_ORIGIN = `http://localhost:${port}`
70
89
 
71
90
  const { serve } = await import("../server/index")
72
- serve(port)
91
+ const { port } = serve(preferredPort)
92
+
93
+ process.env.BETTER_AUTH_URL = `http://localhost:${port}`
94
+ process.env.CORS_ORIGIN = `http://localhost:${port}`
95
+
96
+ const url = `http://localhost:${port}`
97
+ console.log(`\n AlgoCoach running at ${url}\n`)
73
98
 
74
- console.log(`\n AlgoCoach running at http://localhost:${port}\n`)
99
+ // Auto-open browser
100
+ const openCmd = process.platform === "darwin"
101
+ ? ["open", url]
102
+ : process.platform === "win32"
103
+ ? ["cmd", "/c", "start", url]
104
+ : ["xdg-open", url]
105
+ Bun.spawn(openCmd).unwrap().catch(() => {})
75
106
  }
76
107
 
77
108
  const cmd = process.argv[2] || "start"
@@ -89,9 +120,9 @@ switch (cmd) {
89
120
  Usage: algocoach <command>
90
121
 
91
122
  Commands:
92
- start Create .env if needed and start the server
93
- init Create .env in current directory
123
+ start Create config if needed and start the server
124
+ init Create config file only
94
125
 
95
- Run 'algocoach start' to get started.
126
+ Config: ${ENV_PATH}
96
127
  `)
97
128
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "algocoach",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "algocoach": "cli/index.ts"
package/server/index.ts CHANGED
@@ -50,8 +50,19 @@ if (fs.existsSync(distPath)) {
50
50
  })
51
51
  }
52
52
 
53
- export function serve(port: number = 3000) {
54
- Bun.serve({ fetch: app.fetch, port, idleTimeout: 255 })
53
+ export function serve(preferredPort: number = 3000) {
54
+ const maxAttempts = 10
55
+ for (let i = 0; i < maxAttempts; i++) {
56
+ const port = preferredPort + i
57
+ try {
58
+ const server = Bun.serve({ fetch: app.fetch, port, idleTimeout: 255 })
59
+ return { server, port }
60
+ } catch (err: any) {
61
+ if (err.code !== "EADDRINUSE" || i === maxAttempts - 1) {
62
+ throw err
63
+ }
64
+ }
65
+ }
55
66
  }
56
67
 
57
68
  export default app