vibeman 0.0.0 → 0.0.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.
- package/README.md +12 -0
- package/dist/index.js +116 -0
- package/dist/runtime/api/.tsbuildinfo +1 -0
- package/dist/runtime/api/agent/agent-service.d.ts +226 -0
- package/dist/runtime/api/agent/agent-service.js +901 -0
- package/dist/runtime/api/agent/ai-providers/claude-code-adapter.d.ts +61 -0
- package/dist/runtime/api/agent/ai-providers/claude-code-adapter.js +373 -0
- package/dist/runtime/api/agent/ai-providers/codex-cli-provider.d.ts +34 -0
- package/dist/runtime/api/agent/ai-providers/codex-cli-provider.js +281 -0
- package/dist/runtime/api/agent/ai-providers/index.d.ts +9 -0
- package/dist/runtime/api/agent/ai-providers/index.js +7 -0
- package/dist/runtime/api/agent/ai-providers/types.d.ts +180 -0
- package/dist/runtime/api/agent/ai-providers/types.js +5 -0
- package/dist/runtime/api/agent/codex-cli-provider.test.d.ts +1 -0
- package/dist/runtime/api/agent/codex-cli-provider.test.js +88 -0
- package/dist/runtime/api/agent/core-agent-service.d.ts +119 -0
- package/dist/runtime/api/agent/core-agent-service.js +267 -0
- package/dist/runtime/api/agent/parsers.d.ts +15 -0
- package/dist/runtime/api/agent/parsers.js +241 -0
- package/dist/runtime/api/agent/prompt-service.d.ts +17 -0
- package/dist/runtime/api/agent/prompt-service.js +340 -0
- package/dist/runtime/api/agent/routing-policy.d.ts +188 -0
- package/dist/runtime/api/agent/routing-policy.js +246 -0
- package/dist/runtime/api/api/router-helpers.d.ts +32 -0
- package/dist/runtime/api/api/router-helpers.js +31 -0
- package/dist/runtime/api/api/routers/ai.d.ts +188 -0
- package/dist/runtime/api/api/routers/ai.js +410 -0
- package/dist/runtime/api/api/routers/executions.d.ts +98 -0
- package/dist/runtime/api/api/routers/executions.js +103 -0
- package/dist/runtime/api/api/routers/git.d.ts +45 -0
- package/dist/runtime/api/api/routers/git.js +35 -0
- package/dist/runtime/api/api/routers/settings.d.ts +139 -0
- package/dist/runtime/api/api/routers/settings.js +113 -0
- package/dist/runtime/api/api/routers/tasks.d.ts +141 -0
- package/dist/runtime/api/api/routers/tasks.js +238 -0
- package/dist/runtime/api/api/routers/workflows.d.ts +268 -0
- package/dist/runtime/api/api/routers/workflows.js +308 -0
- package/dist/runtime/api/api/routers/worktrees.d.ts +102 -0
- package/dist/runtime/api/api/routers/worktrees.js +80 -0
- package/dist/runtime/api/api/trpc.d.ts +118 -0
- package/dist/runtime/api/api/trpc.js +34 -0
- package/dist/runtime/api/index.d.ts +9 -0
- package/dist/runtime/api/index.js +125 -0
- package/dist/runtime/api/lib/id-generator.d.ts +70 -0
- package/dist/runtime/api/lib/id-generator.js +123 -0
- package/dist/runtime/api/lib/image-paste-drop-extension.d.ts +26 -0
- package/dist/runtime/api/lib/image-paste-drop-extension.js +125 -0
- package/dist/runtime/api/lib/logger.d.ts +11 -0
- package/dist/runtime/api/lib/logger.js +188 -0
- package/dist/runtime/api/lib/markdown-utils.d.ts +8 -0
- package/dist/runtime/api/lib/markdown-utils.js +282 -0
- package/dist/runtime/api/lib/markdown-utils.test.d.ts +1 -0
- package/dist/runtime/api/lib/markdown-utils.test.js +348 -0
- package/dist/runtime/api/lib/server/agent-service-singleton.d.ts +6 -0
- package/dist/runtime/api/lib/server/agent-service-singleton.js +27 -0
- package/dist/runtime/api/lib/server/git-service-singleton.d.ts +6 -0
- package/dist/runtime/api/lib/server/git-service-singleton.js +47 -0
- package/dist/runtime/api/lib/server/project-root.d.ts +2 -0
- package/dist/runtime/api/lib/server/project-root.js +38 -0
- package/dist/runtime/api/lib/server/task-service-singleton.d.ts +7 -0
- package/dist/runtime/api/lib/server/task-service-singleton.js +58 -0
- package/dist/runtime/api/lib/server/vibing-orchestrator-singleton.d.ts +7 -0
- package/dist/runtime/api/lib/server/vibing-orchestrator-singleton.js +57 -0
- package/dist/runtime/api/lib/tiptap-utils.clamp-selection.test.d.ts +1 -0
- package/dist/runtime/api/lib/tiptap-utils.clamp-selection.test.js +27 -0
- package/dist/runtime/api/lib/tiptap-utils.d.ts +130 -0
- package/dist/runtime/api/lib/tiptap-utils.js +327 -0
- package/dist/runtime/api/lib/trpc/client.d.ts +1 -0
- package/dist/runtime/api/lib/trpc/client.js +5 -0
- package/dist/runtime/api/lib/trpc/server.d.ts +822 -0
- package/dist/runtime/api/lib/trpc/server.js +11 -0
- package/dist/runtime/api/lib/trpc/ws-server.d.ts +8 -0
- package/dist/runtime/api/lib/trpc/ws-server.js +33 -0
- package/dist/runtime/api/persistence/database-service.d.ts +14 -0
- package/dist/runtime/api/persistence/database-service.js +74 -0
- package/dist/runtime/api/persistence/execution-log-persistence.d.ts +90 -0
- package/dist/runtime/api/persistence/execution-log-persistence.js +410 -0
- package/dist/runtime/api/persistence/execution-log-persistence.test.d.ts +1 -0
- package/dist/runtime/api/persistence/execution-log-persistence.test.js +170 -0
- package/dist/runtime/api/router.d.ts +825 -0
- package/dist/runtime/api/router.js +56 -0
- package/dist/runtime/api/settings-service.d.ts +110 -0
- package/dist/runtime/api/settings-service.js +611 -0
- package/dist/runtime/api/tasks/file-watcher.d.ts +23 -0
- package/dist/runtime/api/tasks/file-watcher.js +88 -0
- package/dist/runtime/api/tasks/task-file-parser.d.ts +13 -0
- package/dist/runtime/api/tasks/task-file-parser.js +161 -0
- package/dist/runtime/api/tasks/task-service.d.ts +36 -0
- package/dist/runtime/api/tasks/task-service.js +173 -0
- package/dist/runtime/api/types/index.d.ts +179 -0
- package/dist/runtime/api/types/index.js +1 -0
- package/dist/runtime/api/types/settings.d.ts +81 -0
- package/dist/runtime/api/types/settings.js +2 -0
- package/dist/runtime/api/types.d.ts +2 -0
- package/dist/runtime/api/types.js +1 -0
- package/dist/runtime/api/utils/env.d.ts +6 -0
- package/dist/runtime/api/utils/env.js +12 -0
- package/dist/runtime/api/utils/stripNextEnv.d.ts +7 -0
- package/dist/runtime/api/utils/stripNextEnv.js +22 -0
- package/dist/runtime/api/utils/title-slug.d.ts +6 -0
- package/dist/runtime/api/utils/title-slug.js +77 -0
- package/dist/runtime/api/utils/url.d.ts +2 -0
- package/dist/runtime/api/utils/url.js +19 -0
- package/dist/runtime/api/vcs/git-history-service.d.ts +57 -0
- package/dist/runtime/api/vcs/git-history-service.js +228 -0
- package/dist/runtime/api/vcs/git-service.d.ts +127 -0
- package/dist/runtime/api/vcs/git-service.js +284 -0
- package/dist/runtime/api/vcs/worktree-service.d.ts +93 -0
- package/dist/runtime/api/vcs/worktree-service.js +506 -0
- package/dist/runtime/api/vcs/worktree-service.test.d.ts +1 -0
- package/dist/runtime/api/vcs/worktree-service.test.js +20 -0
- package/dist/runtime/api/workflows/quality-pipeline.d.ts +58 -0
- package/dist/runtime/api/workflows/quality-pipeline.js +400 -0
- package/dist/runtime/api/workflows/vibing-orchestrator.d.ts +313 -0
- package/dist/runtime/api/workflows/vibing-orchestrator.js +1861 -0
- package/dist/runtime/web/.next/BUILD_ID +1 -0
- package/dist/runtime/web/.next/app-build-manifest.json +59 -0
- package/dist/runtime/web/.next/app-path-routes-manifest.json +7 -0
- package/dist/runtime/web/.next/build-manifest.json +33 -0
- package/dist/runtime/web/.next/package.json +1 -0
- package/dist/runtime/web/.next/prerender-manifest.json +61 -0
- package/dist/runtime/web/.next/react-loadable-manifest.json +39 -0
- package/dist/runtime/web/.next/required-server-files.json +334 -0
- package/dist/runtime/web/.next/routes-manifest.json +62 -0
- package/dist/runtime/web/.next/server/app/_not-found/page.js +2 -0
- package/dist/runtime/web/.next/server/app/_not-found/page.js.nft.json +1 -0
- package/dist/runtime/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -0
- package/dist/runtime/web/.next/server/app/_not-found.html +7 -0
- package/dist/runtime/web/.next/server/app/_not-found.meta +8 -0
- package/dist/runtime/web/.next/server/app/_not-found.rsc +22 -0
- package/dist/runtime/web/.next/server/app/api/health/route.js +1 -0
- package/dist/runtime/web/.next/server/app/api/health/route.js.nft.json +1 -0
- package/dist/runtime/web/.next/server/app/api/health/route_client-reference-manifest.js +1 -0
- package/dist/runtime/web/.next/server/app/api/images/[...path]/route.js +1 -0
- package/dist/runtime/web/.next/server/app/api/images/[...path]/route.js.nft.json +1 -0
- package/dist/runtime/web/.next/server/app/api/images/[...path]/route_client-reference-manifest.js +1 -0
- package/dist/runtime/web/.next/server/app/api/upload/route.js +1 -0
- package/dist/runtime/web/.next/server/app/api/upload/route.js.nft.json +1 -0
- package/dist/runtime/web/.next/server/app/api/upload/route_client-reference-manifest.js +1 -0
- package/dist/runtime/web/.next/server/app/index.html +7 -0
- package/dist/runtime/web/.next/server/app/index.meta +7 -0
- package/dist/runtime/web/.next/server/app/index.rsc +27 -0
- package/dist/runtime/web/.next/server/app/page.js +147 -0
- package/dist/runtime/web/.next/server/app/page.js.nft.json +1 -0
- package/dist/runtime/web/.next/server/app/page_client-reference-manifest.js +1 -0
- package/dist/runtime/web/.next/server/app-paths-manifest.json +7 -0
- package/dist/runtime/web/.next/server/chunks/217.js +1 -0
- package/dist/runtime/web/.next/server/chunks/383.js +6 -0
- package/dist/runtime/web/.next/server/chunks/458.js +1 -0
- package/dist/runtime/web/.next/server/chunks/576.js +18 -0
- package/dist/runtime/web/.next/server/chunks/635.js +22 -0
- package/dist/runtime/web/.next/server/chunks/761.js +1 -0
- package/dist/runtime/web/.next/server/chunks/777.js +3 -0
- package/dist/runtime/web/.next/server/chunks/825.js +1 -0
- package/dist/runtime/web/.next/server/chunks/838.js +1 -0
- package/dist/runtime/web/.next/server/chunks/973.js +15 -0
- package/dist/runtime/web/.next/server/functions-config-manifest.json +4 -0
- package/dist/runtime/web/.next/server/middleware-build-manifest.js +1 -0
- package/dist/runtime/web/.next/server/middleware-manifest.json +6 -0
- package/dist/runtime/web/.next/server/middleware-react-loadable-manifest.js +1 -0
- package/dist/runtime/web/.next/server/next-font-manifest.js +1 -0
- package/dist/runtime/web/.next/server/next-font-manifest.json +1 -0
- package/dist/runtime/web/.next/server/pages/404.html +7 -0
- package/dist/runtime/web/.next/server/pages/500.html +1 -0
- package/dist/runtime/web/.next/server/pages/_app.js +1 -0
- package/dist/runtime/web/.next/server/pages/_app.js.nft.json +1 -0
- package/dist/runtime/web/.next/server/pages/_document.js +1 -0
- package/dist/runtime/web/.next/server/pages/_document.js.nft.json +1 -0
- package/dist/runtime/web/.next/server/pages/_error.js +19 -0
- package/dist/runtime/web/.next/server/pages/_error.js.nft.json +1 -0
- package/dist/runtime/web/.next/server/pages-manifest.json +6 -0
- package/dist/runtime/web/.next/server/server-reference-manifest.js +1 -0
- package/dist/runtime/web/.next/server/server-reference-manifest.json +1 -0
- package/dist/runtime/web/.next/server/webpack-runtime.js +1 -0
- package/dist/runtime/web/.next/static/1HR8N0rJkCvFRtbTPJMyH/_buildManifest.js +1 -0
- package/dist/runtime/web/.next/static/1HR8N0rJkCvFRtbTPJMyH/_ssgManifest.js +1 -0
- package/dist/runtime/web/.next/static/chunks/18-15c10d3288afef2e.js +1 -0
- package/dist/runtime/web/.next/static/chunks/1c0ca389.537bbe362e3ffbd9.js +3 -0
- package/dist/runtime/web/.next/static/chunks/22747d63-ad5da0c19f4cfe41.js +71 -0
- package/dist/runtime/web/.next/static/chunks/277-0142a939f08738c3.js +63 -0
- package/dist/runtime/web/.next/static/chunks/355.056c2645878a799a.js +1 -0
- package/dist/runtime/web/.next/static/chunks/420.a5ccf151c9e2b2f1.js +1 -0
- package/dist/runtime/web/.next/static/chunks/439.1be0c6242fd248d5.js +15 -0
- package/dist/runtime/web/.next/static/chunks/440.c52e7c0f797e22b2.js +1 -0
- package/dist/runtime/web/.next/static/chunks/575-e2478287c27da87b.js +1 -0
- package/dist/runtime/web/.next/static/chunks/691.920d88c115087314.js +1 -0
- package/dist/runtime/web/.next/static/chunks/765-e838910065b50c3d.js +1 -0
- package/dist/runtime/web/.next/static/chunks/87c73c54-09e1ba5c70e60a51.js +1 -0
- package/dist/runtime/web/.next/static/chunks/891cff7f.0f71fc028f87e683.js +1 -0
- package/dist/runtime/web/.next/static/chunks/8bb4d8db-3e2aa02b0a2384b9.js +1 -0
- package/dist/runtime/web/.next/static/chunks/9af238c7-271a911d4e99ab18.js +1 -0
- package/dist/runtime/web/.next/static/chunks/app/_not-found/page-1cb74d1cba27d0ab.js +1 -0
- package/dist/runtime/web/.next/static/chunks/app/api/health/route-105a61ae865ba536.js +1 -0
- package/dist/runtime/web/.next/static/chunks/app/api/images/[...path]/route-105a61ae865ba536.js +1 -0
- package/dist/runtime/web/.next/static/chunks/app/api/upload/route-105a61ae865ba536.js +1 -0
- package/dist/runtime/web/.next/static/chunks/app/layout-dc0cfd29075b2160.js +1 -0
- package/dist/runtime/web/.next/static/chunks/app/page-f34a8b196b18850b.js +1 -0
- package/dist/runtime/web/.next/static/chunks/cac567b0-5b77dd12911823cd.js +1 -0
- package/dist/runtime/web/.next/static/chunks/framework-2518f1345b5b2806.js +1 -0
- package/dist/runtime/web/.next/static/chunks/main-17665e5e39de9a8a.js +1 -0
- package/dist/runtime/web/.next/static/chunks/main-app-c0b0f5ba4f7f9d75.js +1 -0
- package/dist/runtime/web/.next/static/chunks/pages/_app-d6f6b3bbc3d81ee1.js +1 -0
- package/dist/runtime/web/.next/static/chunks/pages/_error-75a96cf1997cc3b9.js +1 -0
- package/dist/runtime/web/.next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
- package/dist/runtime/web/.next/static/chunks/webpack-c8de37305b4635cf.js +1 -0
- package/dist/runtime/web/.next/static/css/08c950681f1a9a92.css +1 -0
- package/dist/runtime/web/.next/static/css/2728291c68f99cb1.css +3 -0
- package/dist/runtime/web/.next/static/css/521bd69cc298cd1a.css +1 -0
- package/dist/runtime/web/.next/static/css/537e22821e101b87.css +1 -0
- package/dist/runtime/web/.next/static/media/19cfc7226ec3afaa-s.woff2 +0 -0
- package/dist/runtime/web/.next/static/media/21350d82a1f187e9-s.woff2 +0 -0
- package/dist/runtime/web/.next/static/media/8e9860b6e62d6359-s.woff2 +0 -0
- package/dist/runtime/web/.next/static/media/ba9851c3c22cd980-s.woff2 +0 -0
- package/dist/runtime/web/.next/static/media/c5fe6dc8356a8c31-s.woff2 +0 -0
- package/dist/runtime/web/.next/static/media/df0a9ae256c0569c-s.woff2 +0 -0
- package/dist/runtime/web/.next/static/media/e4af272ccee01ff0-s.p.woff2 +0 -0
- package/dist/runtime/web/package.json +65 -0
- package/dist/runtime/web/server.js +44 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +80 -7
|
@@ -0,0 +1,611 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings Service - Centralized configuration management
|
|
3
|
+
*
|
|
4
|
+
* Provides persistent storage and validation for application settings
|
|
5
|
+
* with atomic write operations and schema validation using Zod.
|
|
6
|
+
*/
|
|
7
|
+
import { EventEmitter } from 'events';
|
|
8
|
+
import fs from 'fs/promises';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import { z } from 'zod';
|
|
11
|
+
import { log } from './lib/logger.js';
|
|
12
|
+
import { getProjectRoot } from './lib/server/project-root.js';
|
|
13
|
+
// Zod schema for validation
|
|
14
|
+
const CLAUDE_ALLOWED_MODELS = [
|
|
15
|
+
'claude-sonnet-4-20250514',
|
|
16
|
+
'claude-opus-4-1-20250805',
|
|
17
|
+
'claude-3-5-haiku-20241022',
|
|
18
|
+
];
|
|
19
|
+
const MODEL_ENUM = [...CLAUDE_ALLOWED_MODELS, 'gpt-5'];
|
|
20
|
+
const VibemanSettingsSchema = z.object({
|
|
21
|
+
agents: z.object({
|
|
22
|
+
defaultProvider: z.enum(['claude-code', 'codex']),
|
|
23
|
+
codingAgent: z.object({
|
|
24
|
+
provider: z.enum(['claude-code', 'codex']),
|
|
25
|
+
model: z.enum(MODEL_ENUM),
|
|
26
|
+
maxTokens: z.number().min(256).max(10000000000000).optional(),
|
|
27
|
+
}),
|
|
28
|
+
judgeAgent: z.object({
|
|
29
|
+
provider: z.enum(['claude-code', 'codex']),
|
|
30
|
+
model: z.enum(MODEL_ENUM),
|
|
31
|
+
maxTokens: z.number().min(256).max(10000000000000).optional(),
|
|
32
|
+
reviewThresholdScore: z.number().min(0).max(100),
|
|
33
|
+
}),
|
|
34
|
+
defaultPrompts: z.object({
|
|
35
|
+
systemPrompt: z.string().min(10),
|
|
36
|
+
taskPrompt: z.string().min(10),
|
|
37
|
+
}),
|
|
38
|
+
providers: z
|
|
39
|
+
.object({
|
|
40
|
+
claudeCode: z.object({ binPath: z.string().min(1).optional() }).optional(),
|
|
41
|
+
codex: z.object({ binPath: z.string().min(1).optional() }).optional(),
|
|
42
|
+
})
|
|
43
|
+
.optional(),
|
|
44
|
+
}),
|
|
45
|
+
defaultWorkflow: z.object({
|
|
46
|
+
autoQualityChecks: z.boolean(),
|
|
47
|
+
requireHumanApproval: z.boolean(),
|
|
48
|
+
aiCodeReview: z.boolean(),
|
|
49
|
+
maxImplementationAttempts: z.number().min(1).max(10),
|
|
50
|
+
autoCommit: z.boolean(),
|
|
51
|
+
}),
|
|
52
|
+
quality: z
|
|
53
|
+
.object({
|
|
54
|
+
checks: z.array(z.object({
|
|
55
|
+
name: z.string().min(1),
|
|
56
|
+
command: z.string().min(1),
|
|
57
|
+
args: z.array(z.string()).optional(),
|
|
58
|
+
timeout: z
|
|
59
|
+
.number()
|
|
60
|
+
.min(1000)
|
|
61
|
+
.max(10 * 60 * 1000)
|
|
62
|
+
.optional(),
|
|
63
|
+
enabled: z.boolean().optional(),
|
|
64
|
+
})),
|
|
65
|
+
})
|
|
66
|
+
.optional(),
|
|
67
|
+
development: z.object({
|
|
68
|
+
vibeDir: z.string().min(1).optional(),
|
|
69
|
+
git: z.object({
|
|
70
|
+
defaultBranch: z.string().min(1),
|
|
71
|
+
worktreePath: z.string().min(1).optional(),
|
|
72
|
+
}),
|
|
73
|
+
editor: z
|
|
74
|
+
.object({
|
|
75
|
+
type: z.enum(['vscode', 'cursor', 'vim', 'custom']).optional(),
|
|
76
|
+
command: z.string().min(1).optional(),
|
|
77
|
+
args: z.array(z.string()).optional(),
|
|
78
|
+
name: z.string().min(1).optional(),
|
|
79
|
+
openFileCommand: z.array(z.string()).optional(),
|
|
80
|
+
openFolderCommand: z.array(z.string()).optional(),
|
|
81
|
+
})
|
|
82
|
+
.optional(),
|
|
83
|
+
terminal: z
|
|
84
|
+
.object({
|
|
85
|
+
type: z.enum(['system', 'iterm2', 'warp', 'custom']).optional(),
|
|
86
|
+
command: z.string().min(1).optional(),
|
|
87
|
+
args: z.array(z.string()).optional(),
|
|
88
|
+
name: z.string().min(1).optional(),
|
|
89
|
+
openCommand: z.array(z.string()).optional(),
|
|
90
|
+
})
|
|
91
|
+
.optional(),
|
|
92
|
+
}),
|
|
93
|
+
server: z.object({
|
|
94
|
+
port: z.number().min(3000).max(65535),
|
|
95
|
+
host: z.string().min(1),
|
|
96
|
+
autoOpenBrowser: z.boolean(),
|
|
97
|
+
}),
|
|
98
|
+
});
|
|
99
|
+
// Default settings
|
|
100
|
+
const DEFAULT_SETTINGS = {
|
|
101
|
+
agents: {
|
|
102
|
+
defaultProvider: 'claude-code',
|
|
103
|
+
codingAgent: {
|
|
104
|
+
provider: 'claude-code',
|
|
105
|
+
model: 'claude-sonnet-4-20250514',
|
|
106
|
+
},
|
|
107
|
+
judgeAgent: {
|
|
108
|
+
provider: 'claude-code',
|
|
109
|
+
model: 'claude-sonnet-4-20250514',
|
|
110
|
+
reviewThresholdScore: 70,
|
|
111
|
+
},
|
|
112
|
+
defaultPrompts: {
|
|
113
|
+
systemPrompt: 'You are a senior software engineer assistant. Follow best practices and write clean, maintainable code.',
|
|
114
|
+
taskPrompt: 'Please implement the following task with attention to quality and testing.',
|
|
115
|
+
},
|
|
116
|
+
providers: {},
|
|
117
|
+
},
|
|
118
|
+
defaultWorkflow: {
|
|
119
|
+
autoQualityChecks: true,
|
|
120
|
+
requireHumanApproval: true,
|
|
121
|
+
aiCodeReview: true,
|
|
122
|
+
maxImplementationAttempts: 3,
|
|
123
|
+
autoCommit: false,
|
|
124
|
+
},
|
|
125
|
+
quality: {
|
|
126
|
+
checks: [],
|
|
127
|
+
},
|
|
128
|
+
development: {
|
|
129
|
+
git: {
|
|
130
|
+
defaultBranch: 'main',
|
|
131
|
+
},
|
|
132
|
+
editor: {
|
|
133
|
+
type: 'vscode',
|
|
134
|
+
command: 'code',
|
|
135
|
+
args: [],
|
|
136
|
+
name: 'Visual Studio Code',
|
|
137
|
+
openFileCommand: ['code', '${file}'],
|
|
138
|
+
openFolderCommand: ['code', '${folder}'],
|
|
139
|
+
},
|
|
140
|
+
terminal: {
|
|
141
|
+
type: 'system',
|
|
142
|
+
command: process.platform === 'darwin'
|
|
143
|
+
? 'open'
|
|
144
|
+
: process.platform === 'win32'
|
|
145
|
+
? 'cmd'
|
|
146
|
+
: 'gnome-terminal',
|
|
147
|
+
args: process.platform === 'darwin' ? ['-a', 'Terminal'] : [],
|
|
148
|
+
name: process.platform === 'darwin'
|
|
149
|
+
? 'Terminal'
|
|
150
|
+
: process.platform === 'win32'
|
|
151
|
+
? 'Command Prompt'
|
|
152
|
+
: 'Terminal',
|
|
153
|
+
openCommand: process.platform === 'darwin'
|
|
154
|
+
? ['open', '-a', 'Terminal', '${cwd}']
|
|
155
|
+
: process.platform === 'win32'
|
|
156
|
+
? ['cmd', '/c', 'start', 'cmd', '/k', 'cd', '/d', '${cwd}']
|
|
157
|
+
: ['gnome-terminal', '--working-directory=${cwd}'],
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
server: {
|
|
161
|
+
port: 6969,
|
|
162
|
+
host: 'localhost',
|
|
163
|
+
autoOpenBrowser: true,
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
export class SettingsService extends EventEmitter {
|
|
167
|
+
constructor(config = {}) {
|
|
168
|
+
super();
|
|
169
|
+
this.settings = { ...DEFAULT_SETTINGS };
|
|
170
|
+
this.initialized = false;
|
|
171
|
+
const settingsDir = config.settingsDir ?? '.vibeman';
|
|
172
|
+
const settingsFileName = config.settingsFileName ?? 'settings.json';
|
|
173
|
+
// Resolve settings path relative to the repo project root (not package CWD)
|
|
174
|
+
const projectRoot = getProjectRoot();
|
|
175
|
+
this.settingsPath = path.join(projectRoot, settingsDir, settingsFileName);
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Initialize the settings service
|
|
179
|
+
*/
|
|
180
|
+
async initialize() {
|
|
181
|
+
if (this.initialized)
|
|
182
|
+
return;
|
|
183
|
+
try {
|
|
184
|
+
await this.ensureSettingsDirectory();
|
|
185
|
+
await this.loadSettings();
|
|
186
|
+
this.initialized = true;
|
|
187
|
+
log.info('Settings service initialized', { settingsPath: this.settingsPath }, 'settings-service');
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
log.error('Failed to initialize settings service', error, 'settings-service');
|
|
191
|
+
throw error;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Get all current settings
|
|
196
|
+
*/
|
|
197
|
+
getSettings() {
|
|
198
|
+
return { ...this.settings };
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Get a specific setting by path
|
|
202
|
+
*/
|
|
203
|
+
getSetting(path) {
|
|
204
|
+
let current = this.settings;
|
|
205
|
+
for (const key of path) {
|
|
206
|
+
if (current && typeof current === 'object' && key in current) {
|
|
207
|
+
current = current[key];
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
return undefined;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return current;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Update settings with validation
|
|
217
|
+
*/
|
|
218
|
+
async updateSettings(updates) {
|
|
219
|
+
// Create a copy to test updates
|
|
220
|
+
const testSettings = JSON.parse(JSON.stringify(this.settings));
|
|
221
|
+
const errors = [];
|
|
222
|
+
// Apply all updates to test copy
|
|
223
|
+
for (const update of updates) {
|
|
224
|
+
try {
|
|
225
|
+
this.applySingleUpdate(testSettings, update.path, update.value);
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
errors.push({
|
|
229
|
+
path: update.path,
|
|
230
|
+
message: error instanceof Error ? error.message : 'Invalid update',
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
// Validate the entire updated settings object
|
|
235
|
+
try {
|
|
236
|
+
const validated = VibemanSettingsSchema.parse(testSettings);
|
|
237
|
+
if (errors.length === 0) {
|
|
238
|
+
// Apply updates to actual settings
|
|
239
|
+
this.settings = validated;
|
|
240
|
+
await this.saveSettings();
|
|
241
|
+
// Emit change events
|
|
242
|
+
for (const update of updates) {
|
|
243
|
+
this.emit('settingChanged', {
|
|
244
|
+
path: update.path,
|
|
245
|
+
oldValue: this.getSetting(update.path),
|
|
246
|
+
newValue: update.value,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
this.emit('settingsUpdated', this.settings);
|
|
250
|
+
return { success: true, errors: [] };
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
catch (validationError) {
|
|
254
|
+
if (validationError instanceof z.ZodError) {
|
|
255
|
+
for (const issue of validationError.issues) {
|
|
256
|
+
errors.push({
|
|
257
|
+
path: issue.path.map(String),
|
|
258
|
+
message: issue.message,
|
|
259
|
+
expected: 'Valid value according to schema',
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
errors.push({
|
|
265
|
+
path: [],
|
|
266
|
+
message: validationError instanceof Error ? validationError.message : 'Validation failed',
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return { success: false, errors };
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Reset settings to defaults
|
|
274
|
+
*/
|
|
275
|
+
async resetToDefaults() {
|
|
276
|
+
this.settings = { ...DEFAULT_SETTINGS };
|
|
277
|
+
await this.saveSettings();
|
|
278
|
+
this.emit('settingsReset', this.settings);
|
|
279
|
+
log.info('Settings reset to defaults', undefined, 'settings-service');
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Validate settings schema
|
|
283
|
+
*/
|
|
284
|
+
validateSettings(settings) {
|
|
285
|
+
try {
|
|
286
|
+
VibemanSettingsSchema.parse(settings);
|
|
287
|
+
return { valid: true, errors: [] };
|
|
288
|
+
}
|
|
289
|
+
catch (error) {
|
|
290
|
+
const errors = [];
|
|
291
|
+
if (error instanceof z.ZodError) {
|
|
292
|
+
for (const issue of error.issues) {
|
|
293
|
+
errors.push({
|
|
294
|
+
path: issue.path.map(String),
|
|
295
|
+
message: issue.message,
|
|
296
|
+
expected: 'Valid value according to schema',
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
errors.push({
|
|
302
|
+
path: [],
|
|
303
|
+
message: error instanceof Error ? error.message : 'Validation failed',
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
return { valid: false, errors };
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Get settings file information
|
|
311
|
+
*/
|
|
312
|
+
async getSettingsInfo() {
|
|
313
|
+
return {
|
|
314
|
+
path: this.settingsPath,
|
|
315
|
+
exists: await this.fileExists(this.settingsPath),
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Get predefined editor configurations
|
|
320
|
+
*/
|
|
321
|
+
getEditorPresets() {
|
|
322
|
+
return {
|
|
323
|
+
vscode: {
|
|
324
|
+
type: 'vscode',
|
|
325
|
+
command: 'code',
|
|
326
|
+
name: 'Visual Studio Code',
|
|
327
|
+
openFileCommand: ['code', '${file}'],
|
|
328
|
+
openFolderCommand: ['code', '${folder}'],
|
|
329
|
+
},
|
|
330
|
+
cursor: {
|
|
331
|
+
type: 'cursor',
|
|
332
|
+
command: 'cursor',
|
|
333
|
+
name: 'Cursor',
|
|
334
|
+
openFileCommand: ['cursor', '${file}'],
|
|
335
|
+
openFolderCommand: ['cursor', '${folder}'],
|
|
336
|
+
},
|
|
337
|
+
vim: {
|
|
338
|
+
type: 'vim',
|
|
339
|
+
command: 'vim',
|
|
340
|
+
name: 'Vim',
|
|
341
|
+
openFileCommand: ['vim', '${file}'],
|
|
342
|
+
openFolderCommand: ['vim', '${folder}'],
|
|
343
|
+
},
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Get predefined terminal configurations
|
|
348
|
+
*/
|
|
349
|
+
getTerminalPresets() {
|
|
350
|
+
const platform = process.platform;
|
|
351
|
+
return {
|
|
352
|
+
system: {
|
|
353
|
+
type: 'system',
|
|
354
|
+
command: platform === 'darwin' ? 'open' : platform === 'win32' ? 'cmd' : 'gnome-terminal',
|
|
355
|
+
args: platform === 'darwin' ? ['-a', 'Terminal'] : [],
|
|
356
|
+
name: platform === 'darwin' ? 'Terminal' : platform === 'win32' ? 'Command Prompt' : 'Terminal',
|
|
357
|
+
openCommand: platform === 'darwin'
|
|
358
|
+
? ['open', '-a', 'Terminal', '${cwd}']
|
|
359
|
+
: platform === 'win32'
|
|
360
|
+
? ['cmd', '/c', 'start', 'cmd', '/k', 'cd', '/d', '${cwd}']
|
|
361
|
+
: ['gnome-terminal', '--working-directory=${cwd}'],
|
|
362
|
+
},
|
|
363
|
+
iterm2: {
|
|
364
|
+
type: 'iterm2',
|
|
365
|
+
command: 'open',
|
|
366
|
+
args: ['-a', 'iTerm'],
|
|
367
|
+
name: 'iTerm2',
|
|
368
|
+
openCommand: ['open', '-a', 'iTerm', '${cwd}'],
|
|
369
|
+
},
|
|
370
|
+
warp: {
|
|
371
|
+
type: 'warp',
|
|
372
|
+
command: 'open',
|
|
373
|
+
args: ['-a', 'Warp'],
|
|
374
|
+
name: 'Warp',
|
|
375
|
+
openCommand: ['open', '-a', 'Warp', '${cwd}'],
|
|
376
|
+
},
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Apply editor preset configuration
|
|
381
|
+
*/
|
|
382
|
+
async applyEditorPreset(presetName) {
|
|
383
|
+
const presets = this.getEditorPresets();
|
|
384
|
+
const preset = presets[presetName];
|
|
385
|
+
if (!preset) {
|
|
386
|
+
return {
|
|
387
|
+
success: false,
|
|
388
|
+
errors: [
|
|
389
|
+
{ path: ['development', 'editor'], message: `Unknown editor preset: ${presetName}` },
|
|
390
|
+
],
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
return this.updateSettings([
|
|
394
|
+
{
|
|
395
|
+
path: ['development', 'editor'],
|
|
396
|
+
value: preset,
|
|
397
|
+
},
|
|
398
|
+
]);
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Apply terminal preset configuration
|
|
402
|
+
*/
|
|
403
|
+
async applyTerminalPreset(presetName) {
|
|
404
|
+
const presets = this.getTerminalPresets();
|
|
405
|
+
const preset = presets[presetName];
|
|
406
|
+
if (!preset) {
|
|
407
|
+
return {
|
|
408
|
+
success: false,
|
|
409
|
+
errors: [
|
|
410
|
+
{ path: ['development', 'terminal'], message: `Unknown terminal preset: ${presetName}` },
|
|
411
|
+
],
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
return this.updateSettings([
|
|
415
|
+
{
|
|
416
|
+
path: ['development', 'terminal'],
|
|
417
|
+
value: preset,
|
|
418
|
+
},
|
|
419
|
+
]);
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Generate command to open a file with the configured editor
|
|
423
|
+
*/
|
|
424
|
+
getOpenFileCommand(filePath) {
|
|
425
|
+
const editor = this.settings.development?.editor;
|
|
426
|
+
if (!editor?.openFileCommand)
|
|
427
|
+
return null;
|
|
428
|
+
return editor.openFileCommand.map((arg) => arg.replace('${file}', filePath));
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Generate command to open a folder with the configured editor
|
|
432
|
+
*/
|
|
433
|
+
getOpenFolderCommand(folderPath) {
|
|
434
|
+
const editor = this.settings.development?.editor;
|
|
435
|
+
if (!editor?.openFolderCommand)
|
|
436
|
+
return null;
|
|
437
|
+
return editor.openFolderCommand.map((arg) => arg.replace('${folder}', folderPath));
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Generate command to open terminal in specific directory
|
|
441
|
+
*/
|
|
442
|
+
getOpenTerminalCommand(cwd) {
|
|
443
|
+
const terminal = this.settings.development?.terminal;
|
|
444
|
+
if (!terminal?.openCommand)
|
|
445
|
+
return null;
|
|
446
|
+
return terminal.openCommand.map((arg) => arg.replace('${cwd}', cwd));
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Validate if editor is available on system
|
|
450
|
+
*/
|
|
451
|
+
async validateEditorAvailability() {
|
|
452
|
+
const editor = this.settings.development?.editor;
|
|
453
|
+
if (!editor?.command) {
|
|
454
|
+
return { available: false, error: 'No editor command configured' };
|
|
455
|
+
}
|
|
456
|
+
try {
|
|
457
|
+
const { exec } = await import('child_process');
|
|
458
|
+
const { promisify } = await import('util');
|
|
459
|
+
const execAsync = promisify(exec);
|
|
460
|
+
// Try to get version or check if command exists
|
|
461
|
+
await execAsync(`which ${editor.command}`, { timeout: 5000 });
|
|
462
|
+
return { available: true };
|
|
463
|
+
}
|
|
464
|
+
catch {
|
|
465
|
+
return {
|
|
466
|
+
available: false,
|
|
467
|
+
error: `Editor command '${editor.command}' not found in PATH`,
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Validate if terminal is available on system
|
|
473
|
+
*/
|
|
474
|
+
async validateTerminalAvailability() {
|
|
475
|
+
const terminal = this.settings.development?.terminal;
|
|
476
|
+
if (!terminal?.command) {
|
|
477
|
+
return { available: false, error: 'No terminal command configured' };
|
|
478
|
+
}
|
|
479
|
+
try {
|
|
480
|
+
const { exec } = await import('child_process');
|
|
481
|
+
const { promisify } = await import('util');
|
|
482
|
+
const execAsync = promisify(exec);
|
|
483
|
+
// Try to check if command exists
|
|
484
|
+
await execAsync(`which ${terminal.command}`, { timeout: 5000 });
|
|
485
|
+
return { available: true };
|
|
486
|
+
}
|
|
487
|
+
catch {
|
|
488
|
+
return {
|
|
489
|
+
available: false,
|
|
490
|
+
error: `Terminal command '${terminal.command}' not found in PATH`,
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
// Private methods
|
|
495
|
+
async ensureSettingsDirectory() {
|
|
496
|
+
const settingsDir = path.dirname(this.settingsPath);
|
|
497
|
+
try {
|
|
498
|
+
await fs.access(settingsDir);
|
|
499
|
+
}
|
|
500
|
+
catch {
|
|
501
|
+
await fs.mkdir(settingsDir, { recursive: true });
|
|
502
|
+
log.info('Created settings directory', { settingsDir }, 'settings-service');
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
async loadSettings() {
|
|
506
|
+
try {
|
|
507
|
+
await fs.access(this.settingsPath);
|
|
508
|
+
const data = await fs.readFile(this.settingsPath, 'utf-8');
|
|
509
|
+
const parsed = JSON.parse(data);
|
|
510
|
+
// Validate and merge with defaults
|
|
511
|
+
const validated = VibemanSettingsSchema.parse(parsed);
|
|
512
|
+
this.settings = this.mergeWithDefaults(validated);
|
|
513
|
+
log.info('Settings loaded from file', { settingsPath: this.settingsPath }, 'settings-service');
|
|
514
|
+
}
|
|
515
|
+
catch (error) {
|
|
516
|
+
log.info('Settings file not found or invalid, using defaults', { error: String(error) }, 'settings-service');
|
|
517
|
+
this.settings = { ...DEFAULT_SETTINGS };
|
|
518
|
+
await this.saveSettings();
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
async saveSettings() {
|
|
522
|
+
const data = JSON.stringify(this.settings, null, 2);
|
|
523
|
+
await fs.writeFile(this.settingsPath, data, 'utf-8');
|
|
524
|
+
log.debug('Settings saved to file', { settingsPath: this.settingsPath }, 'settings-service');
|
|
525
|
+
}
|
|
526
|
+
async fileExists(filePath) {
|
|
527
|
+
try {
|
|
528
|
+
await fs.access(filePath);
|
|
529
|
+
return true;
|
|
530
|
+
}
|
|
531
|
+
catch {
|
|
532
|
+
return false;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
mergeWithDefaults(settings) {
|
|
536
|
+
// Deep merge settings with defaults to handle missing properties
|
|
537
|
+
return {
|
|
538
|
+
...DEFAULT_SETTINGS,
|
|
539
|
+
...settings,
|
|
540
|
+
agents: {
|
|
541
|
+
...DEFAULT_SETTINGS.agents,
|
|
542
|
+
...settings.agents,
|
|
543
|
+
codingAgent: {
|
|
544
|
+
...DEFAULT_SETTINGS.agents.codingAgent,
|
|
545
|
+
...settings.agents?.codingAgent,
|
|
546
|
+
},
|
|
547
|
+
judgeAgent: {
|
|
548
|
+
...DEFAULT_SETTINGS.agents.judgeAgent,
|
|
549
|
+
...settings.agents?.judgeAgent,
|
|
550
|
+
},
|
|
551
|
+
providers: {
|
|
552
|
+
...(DEFAULT_SETTINGS.agents.providers ?? {}),
|
|
553
|
+
...(settings.agents?.providers ?? {}),
|
|
554
|
+
},
|
|
555
|
+
defaultPrompts: {
|
|
556
|
+
...DEFAULT_SETTINGS.agents.defaultPrompts,
|
|
557
|
+
...settings.agents?.defaultPrompts,
|
|
558
|
+
},
|
|
559
|
+
},
|
|
560
|
+
defaultWorkflow: {
|
|
561
|
+
...DEFAULT_SETTINGS.defaultWorkflow,
|
|
562
|
+
...settings.defaultWorkflow,
|
|
563
|
+
},
|
|
564
|
+
quality: {
|
|
565
|
+
...DEFAULT_SETTINGS.quality,
|
|
566
|
+
...settings.quality,
|
|
567
|
+
checks: (settings.quality?.checks ?? DEFAULT_SETTINGS.quality?.checks),
|
|
568
|
+
},
|
|
569
|
+
development: {
|
|
570
|
+
...DEFAULT_SETTINGS.development,
|
|
571
|
+
...settings.development,
|
|
572
|
+
git: {
|
|
573
|
+
...DEFAULT_SETTINGS.development.git,
|
|
574
|
+
...settings.development?.git,
|
|
575
|
+
},
|
|
576
|
+
editor: {
|
|
577
|
+
...DEFAULT_SETTINGS.development.editor,
|
|
578
|
+
...settings.development?.editor,
|
|
579
|
+
},
|
|
580
|
+
terminal: {
|
|
581
|
+
...DEFAULT_SETTINGS.development.terminal,
|
|
582
|
+
...settings.development?.terminal,
|
|
583
|
+
},
|
|
584
|
+
},
|
|
585
|
+
server: {
|
|
586
|
+
...DEFAULT_SETTINGS.server,
|
|
587
|
+
...settings.server,
|
|
588
|
+
},
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
applySingleUpdate(target, path, value) {
|
|
592
|
+
if (path.length === 0) {
|
|
593
|
+
throw new Error('Empty path not allowed');
|
|
594
|
+
}
|
|
595
|
+
if (path.length === 1) {
|
|
596
|
+
target[path[0]] = value;
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
const [head, ...tail] = path;
|
|
600
|
+
if (!target[head] || typeof target[head] !== 'object') {
|
|
601
|
+
target[head] = {};
|
|
602
|
+
}
|
|
603
|
+
this.applySingleUpdate(target[head], tail, value);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
// Singleton instance
|
|
607
|
+
let settingsService = null;
|
|
608
|
+
export function getSettingsService(config) {
|
|
609
|
+
settingsService ?? (settingsService = new SettingsService(config));
|
|
610
|
+
return settingsService;
|
|
611
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
export interface FileWatcherOptions {
|
|
3
|
+
recursive?: boolean;
|
|
4
|
+
debounceMs?: number;
|
|
5
|
+
fileFilter?: (filename: string) => boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface FileChangeEvent {
|
|
8
|
+
type: 'created' | 'updated' | 'deleted';
|
|
9
|
+
filename: string;
|
|
10
|
+
fullPath: string;
|
|
11
|
+
}
|
|
12
|
+
export declare class FileWatcher extends EventEmitter {
|
|
13
|
+
private watcher;
|
|
14
|
+
private watchDir;
|
|
15
|
+
private options;
|
|
16
|
+
private pendingChanges;
|
|
17
|
+
constructor(watchDir: string, options?: FileWatcherOptions);
|
|
18
|
+
start(): void;
|
|
19
|
+
stop(): void;
|
|
20
|
+
private debounceFileChange;
|
|
21
|
+
private handleFileChange;
|
|
22
|
+
private fileExists;
|
|
23
|
+
}
|