sanjang 0.3.1 → 0.3.3

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 (69) hide show
  1. package/dashboard/app.js +304 -44
  2. package/dashboard/index.html +43 -6
  3. package/dashboard/style.css +306 -0
  4. package/dist/bin/sanjang.d.ts +1 -0
  5. package/dist/bin/sanjang.js +138 -0
  6. package/dist/lib/config.d.ts +19 -0
  7. package/dist/lib/config.js +318 -0
  8. package/dist/lib/engine/cache.d.ts +7 -0
  9. package/dist/lib/engine/cache.js +183 -0
  10. package/dist/lib/engine/config-hotfix.d.ts +7 -0
  11. package/dist/lib/engine/config-hotfix.js +129 -0
  12. package/dist/lib/engine/conflict.d.ts +12 -0
  13. package/dist/lib/engine/conflict.js +32 -0
  14. package/dist/lib/engine/diagnostics.d.ts +15 -0
  15. package/dist/lib/engine/diagnostics.js +58 -0
  16. package/dist/lib/engine/naming.d.ts +10 -0
  17. package/dist/lib/engine/naming.js +83 -0
  18. package/dist/lib/engine/ports.d.ts +9 -0
  19. package/dist/lib/engine/ports.js +55 -0
  20. package/dist/lib/engine/pr.d.ts +27 -0
  21. package/dist/lib/engine/pr.js +54 -0
  22. package/dist/lib/engine/process.d.ts +15 -0
  23. package/dist/lib/engine/process.js +250 -0
  24. package/dist/lib/engine/self-heal.d.ts +12 -0
  25. package/dist/lib/engine/self-heal.js +98 -0
  26. package/dist/lib/engine/smart-init.d.ts +7 -0
  27. package/dist/lib/engine/smart-init.js +138 -0
  28. package/dist/lib/engine/smart-pr.d.ts +19 -0
  29. package/dist/lib/engine/smart-pr.js +105 -0
  30. package/dist/lib/engine/snapshot.d.ts +10 -0
  31. package/dist/lib/engine/snapshot.js +35 -0
  32. package/dist/lib/engine/state.d.ts +7 -0
  33. package/dist/lib/engine/state.js +53 -0
  34. package/dist/lib/engine/suggest.d.ts +21 -0
  35. package/dist/lib/engine/suggest.js +121 -0
  36. package/dist/lib/engine/warp.d.ts +23 -0
  37. package/dist/lib/engine/warp.js +32 -0
  38. package/dist/lib/engine/watcher.d.ts +11 -0
  39. package/dist/lib/engine/watcher.js +43 -0
  40. package/dist/lib/engine/worktree.d.ts +13 -0
  41. package/dist/lib/engine/worktree.js +91 -0
  42. package/dist/lib/server.d.ts +20 -0
  43. package/dist/lib/server.js +1466 -0
  44. package/dist/lib/types.d.ts +109 -0
  45. package/dist/lib/types.js +2 -0
  46. package/package.json +5 -4
  47. package/bin/__tests__/sanjang.test.ts +0 -42
  48. package/bin/sanjang.js +0 -17
  49. package/bin/sanjang.ts +0 -144
  50. package/lib/config.ts +0 -337
  51. package/lib/engine/cache.ts +0 -218
  52. package/lib/engine/config-hotfix.ts +0 -161
  53. package/lib/engine/conflict.ts +0 -33
  54. package/lib/engine/diagnostics.ts +0 -81
  55. package/lib/engine/naming.ts +0 -93
  56. package/lib/engine/ports.ts +0 -61
  57. package/lib/engine/pr.ts +0 -71
  58. package/lib/engine/process.ts +0 -283
  59. package/lib/engine/self-heal.ts +0 -130
  60. package/lib/engine/smart-init.ts +0 -136
  61. package/lib/engine/smart-pr.ts +0 -130
  62. package/lib/engine/snapshot.ts +0 -45
  63. package/lib/engine/state.ts +0 -60
  64. package/lib/engine/suggest.ts +0 -169
  65. package/lib/engine/warp.ts +0 -47
  66. package/lib/engine/watcher.ts +0 -40
  67. package/lib/engine/worktree.ts +0 -100
  68. package/lib/server.ts +0 -1560
  69. package/lib/types.ts +0 -130
@@ -2110,3 +2110,309 @@ header.hidden {
2110
2110
  color: var(--text-muted, #666);
2111
2111
  flex: 1;
2112
2112
  }
2113
+
2114
+ /* ============================================================
2115
+ Basecamp Scene
2116
+ ============================================================ */
2117
+
2118
+ .bc-scene {
2119
+ position: relative;
2120
+ height: 200px;
2121
+ background: linear-gradient(180deg, #080a10 0%, #0c0e14 60%, #12151e 100%);
2122
+ border-radius: 12px;
2123
+ overflow: hidden;
2124
+ border: 1px solid #1c2030;
2125
+ }
2126
+
2127
+ /* Stars */
2128
+ .bc-star {
2129
+ position: absolute;
2130
+ width: 4px;
2131
+ height: 4px;
2132
+ background: #fff;
2133
+ image-rendering: pixelated;
2134
+ }
2135
+
2136
+ .bc-star.twinkle {
2137
+ animation: twinkle 2s steps(2) infinite;
2138
+ }
2139
+
2140
+ .bc-star.twinkle-slow {
2141
+ animation: twinkle 3.5s steps(2) infinite 0.8s;
2142
+ }
2143
+
2144
+ @keyframes twinkle {
2145
+ 0%, 100% { opacity: 0.6; }
2146
+ 50% { opacity: 0.1; }
2147
+ }
2148
+
2149
+ /* Mountains */
2150
+ .bc-mountain {
2151
+ position: absolute;
2152
+ bottom: 0;
2153
+ left: 0;
2154
+ right: 0;
2155
+ image-rendering: pixelated;
2156
+ }
2157
+
2158
+ .bc-mountain-back {
2159
+ height: 100px;
2160
+ background: #1a1d2a;
2161
+ clip-path: polygon(
2162
+ 0% 100%, 0% 70%, 5% 70%, 5% 60%, 10% 60%, 10% 50%, 15% 50%, 15% 40%,
2163
+ 20% 40%, 20% 35%, 25% 35%, 25% 45%, 30% 45%, 30% 55%, 35% 55%, 35% 50%,
2164
+ 40% 50%, 40% 35%, 45% 35%, 45% 25%, 50% 25%, 50% 20%, 55% 20%, 55% 30%,
2165
+ 60% 30%, 60% 40%, 65% 40%, 65% 50%, 70% 50%, 70% 40%, 75% 40%, 75% 50%,
2166
+ 80% 50%, 80% 60%, 85% 60%, 85% 70%, 90% 70%, 90% 80%, 95% 80%, 100% 80%,
2167
+ 100% 100%
2168
+ );
2169
+ }
2170
+
2171
+ .bc-mountain-front {
2172
+ height: 80px;
2173
+ background: #12151e;
2174
+ clip-path: polygon(
2175
+ 0% 100%, 0% 80%, 5% 80%, 5% 70%, 10% 70%, 10% 55%, 15% 55%, 15% 45%,
2176
+ 20% 45%, 20% 40%, 25% 40%, 25% 50%, 30% 50%, 30% 60%, 35% 60%, 35% 55%,
2177
+ 40% 55%, 40% 40%, 45% 40%, 45% 30%, 48% 30%, 48% 25%, 52% 25%, 52% 20%,
2178
+ 55% 20%, 55% 30%, 58% 30%, 58% 40%, 62% 40%, 62% 50%, 65% 50%, 65% 45%,
2179
+ 70% 45%, 70% 35%, 75% 35%, 75% 45%, 80% 45%, 80% 55%, 85% 55%, 85% 65%,
2180
+ 90% 65%, 90% 75%, 95% 75%, 95% 85%, 100% 85%, 100% 100%
2181
+ );
2182
+ }
2183
+
2184
+ /* Snow caps */
2185
+ .bc-snow {
2186
+ position: absolute;
2187
+ height: 4px;
2188
+ background: rgba(255, 255, 255, 0.25);
2189
+ image-rendering: pixelated;
2190
+ }
2191
+
2192
+ /* Campfire glow */
2193
+ .bc-glow {
2194
+ position: absolute;
2195
+ bottom: 0;
2196
+ left: 50%;
2197
+ transform: translateX(-50%);
2198
+ width: 80px;
2199
+ height: 40px;
2200
+ background: radial-gradient(ellipse at center bottom, rgba(255, 140, 50, 0.15) 0%, transparent 70%);
2201
+ animation: glow-pulse 2s ease infinite;
2202
+ }
2203
+
2204
+ @keyframes glow-pulse {
2205
+ 0%, 100% { opacity: 1; }
2206
+ 50% { opacity: 0.6; }
2207
+ }
2208
+
2209
+ /* Campfire pixel */
2210
+ .bc-campfire {
2211
+ position: absolute;
2212
+ bottom: 12px;
2213
+ left: 50%;
2214
+ transform: translateX(-50%);
2215
+ width: 4px;
2216
+ height: 4px;
2217
+ background: #8b4513;
2218
+ image-rendering: pixelated;
2219
+ box-shadow:
2220
+ -4px 0 0 #8b4513,
2221
+ 4px 0 0 #8b4513,
2222
+ 0 -4px 0 #ff6600,
2223
+ -4px -4px 0 #ff8800,
2224
+ 4px -4px 0 #ff8800,
2225
+ 0 -8px 0 #ffaa00,
2226
+ -4px -8px 0 #ff6600,
2227
+ 4px -8px 0 #ff6600,
2228
+ 0 -12px 0 #ffcc00;
2229
+ animation: bc-fire 0.4s steps(1) infinite;
2230
+ }
2231
+
2232
+ @keyframes bc-fire {
2233
+ 0% {
2234
+ box-shadow:
2235
+ -4px 0 0 #8b4513,
2236
+ 4px 0 0 #8b4513,
2237
+ 0 -4px 0 #ff6600,
2238
+ -4px -4px 0 #ff8800,
2239
+ 4px -4px 0 #ff8800,
2240
+ 0 -8px 0 #ffaa00,
2241
+ -4px -8px 0 #ff6600,
2242
+ 4px -8px 0 #ff6600,
2243
+ 0 -12px 0 #ffcc00;
2244
+ }
2245
+ 50% {
2246
+ box-shadow:
2247
+ -4px 0 0 #8b4513,
2248
+ 4px 0 0 #8b4513,
2249
+ 0 -4px 0 #ff8800,
2250
+ -4px -4px 0 #ff6600,
2251
+ 4px -4px 0 #ffaa00,
2252
+ 0 -8px 0 #ff6600,
2253
+ -4px -8px 0 #ffcc00,
2254
+ 4px -8px 0 #ff8800,
2255
+ 0 -12px 0 #ff6600;
2256
+ }
2257
+ }
2258
+
2259
+ /* Sherpa pixel character (idle pose) */
2260
+ .bc-sherpa {
2261
+ position: absolute;
2262
+ bottom: 16px;
2263
+ left: calc(50% + 24px);
2264
+ width: 4px;
2265
+ height: 4px;
2266
+ background: transparent;
2267
+ image-rendering: pixelated;
2268
+ box-shadow:
2269
+ /* Hat */
2270
+ 0 -20px 0 #e74c3c,
2271
+ -4px -20px 0 #e74c3c,
2272
+ 4px -20px 0 #e74c3c,
2273
+ -4px -24px 0 #e74c3c,
2274
+ 0 -24px 0 #e74c3c,
2275
+ 4px -24px 0 #e74c3c,
2276
+ /* Face */
2277
+ -4px -16px 0 #f5c6a0,
2278
+ 0 -16px 0 #f5c6a0,
2279
+ 4px -16px 0 #f5c6a0,
2280
+ /* Body */
2281
+ -4px -12px 0 #3498db,
2282
+ 0 -12px 0 #3498db,
2283
+ 4px -12px 0 #3498db,
2284
+ -4px -8px 0 #3498db,
2285
+ 0 -8px 0 #3498db,
2286
+ 4px -8px 0 #3498db,
2287
+ /* Backpack */
2288
+ 8px -12px 0 #8b6914,
2289
+ 8px -8px 0 #8b6914,
2290
+ /* Legs */
2291
+ -4px -4px 0 #2c3e50,
2292
+ 0 -4px 0 #2c3e50,
2293
+ -4px 0 0 #2c3e50,
2294
+ 4px 0 0 #2c3e50;
2295
+ animation: sherpa-idle 1s steps(1) infinite;
2296
+ }
2297
+
2298
+ @keyframes sherpa-idle {
2299
+ 0%, 100% {
2300
+ box-shadow:
2301
+ 0 -20px 0 #e74c3c,
2302
+ -4px -20px 0 #e74c3c,
2303
+ 4px -20px 0 #e74c3c,
2304
+ -4px -24px 0 #e74c3c,
2305
+ 0 -24px 0 #e74c3c,
2306
+ 4px -24px 0 #e74c3c,
2307
+ -4px -16px 0 #f5c6a0,
2308
+ 0 -16px 0 #f5c6a0,
2309
+ 4px -16px 0 #f5c6a0,
2310
+ -4px -12px 0 #3498db,
2311
+ 0 -12px 0 #3498db,
2312
+ 4px -12px 0 #3498db,
2313
+ -4px -8px 0 #3498db,
2314
+ 0 -8px 0 #3498db,
2315
+ 4px -8px 0 #3498db,
2316
+ 8px -12px 0 #8b6914,
2317
+ 8px -8px 0 #8b6914,
2318
+ -4px -4px 0 #2c3e50,
2319
+ 0 -4px 0 #2c3e50,
2320
+ -4px 0 0 #2c3e50,
2321
+ 4px 0 0 #2c3e50;
2322
+ }
2323
+ 50% {
2324
+ box-shadow:
2325
+ 0 -20px 0 #e74c3c,
2326
+ -4px -20px 0 #e74c3c,
2327
+ 4px -20px 0 #e74c3c,
2328
+ -4px -24px 0 #e74c3c,
2329
+ 0 -24px 0 #e74c3c,
2330
+ 4px -24px 0 #e74c3c,
2331
+ -4px -16px 0 #f5c6a0,
2332
+ 0 -16px 0 #f5c6a0,
2333
+ 4px -16px 0 #f5c6a0,
2334
+ -4px -12px 0 #3498db,
2335
+ 0 -12px 0 #3498db,
2336
+ 4px -12px 0 #3498db,
2337
+ -4px -8px 0 #3498db,
2338
+ 0 -8px 0 #3498db,
2339
+ 4px -8px 0 #3498db,
2340
+ 8px -12px 0 #8b6914,
2341
+ 8px -8px 0 #8b6914,
2342
+ -4px -4px 0 #2c3e50,
2343
+ 4px -4px 0 #2c3e50,
2344
+ -4px 0 0 #2c3e50,
2345
+ 0 0 0 #2c3e50;
2346
+ }
2347
+ }
2348
+
2349
+ /* Speech bubble */
2350
+ .bc-speech {
2351
+ position: absolute;
2352
+ bottom: 52px;
2353
+ left: calc(50% + 8px);
2354
+ background: #1c2030;
2355
+ border: 1px solid #2a2f42;
2356
+ border-radius: 8px;
2357
+ padding: 6px 10px;
2358
+ font-size: 11px;
2359
+ white-space: nowrap;
2360
+ color: var(--text-secondary);
2361
+ animation: bubble-float 3s ease-in-out infinite;
2362
+ }
2363
+
2364
+ .bc-speech::after {
2365
+ content: '';
2366
+ position: absolute;
2367
+ bottom: -6px;
2368
+ left: 20px;
2369
+ width: 0;
2370
+ height: 0;
2371
+ border-left: 6px solid transparent;
2372
+ border-right: 6px solid transparent;
2373
+ border-top: 6px solid #1c2030;
2374
+ }
2375
+
2376
+ @keyframes bubble-float {
2377
+ 0%, 100% { transform: translateY(0); }
2378
+ 50% { transform: translateY(-3px); }
2379
+ }
2380
+
2381
+ .bc-speech.fade-out {
2382
+ opacity: 0;
2383
+ transition: opacity 0.5s;
2384
+ }
2385
+
2386
+ .bc-speech.fade-in {
2387
+ opacity: 1;
2388
+ transition: opacity 0.5s;
2389
+ }
2390
+
2391
+ /* ============================================================
2392
+ Activity Trail
2393
+ ============================================================ */
2394
+
2395
+ #activity-trail {
2396
+ background: #0a0c11;
2397
+ border: 1px solid #1c2030;
2398
+ border-radius: 10px;
2399
+ overflow: hidden;
2400
+ }
2401
+
2402
+ .activity-info {
2403
+ padding: 12px 20px;
2404
+ display: flex;
2405
+ justify-content: space-between;
2406
+ align-items: center;
2407
+ }
2408
+
2409
+ .activity-streak {
2410
+ font-size: 13px;
2411
+ color: #8fbc8f;
2412
+ font-weight: 600;
2413
+ }
2414
+
2415
+ .activity-period {
2416
+ font-size: 11px;
2417
+ color: var(--text-muted);
2418
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,138 @@
1
+ #!/usr/bin/env node
2
+ import { execSync } from "node:child_process";
3
+ import { existsSync } from "node:fs";
4
+ import { resolve } from "node:path";
5
+ const args = process.argv.slice(2);
6
+ const command = args[0];
7
+ // Parse options
8
+ let projectRoot = process.cwd();
9
+ let port = 4000;
10
+ let force = false;
11
+ for (let i = 0; i < args.length; i++) {
12
+ if (args[i] === "--project" && args[i + 1]) {
13
+ projectRoot = resolve(args[i + 1]);
14
+ i++;
15
+ }
16
+ if (args[i] === "--port" && args[i + 1]) {
17
+ port = parseInt(args[i + 1]);
18
+ i++;
19
+ }
20
+ if (args[i] === "--force") {
21
+ force = true;
22
+ }
23
+ }
24
+ // Find git root
25
+ try {
26
+ projectRoot = execSync("git rev-parse --show-toplevel", {
27
+ cwd: projectRoot,
28
+ encoding: "utf8",
29
+ stdio: ["pipe", "pipe", "pipe"],
30
+ }).trim();
31
+ }
32
+ catch {
33
+ console.error("⛰ 산장: git 저장소를 찾을 수 없습니다.");
34
+ console.error(" git 저장소 안에서 실행해주세요.");
35
+ process.exit(1);
36
+ }
37
+ if (command === "init") {
38
+ const { generateConfig, detectApps } = await import("../lib/config.js");
39
+ // Detect apps in subdirectories
40
+ const apps = detectApps(projectRoot);
41
+ let appDir;
42
+ if (apps.length >= 2) {
43
+ // Multi-app interview
44
+ console.log("");
45
+ console.log("⛰ 여러 앱이 감지되었습니다:");
46
+ for (let i = 0; i < apps.length; i++) {
47
+ console.log(` ${i + 1}) ${apps[i].dir}/\t(${apps[i].framework})`);
48
+ }
49
+ console.log("");
50
+ const { createInterface } = await import("node:readline");
51
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
52
+ const answer = await new Promise((resolve) => {
53
+ rl.question(" 어떤 앱을 띄울까요? [번호]: ", resolve);
54
+ });
55
+ rl.close();
56
+ const idx = parseInt(answer) - 1;
57
+ if (idx < 0 || idx >= apps.length || isNaN(idx)) {
58
+ console.error("⛰ 잘못된 선택입니다.");
59
+ process.exit(1);
60
+ }
61
+ appDir = apps[idx].dir;
62
+ console.log(` → ${appDir}/ (${apps[idx].framework}) 선택됨`);
63
+ }
64
+ else if (apps.length === 1) {
65
+ appDir = apps[0].dir;
66
+ }
67
+ const result = generateConfig(projectRoot, { appDir, force });
68
+ if (result.created) {
69
+ console.log(`⛰ ${result.message}`);
70
+ console.log(` 프레임워크: ${result.framework}`);
71
+ console.log(` 설정 파일: ${result.configPath}`);
72
+ }
73
+ else {
74
+ console.log(`⛰ ${result.message}`);
75
+ }
76
+ // Add .sanjang to .gitignore if not present
77
+ const gitignorePath = resolve(projectRoot, ".gitignore");
78
+ if (existsSync(gitignorePath)) {
79
+ const { readFileSync, appendFileSync } = await import("node:fs");
80
+ const content = readFileSync(gitignorePath, "utf8");
81
+ if (!content.includes(".sanjang")) {
82
+ appendFileSync(gitignorePath, "\n# Sanjang local dev camps\n.sanjang/\n");
83
+ console.log(" .gitignore에 .sanjang/ 추가됨");
84
+ }
85
+ }
86
+ // Prebuild dependency cache
87
+ const { loadConfig } = await import("../lib/config.js");
88
+ const initConfig = await loadConfig(projectRoot);
89
+ if (initConfig.setup) {
90
+ console.log("");
91
+ console.log(" 의존성 캐시를 빌드합니다...");
92
+ const { buildCache } = await import("../lib/engine/cache.js");
93
+ const cacheResult = await buildCache(projectRoot, initConfig, (msg) => {
94
+ console.log(` ${msg}`);
95
+ });
96
+ if (cacheResult.success) {
97
+ console.log(` 캐시 빌드 완료 ✓ (${(cacheResult.duration / 1000).toFixed(1)}초)`);
98
+ }
99
+ else {
100
+ console.log(` ⚠️ 캐시 빌드 실패: ${cacheResult.error}`);
101
+ console.log(" 캠프 생성 시 일반 설치를 사용합니다.");
102
+ }
103
+ }
104
+ // Auto-start server unless --no-start
105
+ const noStart = args.includes("--no-start");
106
+ if (!noStart) {
107
+ console.log("");
108
+ console.log(" 서버를 시작합니다...");
109
+ const { startServer } = await import("../lib/server.js");
110
+ await startServer(projectRoot, { port });
111
+ }
112
+ else {
113
+ console.log("");
114
+ console.log(" 다음 단계: sanjang 또는 npx sanjang 으로 서버를 시작하세요.");
115
+ }
116
+ }
117
+ else if (command === "help" || command === "--help" || command === "-h") {
118
+ console.log(`
119
+ ⛰ 산장 (Sanjang) — 바이브코더를 위한 로컬 개발 환경 매니저
120
+
121
+ 사용법:
122
+ sanjang 서버 시작 (대시보드: http://localhost:4000)
123
+ sanjang init 프로젝트 분석 → sanjang.config.js 생성
124
+ sanjang help 이 도움말
125
+
126
+ 옵션:
127
+ --port <N> 대시보드 포트 (기본: 4000)
128
+ --project <path> 프로젝트 경로 (기본: 현재 디렉토리)
129
+ --force 기존 설정을 덮어쓰고 다시 생성
130
+
131
+ 자세히: https://github.com/paul-sherpas/sanjang
132
+ `);
133
+ }
134
+ else {
135
+ // Default: start server
136
+ const { startServer } = await import("../lib/server.js");
137
+ await startServer(projectRoot, { port });
138
+ }
@@ -0,0 +1,19 @@
1
+ import type { DetectedApp, DetectedProject, GenerateConfigResult, SanjangConfig } from "./types.ts";
2
+ /**
3
+ * Load sanjang.config.js from project root.
4
+ * Returns merged config with defaults.
5
+ */
6
+ export declare function loadConfig(projectRoot: string): Promise<SanjangConfig>;
7
+ /**
8
+ * Auto-detect project type and generate config.
9
+ */
10
+ export declare function detectProject(projectRoot: string): DetectedProject;
11
+ /**
12
+ * Scan first-level subdirectories for app candidates.
13
+ * Returns array of { dir, framework, detected } sorted by dir name.
14
+ */
15
+ export declare function detectApps(projectRoot: string): DetectedApp[];
16
+ export declare function generateConfig(projectRoot: string, options?: {
17
+ appDir?: string;
18
+ force?: boolean;
19
+ }): GenerateConfigResult;