@geraldmaron/construct 1.0.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.
- package/.env.example +69 -0
- package/LICENSE +21 -0
- package/README.md +344 -0
- package/agents/prompts/cx-accessibility.md +29 -0
- package/agents/prompts/cx-ai-engineer.md +31 -0
- package/agents/prompts/cx-architect.md +40 -0
- package/agents/prompts/cx-business-strategist.md +25 -0
- package/agents/prompts/cx-data-analyst.md +30 -0
- package/agents/prompts/cx-data-engineer.md +32 -0
- package/agents/prompts/cx-debugger.md +27 -0
- package/agents/prompts/cx-designer.md +33 -0
- package/agents/prompts/cx-devil-advocate.md +36 -0
- package/agents/prompts/cx-docs-keeper.md +50 -0
- package/agents/prompts/cx-engineer.md +36 -0
- package/agents/prompts/cx-evaluator.md +26 -0
- package/agents/prompts/cx-explorer.md +38 -0
- package/agents/prompts/cx-legal-compliance.md +25 -0
- package/agents/prompts/cx-operations.md +28 -0
- package/agents/prompts/cx-orchestrator.md +30 -0
- package/agents/prompts/cx-platform-engineer.md +25 -0
- package/agents/prompts/cx-product-manager.md +30 -0
- package/agents/prompts/cx-qa.md +42 -0
- package/agents/prompts/cx-rd-lead.md +25 -0
- package/agents/prompts/cx-release-manager.md +31 -0
- package/agents/prompts/cx-researcher.md +31 -0
- package/agents/prompts/cx-reviewer.md +36 -0
- package/agents/prompts/cx-security.md +37 -0
- package/agents/prompts/cx-sre.md +26 -0
- package/agents/prompts/cx-test-automation.md +35 -0
- package/agents/prompts/cx-trace-reviewer.md +76 -0
- package/agents/prompts/cx-ux-researcher.md +30 -0
- package/agents/registry.json +879 -0
- package/agents/teams.json +94 -0
- package/bin/construct +613 -0
- package/commands/build/feature.md +21 -0
- package/commands/build/fix.md +20 -0
- package/commands/design/access.md +19 -0
- package/commands/design/flow.md +16 -0
- package/commands/design/ui.md +18 -0
- package/commands/measure/experiment.md +18 -0
- package/commands/measure/metrics.md +18 -0
- package/commands/measure/results.md +18 -0
- package/commands/plan/api.md +19 -0
- package/commands/plan/challenge.md +18 -0
- package/commands/plan/decide.md +20 -0
- package/commands/plan/feature.md +49 -0
- package/commands/plan/requirements.md +24 -0
- package/commands/remember/context.md +26 -0
- package/commands/remember/handoff.md +19 -0
- package/commands/remember/runbook.md +20 -0
- package/commands/review/code.md +21 -0
- package/commands/review/quality.md +19 -0
- package/commands/review/security.md +16 -0
- package/commands/ship/ready.md +19 -0
- package/commands/ship/release.md +19 -0
- package/commands/ship/status.md +18 -0
- package/commands/understand/docs.md +22 -0
- package/commands/understand/research.md +22 -0
- package/commands/understand/this.md +23 -0
- package/commands/understand/why.md +18 -0
- package/commands/work/clean.md +32 -0
- package/commands/work/drive.md +69 -0
- package/commands/work/optimize-prompts.md +46 -0
- package/commands/work/parallel-review.md +40 -0
- package/db/migrations/001_init.sql +38 -0
- package/langfuse/docker-compose.yml +82 -0
- package/lib/audit-skills.mjs +118 -0
- package/lib/auto-docs.mjs +293 -0
- package/lib/cli-commands.mjs +409 -0
- package/lib/codex-config.mjs +114 -0
- package/lib/comment-lint.mjs +191 -0
- package/lib/completions.mjs +170 -0
- package/lib/context-state.mjs +99 -0
- package/lib/cost.mjs +213 -0
- package/lib/distill.mjs +403 -0
- package/lib/efficiency.mjs +139 -0
- package/lib/env-config.mjs +53 -0
- package/lib/eval-harness.mjs +59 -0
- package/lib/features.mjs +209 -0
- package/lib/headhunt.mjs +597 -0
- package/lib/hooks/adaptive-lint.mjs +112 -0
- package/lib/hooks/agent-tracker.mjs +50 -0
- package/lib/hooks/bootstrap-guard.mjs +90 -0
- package/lib/hooks/comment-lint.mjs +26 -0
- package/lib/hooks/config-protection.mjs +52 -0
- package/lib/hooks/console-warn.mjs +43 -0
- package/lib/hooks/context-window-recovery.mjs +90 -0
- package/lib/hooks/continuation-enforcer.mjs +72 -0
- package/lib/hooks/dep-audit.mjs +155 -0
- package/lib/hooks/drive-guard.mjs +89 -0
- package/lib/hooks/edit-accumulator.mjs +36 -0
- package/lib/hooks/edit-error-recovery.mjs +46 -0
- package/lib/hooks/edit-guard.mjs +109 -0
- package/lib/hooks/env-check.mjs +80 -0
- package/lib/hooks/guard-bash.mjs +83 -0
- package/lib/hooks/mcp-audit.mjs +57 -0
- package/lib/hooks/mcp-health-check.mjs +51 -0
- package/lib/hooks/mcp-task-scope.mjs +47 -0
- package/lib/hooks/model-fallback.mjs +105 -0
- package/lib/hooks/pre-compact.mjs +212 -0
- package/lib/hooks/pre-push-gate.mjs +129 -0
- package/lib/hooks/read-tracker.mjs +129 -0
- package/lib/hooks/registry-sync.mjs +23 -0
- package/lib/hooks/scan-secrets.mjs +76 -0
- package/lib/hooks/session-start.mjs +95 -0
- package/lib/hooks/stop-notify.mjs +191 -0
- package/lib/hooks/stop-typecheck.mjs +83 -0
- package/lib/hooks/task-completed-guard.mjs +43 -0
- package/lib/hooks/teammate-idle-guard.mjs +54 -0
- package/lib/hooks/workflow-guard.mjs +62 -0
- package/lib/host-capabilities.mjs +123 -0
- package/lib/init-docs.mjs +382 -0
- package/lib/mcp/server.mjs +1123 -0
- package/lib/mcp-catalog.json +243 -0
- package/lib/mcp-manager.mjs +433 -0
- package/lib/mcp-platform-config.mjs +104 -0
- package/lib/model-router.mjs +810 -0
- package/lib/opencode-config.mjs +44 -0
- package/lib/opencode-runtime-plugin.mjs +909 -0
- package/lib/opencode-telemetry.mjs +433 -0
- package/lib/orchestration-policy.mjs +258 -0
- package/lib/prompt-composer.mjs +196 -0
- package/lib/prompt-metadata.mjs +68 -0
- package/lib/review.mjs +429 -0
- package/lib/role-preload.mjs +52 -0
- package/lib/schemas/decision.json +50 -0
- package/lib/schemas/implementation.json +51 -0
- package/lib/schemas/review-report.json +32 -0
- package/lib/schemas/test-report.json +43 -0
- package/lib/server/index.mjs +334 -0
- package/lib/server/static/app.js +841 -0
- package/lib/server/static/index.html +820 -0
- package/lib/service-manager.mjs +225 -0
- package/lib/setup.mjs +319 -0
- package/lib/status.mjs +707 -0
- package/lib/storage/backend.mjs +44 -0
- package/lib/storage/embeddings.mjs +70 -0
- package/lib/storage/hybrid-query.mjs +184 -0
- package/lib/storage/sql-store.mjs +55 -0
- package/lib/storage/state-source.mjs +101 -0
- package/lib/storage/sync.mjs +164 -0
- package/lib/storage/vector-store.mjs +59 -0
- package/lib/telemetry/backends/langfuse.mjs +58 -0
- package/lib/telemetry/backends/noop.mjs +16 -0
- package/lib/telemetry/langfuse-ingest.mjs +108 -0
- package/lib/telemetry/langfuse-model-sync.mjs +270 -0
- package/lib/telemetry/team-rollup.mjs +143 -0
- package/lib/toolkit-env.mjs +30 -0
- package/lib/validator.mjs +181 -0
- package/lib/workflow-state.mjs +794 -0
- package/package.json +53 -0
- package/personas/construct.md +94 -0
- package/platforms/claude/CLAUDE.md +30 -0
- package/platforms/claude/settings.template.json +472 -0
- package/platforms/opencode/config.template.json +65 -0
- package/platforms/opencode/plugins/construct-fallback.js +5 -0
- package/platforms/opencode/sync-config.mjs +69 -0
- package/rules/common/agents.md +55 -0
- package/rules/common/code-review.md +129 -0
- package/rules/common/coding-style.md +95 -0
- package/rules/common/comments.md +139 -0
- package/rules/common/cx-agent-routing.md +61 -0
- package/rules/common/cx-skill-routing.md +11 -0
- package/rules/common/development-workflow.md +49 -0
- package/rules/common/git-workflow.md +29 -0
- package/rules/common/hooks.md +35 -0
- package/rules/common/patterns.md +36 -0
- package/rules/common/performance.md +71 -0
- package/rules/common/security.md +34 -0
- package/rules/common/testing.md +62 -0
- package/rules/golang/coding-style.md +37 -0
- package/rules/golang/hooks.md +22 -0
- package/rules/golang/patterns.md +50 -0
- package/rules/golang/security.md +39 -0
- package/rules/golang/testing.md +36 -0
- package/rules/python/coding-style.md +47 -0
- package/rules/python/hooks.md +24 -0
- package/rules/python/patterns.md +44 -0
- package/rules/python/security.md +35 -0
- package/rules/python/testing.md +43 -0
- package/rules/swift/coding-style.md +52 -0
- package/rules/swift/hooks.md +25 -0
- package/rules/swift/patterns.md +71 -0
- package/rules/swift/security.md +38 -0
- package/rules/swift/testing.md +50 -0
- package/rules/typescript/coding-style.md +204 -0
- package/rules/typescript/hooks.md +27 -0
- package/rules/typescript/patterns.md +57 -0
- package/rules/typescript/security.md +33 -0
- package/rules/typescript/testing.md +23 -0
- package/rules/web/coding-style.md +101 -0
- package/rules/web/design-quality.md +68 -0
- package/rules/web/hooks.md +125 -0
- package/rules/web/patterns.md +84 -0
- package/rules/web/performance.md +69 -0
- package/rules/web/security.md +62 -0
- package/rules/web/testing.md +60 -0
- package/rules/zh/README.md +113 -0
- package/rules/zh/agents.md +55 -0
- package/rules/zh/code-review.md +129 -0
- package/rules/zh/coding-style.md +53 -0
- package/rules/zh/development-workflow.md +49 -0
- package/rules/zh/git-workflow.md +29 -0
- package/rules/zh/hooks.md +35 -0
- package/rules/zh/patterns.md +36 -0
- package/rules/zh/performance.md +60 -0
- package/rules/zh/security.md +34 -0
- package/rules/zh/testing.md +34 -0
- package/skills/ai/agent-dev.md +102 -0
- package/skills/ai/llm-security.md +105 -0
- package/skills/ai/ml-ops.md +169 -0
- package/skills/ai/orchestration-workflow.md +87 -0
- package/skills/ai/prompt-and-eval.md +114 -0
- package/skills/ai/prompt-optimizer.md +114 -0
- package/skills/ai/rag-system.md +104 -0
- package/skills/architecture/api-design.md +100 -0
- package/skills/architecture/caching.md +101 -0
- package/skills/architecture/cloud-native.md +97 -0
- package/skills/architecture/message-queue.md +100 -0
- package/skills/architecture/security-arch.md +105 -0
- package/skills/development/cpp.md +127 -0
- package/skills/development/go.md +129 -0
- package/skills/development/java.md +134 -0
- package/skills/development/kotlin.md +140 -0
- package/skills/development/mobile-crossplatform.md +144 -0
- package/skills/development/python.md +109 -0
- package/skills/development/rust.md +127 -0
- package/skills/development/shell.md +127 -0
- package/skills/development/swift.md +129 -0
- package/skills/development/typescript.md +129 -0
- package/skills/devops/ci-cd.md +108 -0
- package/skills/devops/containerization.md +106 -0
- package/skills/devops/cost-optimization.md +105 -0
- package/skills/devops/data-engineering.md +181 -0
- package/skills/devops/database.md +95 -0
- package/skills/devops/dependency-management.md +91 -0
- package/skills/devops/devsecops.md +96 -0
- package/skills/devops/git-workflow.md +100 -0
- package/skills/devops/incident-response.md +167 -0
- package/skills/devops/monorepo.md +87 -0
- package/skills/devops/observability.md +103 -0
- package/skills/devops/performance.md +104 -0
- package/skills/devops/testing.md +104 -0
- package/skills/docs/adr-workflow.md +32 -0
- package/skills/docs/backlog-proposal-workflow.md +19 -0
- package/skills/docs/customer-profile-workflow.md +22 -0
- package/skills/docs/evidence-ingest-workflow.md +23 -0
- package/skills/docs/init-docs.md +160 -0
- package/skills/docs/init-project.md +50 -0
- package/skills/docs/prd-workflow.md +57 -0
- package/skills/docs/prfaq-workflow.md +25 -0
- package/skills/docs/product-intelligence-review.md +22 -0
- package/skills/docs/product-intelligence-workflow.md +62 -0
- package/skills/docs/product-signal-workflow.md +27 -0
- package/skills/docs/research-workflow.md +28 -0
- package/skills/docs/runbook-workflow.md +24 -0
- package/skills/exploration/repo-map.md +297 -0
- package/skills/frameworks/django.md +159 -0
- package/skills/frameworks/nextjs.md +148 -0
- package/skills/frameworks/react.md +132 -0
- package/skills/frameworks/spring-boot.md +165 -0
- package/skills/frontend-design/accessibility.md +153 -0
- package/skills/frontend-design/component-patterns.md +111 -0
- package/skills/frontend-design/engineering.md +122 -0
- package/skills/frontend-design/state-management.md +118 -0
- package/skills/frontend-design/ui-aesthetics.md +102 -0
- package/skills/frontend-design/ux-principles.md +126 -0
- package/skills/quality-gates/review-work.md +90 -0
- package/skills/quality-gates/verify-change.md +94 -0
- package/skills/quality-gates/verify-module.md +96 -0
- package/skills/quality-gates/verify-quality.md +93 -0
- package/skills/quality-gates/verify-security.md +95 -0
- package/skills/roles/architect.ai-systems.md +37 -0
- package/skills/roles/architect.data.md +37 -0
- package/skills/roles/architect.enterprise.md +37 -0
- package/skills/roles/architect.integration.md +37 -0
- package/skills/roles/architect.md +68 -0
- package/skills/roles/architect.platform.md +37 -0
- package/skills/roles/data-analyst.experiment.md +37 -0
- package/skills/roles/data-analyst.md +68 -0
- package/skills/roles/data-analyst.product-intelligence.md +37 -0
- package/skills/roles/data-analyst.product.md +37 -0
- package/skills/roles/data-analyst.telemetry.md +37 -0
- package/skills/roles/data-engineer.pipeline.md +37 -0
- package/skills/roles/data-engineer.vector-retrieval.md +37 -0
- package/skills/roles/data-engineer.warehouse.md +37 -0
- package/skills/roles/debugger.md +68 -0
- package/skills/roles/designer.accessibility.md +43 -0
- package/skills/roles/designer.md +68 -0
- package/skills/roles/engineer.ai.md +49 -0
- package/skills/roles/engineer.data.md +49 -0
- package/skills/roles/engineer.md +85 -0
- package/skills/roles/engineer.platform.md +49 -0
- package/skills/roles/operator.docs.md +43 -0
- package/skills/roles/operator.md +68 -0
- package/skills/roles/operator.release.md +43 -0
- package/skills/roles/operator.sre.md +43 -0
- package/skills/roles/orchestrator.md +66 -0
- package/skills/roles/product-manager.ai-product.md +37 -0
- package/skills/roles/product-manager.business-strategy.md +43 -0
- package/skills/roles/product-manager.enterprise.md +37 -0
- package/skills/roles/product-manager.growth.md +37 -0
- package/skills/roles/product-manager.md +67 -0
- package/skills/roles/product-manager.platform.md +37 -0
- package/skills/roles/product-manager.product.md +37 -0
- package/skills/roles/qa.ai-eval.md +37 -0
- package/skills/roles/qa.api-contract.md +37 -0
- package/skills/roles/qa.data-pipeline.md +37 -0
- package/skills/roles/qa.md +68 -0
- package/skills/roles/qa.test-automation.md +49 -0
- package/skills/roles/qa.web-ui.md +37 -0
- package/skills/roles/researcher.explorer.md +43 -0
- package/skills/roles/researcher.md +68 -0
- package/skills/roles/researcher.ux.md +43 -0
- package/skills/roles/reviewer.devil-advocate.md +43 -0
- package/skills/roles/reviewer.evaluator.md +43 -0
- package/skills/roles/reviewer.md +68 -0
- package/skills/roles/reviewer.trace.md +43 -0
- package/skills/roles/security.ai.md +37 -0
- package/skills/roles/security.appsec.md +37 -0
- package/skills/roles/security.cloud.md +37 -0
- package/skills/roles/security.legal-compliance.md +49 -0
- package/skills/roles/security.md +68 -0
- package/skills/roles/security.privacy.md +37 -0
- package/skills/roles/security.supply-chain.md +37 -0
- package/skills/routing.md +139 -0
- package/skills/security/blue-team.md +83 -0
- package/skills/security/code-audit.md +94 -0
- package/skills/security/pentest.md +84 -0
- package/skills/security/red-team.md +81 -0
- package/skills/security/threat-intel.md +89 -0
- package/skills/security/vuln-research.md +87 -0
- package/skills/utility/clean-code.md +165 -0
- package/sync-agents.mjs +899 -0
- package/templates/docs/adr.md +27 -0
- package/templates/docs/backlog-proposal.md +26 -0
- package/templates/docs/customer-profile.md +34 -0
- package/templates/docs/evidence-brief.md +37 -0
- package/templates/docs/incident-report.md +46 -0
- package/templates/docs/memo.md +27 -0
- package/templates/docs/meta-prd.md +56 -0
- package/templates/docs/one-pager.md +24 -0
- package/templates/docs/prd-business.md +61 -0
- package/templates/docs/prd-platform.md +62 -0
- package/templates/docs/prd.md +47 -0
- package/templates/docs/prfaq.md +31 -0
- package/templates/docs/product-intelligence-report.md +31 -0
- package/templates/docs/research-brief.md +29 -0
- package/templates/docs/rfc-platform.md +60 -0
- package/templates/docs/rfc.md +46 -0
- package/templates/docs/runbook.md +33 -0
- package/templates/docs/signal-brief.md +25 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/service-manager.mjs — start, stop, and describe Construct runtime services.
|
|
3
|
+
*
|
|
4
|
+
* Manages the dashboard process, local Langfuse (Docker), memory server (cm),
|
|
5
|
+
* and OpenCode. startServices() is the single entry point for `construct up`;
|
|
6
|
+
* it spawns whatever is available and returns a result per service.
|
|
7
|
+
*/
|
|
8
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
9
|
+
import fs from 'node:fs';
|
|
10
|
+
import os from 'node:os';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
|
|
13
|
+
import { findAvailablePort } from './host-capabilities.mjs';
|
|
14
|
+
import { getUserEnvPath, loadConstructEnv, writeEnvValues } from './env-config.mjs';
|
|
15
|
+
import { detectDockerCompose } from './setup.mjs';
|
|
16
|
+
|
|
17
|
+
const DASHBOARD_STATE_FILE = 'dashboard.json';
|
|
18
|
+
|
|
19
|
+
const LANGFUSE_LOCAL_BASEURL = 'http://localhost:3000';
|
|
20
|
+
const LANGFUSE_LOCAL_PUBLIC_KEY = 'pk-lf-construct-local';
|
|
21
|
+
const LANGFUSE_LOCAL_SECRET_KEY = 'sk-lf-construct-local';
|
|
22
|
+
|
|
23
|
+
function runtimeStateDir(homeDir = os.homedir()) {
|
|
24
|
+
return path.join(homeDir, '.construct', 'runtime');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function dashboardStatePath(homeDir = os.homedir()) {
|
|
28
|
+
return path.join(runtimeStateDir(homeDir), DASHBOARD_STATE_FILE);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function readJson(filePath) {
|
|
32
|
+
if (!fs.existsSync(filePath)) return null;
|
|
33
|
+
try {
|
|
34
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
35
|
+
} catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function writeJson(filePath, value) {
|
|
41
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
42
|
+
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function processExists(pid) {
|
|
46
|
+
if (!pid || Number(pid) <= 0) return false;
|
|
47
|
+
try {
|
|
48
|
+
process.kill(Number(pid), 0);
|
|
49
|
+
return true;
|
|
50
|
+
} catch {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function spawnDetached(command, args, homeDir, logFile) {
|
|
56
|
+
const logPath = path.join(runtimeStateDir(homeDir), logFile);
|
|
57
|
+
fs.mkdirSync(path.dirname(logPath), { recursive: true });
|
|
58
|
+
const fd = fs.openSync(logPath, 'a');
|
|
59
|
+
const child = spawn(command, args, { detached: true, stdio: ['ignore', fd, fd] });
|
|
60
|
+
child.unref();
|
|
61
|
+
return child;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function readDashboardState(homeDir = os.homedir()) {
|
|
65
|
+
const state = readJson(dashboardStatePath(homeDir));
|
|
66
|
+
if (!state) return null;
|
|
67
|
+
if (!processExists(state.pid)) return null;
|
|
68
|
+
return state;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function clearDashboardState(homeDir = os.homedir()) {
|
|
72
|
+
const filePath = dashboardStatePath(homeDir);
|
|
73
|
+
if (fs.existsSync(filePath)) fs.rmSync(filePath, { force: true });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function stopDashboard(homeDir = os.homedir()) {
|
|
77
|
+
const state = readJson(dashboardStatePath(homeDir));
|
|
78
|
+
if (!state?.pid) {
|
|
79
|
+
clearDashboardState(homeDir);
|
|
80
|
+
return { stopped: false, reason: 'not-running' };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!processExists(state.pid)) {
|
|
84
|
+
clearDashboardState(homeDir);
|
|
85
|
+
return { stopped: false, reason: 'stale-state' };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
process.kill(Number(state.pid), 'SIGTERM');
|
|
89
|
+
clearDashboardState(homeDir);
|
|
90
|
+
return { stopped: true, pid: Number(state.pid), port: state.port };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export async function startDashboard({ rootDir, homeDir = os.homedir(), preferredPort = 4242 } = {}) {
|
|
94
|
+
const existing = readDashboardState(homeDir);
|
|
95
|
+
if (existing) return { started: false, reused: true, pid: existing.pid, port: existing.port, url: existing.url };
|
|
96
|
+
|
|
97
|
+
const port = await findAvailablePort(preferredPort);
|
|
98
|
+
const outPath = path.join(runtimeStateDir(homeDir), 'dashboard.log');
|
|
99
|
+
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
100
|
+
const out = fs.openSync(outPath, 'a');
|
|
101
|
+
const err = fs.openSync(outPath, 'a');
|
|
102
|
+
const child = spawn(process.execPath, [path.join(rootDir, 'lib', 'server', 'index.mjs')], {
|
|
103
|
+
detached: true,
|
|
104
|
+
stdio: ['ignore', out, err],
|
|
105
|
+
env: { ...process.env, PORT: String(port) },
|
|
106
|
+
});
|
|
107
|
+
child.unref();
|
|
108
|
+
|
|
109
|
+
const state = {
|
|
110
|
+
pid: child.pid,
|
|
111
|
+
port,
|
|
112
|
+
url: `http://127.0.0.1:${port}`,
|
|
113
|
+
startedAt: new Date().toISOString(),
|
|
114
|
+
logPath: outPath,
|
|
115
|
+
};
|
|
116
|
+
writeJson(dashboardStatePath(homeDir), state);
|
|
117
|
+
return { started: true, reused: false, ...state };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export async function getRuntimePorts(homeDir = os.homedir()) {
|
|
121
|
+
const dashboard = readDashboardState(homeDir);
|
|
122
|
+
return {
|
|
123
|
+
dashboard: dashboard?.port ?? await findAvailablePort(4242),
|
|
124
|
+
memory: await findAvailablePort(8765),
|
|
125
|
+
bridge: await findAvailablePort(5173),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export async function describeRuntimeSupport() {
|
|
130
|
+
function commandExists(command) {
|
|
131
|
+
try {
|
|
132
|
+
const checker = process.platform === 'win32' ? 'where' : 'which';
|
|
133
|
+
return spawnSync(checker, [command], { stdio: 'ignore' }).status === 0;
|
|
134
|
+
} catch {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
tmux: commandExists('tmux'),
|
|
140
|
+
docker: commandExists('docker'),
|
|
141
|
+
cm: commandExists('cm'),
|
|
142
|
+
opencode: commandExists('opencode'),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export async function startServices({ rootDir, homeDir = os.homedir() } = {}) {
|
|
147
|
+
const support = await describeRuntimeSupport();
|
|
148
|
+
const ports = await getRuntimePorts(homeDir);
|
|
149
|
+
const envPath = getUserEnvPath(homeDir);
|
|
150
|
+
|
|
151
|
+
writeEnvValues(envPath, {
|
|
152
|
+
DASHBOARD_PORT: String(ports.dashboard),
|
|
153
|
+
MEMORY_PORT: String(ports.memory),
|
|
154
|
+
BRIDGE_PORT: String(ports.bridge),
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const results = [];
|
|
158
|
+
|
|
159
|
+
// Dashboard
|
|
160
|
+
const dashboard = await startDashboard({ rootDir, homeDir, preferredPort: ports.dashboard });
|
|
161
|
+
results.push({
|
|
162
|
+
name: 'Dashboard',
|
|
163
|
+
url: dashboard.url,
|
|
164
|
+
status: dashboard.reused ? 'reused' : 'started',
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// Langfuse — start local Docker unless a remote URL is explicitly configured
|
|
168
|
+
const liveEnv = loadConstructEnv({ rootDir, homeDir });
|
|
169
|
+
const langfuseUrl = liveEnv.LANGFUSE_BASEURL ?? '';
|
|
170
|
+
const isRemoteLangfuse = langfuseUrl && !langfuseUrl.includes('localhost') && !langfuseUrl.includes('127.0.0.1');
|
|
171
|
+
|
|
172
|
+
if (isRemoteLangfuse) {
|
|
173
|
+
results.push({ name: 'Langfuse', url: langfuseUrl, status: 'configured' });
|
|
174
|
+
} else {
|
|
175
|
+
const composeRunner = detectDockerCompose();
|
|
176
|
+
if (composeRunner) {
|
|
177
|
+
const composeFile = path.join(rootDir, 'langfuse', 'docker-compose.yml');
|
|
178
|
+
const r = spawnSync(
|
|
179
|
+
composeRunner.command,
|
|
180
|
+
[...composeRunner.argsPrefix, '-p', 'construct-langfuse', '-f', composeFile, 'up', '-d'],
|
|
181
|
+
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] },
|
|
182
|
+
);
|
|
183
|
+
if (r.status === 0) {
|
|
184
|
+
writeEnvValues(envPath, {
|
|
185
|
+
LANGFUSE_BASEURL: LANGFUSE_LOCAL_BASEURL,
|
|
186
|
+
LANGFUSE_PUBLIC_KEY: LANGFUSE_LOCAL_PUBLIC_KEY,
|
|
187
|
+
LANGFUSE_SECRET_KEY: LANGFUSE_LOCAL_SECRET_KEY,
|
|
188
|
+
});
|
|
189
|
+
results.push({ name: 'Langfuse', url: LANGFUSE_LOCAL_BASEURL, status: 'started', note: 'containers starting — allow 30-60s' });
|
|
190
|
+
} else {
|
|
191
|
+
const msg = (r.stderr || r.stdout || 'docker compose failed').trim().split('\n')[0];
|
|
192
|
+
results.push({ name: 'Langfuse', status: 'failed', note: msg });
|
|
193
|
+
}
|
|
194
|
+
} else {
|
|
195
|
+
results.push({ name: 'Langfuse', status: 'unavailable', note: 'Docker not available' });
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Memory (cm)
|
|
200
|
+
if (support.cm) {
|
|
201
|
+
spawnDetached('cm', ['serve', '--port', String(ports.memory)], homeDir, 'cm.log');
|
|
202
|
+
results.push({ name: 'Memory (cm)', url: `http://127.0.0.1:${ports.memory}`, status: 'started' });
|
|
203
|
+
} else {
|
|
204
|
+
results.push({ name: 'Memory (cm)', status: 'unavailable', note: 'cm not installed — run: npm install -g @cass-ai/memory' });
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// OpenCode (optional)
|
|
208
|
+
if (support.opencode) {
|
|
209
|
+
spawnDetached('opencode', ['serve', '--port', String(ports.bridge)], homeDir, 'opencode.log');
|
|
210
|
+
results.push({ name: 'OpenCode', url: `http://127.0.0.1:${ports.bridge}`, status: 'started' });
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return { support, ports, results };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export async function stopServices({ homeDir = os.homedir() } = {}) {
|
|
217
|
+
const dashboard = stopDashboard(homeDir);
|
|
218
|
+
return {
|
|
219
|
+
stopped: dashboard.stopped ? ['dashboard'] : [],
|
|
220
|
+
guidance: dashboard.stopped
|
|
221
|
+
? []
|
|
222
|
+
: ['Stop Construct-related services with their native commands or your process manager.'],
|
|
223
|
+
dashboard,
|
|
224
|
+
};
|
|
225
|
+
}
|
package/lib/setup.mjs
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* lib/setup.mjs — <one-line purpose>
|
|
4
|
+
*
|
|
5
|
+
* <2–6 line summary.>
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import os from 'node:os';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import { spawnSync } from 'node:child_process';
|
|
12
|
+
|
|
13
|
+
import { ensureUserConfigDir, getUserEnvPath, writeEnvValues } from './env-config.mjs';
|
|
14
|
+
import { getCanonicalOpenCodeConfigPath, readOpenCodeConfig, writeOpenCodeConfig } from './opencode-config.mjs';
|
|
15
|
+
import { syncFileStateToSql } from './storage/sync.mjs';
|
|
16
|
+
import { createSqlClient, closeSqlClient } from './storage/backend.mjs';
|
|
17
|
+
import { EMBEDDING_MODEL } from './storage/embeddings.mjs';
|
|
18
|
+
|
|
19
|
+
const ROOT_DIR = path.resolve(import.meta.dirname, '..');
|
|
20
|
+
const HOME = os.homedir();
|
|
21
|
+
const LOCAL_POSTGRES_PORT = '54329';
|
|
22
|
+
const LOCAL_POSTGRES_USER = 'construct';
|
|
23
|
+
const LOCAL_POSTGRES_PASSWORD = 'construct';
|
|
24
|
+
const LOCAL_POSTGRES_DB = 'construct';
|
|
25
|
+
const LOCAL_DATABASE_URL = `postgresql://${LOCAL_POSTGRES_USER}:${LOCAL_POSTGRES_PASSWORD}@127.0.0.1:${LOCAL_POSTGRES_PORT}/${LOCAL_POSTGRES_DB}`;
|
|
26
|
+
|
|
27
|
+
function printHelp() {
|
|
28
|
+
console.log(`Construct setup
|
|
29
|
+
|
|
30
|
+
Usage:
|
|
31
|
+
construct setup [--yes] [--no-docker]
|
|
32
|
+
|
|
33
|
+
What it does:
|
|
34
|
+
- creates ~/.construct/config.env
|
|
35
|
+
- ensures OpenCode config exists
|
|
36
|
+
- configures managed defaults for local vector retrieval
|
|
37
|
+
- starts local Postgres with Docker when available
|
|
38
|
+
- wires Memory, GitHub, and Langfuse configuration
|
|
39
|
+
- runs construct sync
|
|
40
|
+
- runs construct doctor
|
|
41
|
+
|
|
42
|
+
Use --yes to run without prompts and accept detected environment defaults.`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function runConstruct(argsList, { optional = false } = {}) {
|
|
46
|
+
const result = spawnSync(process.execPath, [path.join(ROOT_DIR, 'bin', 'construct'), ...argsList], {
|
|
47
|
+
stdio: 'inherit',
|
|
48
|
+
env: process.env,
|
|
49
|
+
});
|
|
50
|
+
if (result.status !== 0 && optional) {
|
|
51
|
+
console.log(`\nOptional setup step skipped: construct ${argsList.join(' ')}`);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (result.status !== 0) process.exit(result.status ?? 1);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function findCommand(command) {
|
|
58
|
+
const result = spawnSync('zsh', ['-lc', `command -v ${command}`], {
|
|
59
|
+
encoding: 'utf8',
|
|
60
|
+
env: process.env,
|
|
61
|
+
});
|
|
62
|
+
return result.status === 0 ? result.stdout.trim() : '';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function defaultVectorIndexPath(homeDir = HOME) {
|
|
66
|
+
return path.join(homeDir, '.construct', 'vector', 'index.json');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function buildManagedSetupValues({ homeDir = HOME, env = process.env, databaseUrl = '' } = {}) {
|
|
70
|
+
const values = {
|
|
71
|
+
CONSTRUCT_TRACE_BACKEND: env.CONSTRUCT_TRACE_BACKEND || 'langfuse',
|
|
72
|
+
LANGFUSE_BASEURL: env.LANGFUSE_BASEURL || 'https://cloud.langfuse.com',
|
|
73
|
+
CONSTRUCT_VECTOR_INDEX_PATH: env.CONSTRUCT_VECTOR_INDEX_PATH || defaultVectorIndexPath(homeDir),
|
|
74
|
+
CONSTRUCT_VECTOR_MODEL: env.CONSTRUCT_VECTOR_MODEL || EMBEDDING_MODEL,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const resolvedDatabaseUrl = databaseUrl || env.DATABASE_URL || '';
|
|
78
|
+
if (resolvedDatabaseUrl) values.DATABASE_URL = resolvedDatabaseUrl;
|
|
79
|
+
if (env.CONSTRUCT_VECTOR_URL) values.CONSTRUCT_VECTOR_URL = env.CONSTRUCT_VECTOR_URL;
|
|
80
|
+
if (env.LANGFUSE_PUBLIC_KEY) values.LANGFUSE_PUBLIC_KEY = env.LANGFUSE_PUBLIC_KEY;
|
|
81
|
+
if (env.LANGFUSE_SECRET_KEY) values.LANGFUSE_SECRET_KEY = env.LANGFUSE_SECRET_KEY;
|
|
82
|
+
if (env.LANGFUSE_PROJECT_ID) values.LANGFUSE_PROJECT_ID = env.LANGFUSE_PROJECT_ID;
|
|
83
|
+
return values;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function localPostgresComposePath(homeDir = HOME) {
|
|
87
|
+
return path.join(homeDir, '.construct', 'services', 'postgres', 'docker-compose.yml');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function writeLocalPostgresCompose(homeDir = HOME) {
|
|
91
|
+
const composePath = localPostgresComposePath(homeDir);
|
|
92
|
+
fs.mkdirSync(path.dirname(composePath), { recursive: true });
|
|
93
|
+
const content = `services:
|
|
94
|
+
postgres:
|
|
95
|
+
image: postgres:16-alpine
|
|
96
|
+
container_name: construct-postgres
|
|
97
|
+
restart: unless-stopped
|
|
98
|
+
environment:
|
|
99
|
+
POSTGRES_USER: ${LOCAL_POSTGRES_USER}
|
|
100
|
+
POSTGRES_PASSWORD: ${LOCAL_POSTGRES_PASSWORD}
|
|
101
|
+
POSTGRES_DB: ${LOCAL_POSTGRES_DB}
|
|
102
|
+
ports:
|
|
103
|
+
- "127.0.0.1:${LOCAL_POSTGRES_PORT}:5432"
|
|
104
|
+
volumes:
|
|
105
|
+
- construct-postgres-data:/var/lib/postgresql/data
|
|
106
|
+
healthcheck:
|
|
107
|
+
test: ["CMD-SHELL", "pg_isready -U ${LOCAL_POSTGRES_USER} -d ${LOCAL_POSTGRES_DB}"]
|
|
108
|
+
interval: 5s
|
|
109
|
+
timeout: 5s
|
|
110
|
+
retries: 20
|
|
111
|
+
|
|
112
|
+
volumes:
|
|
113
|
+
construct-postgres-data:
|
|
114
|
+
`;
|
|
115
|
+
fs.writeFileSync(composePath, content, 'utf8');
|
|
116
|
+
return composePath;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function runQuiet(command, args, { env = process.env, spawn = spawnSync } = {}) {
|
|
120
|
+
return spawn(command, args, {
|
|
121
|
+
encoding: 'utf8',
|
|
122
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
123
|
+
env,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function detectDockerCompose({ env = process.env, spawn = spawnSync } = {}) {
|
|
128
|
+
const docker = runQuiet('docker', ['info'], { env, spawn });
|
|
129
|
+
if (docker.status !== 0) return null;
|
|
130
|
+
const compose = runQuiet('docker', ['compose', 'version'], { env, spawn });
|
|
131
|
+
if (compose.status === 0) return { command: 'docker', argsPrefix: ['compose'] };
|
|
132
|
+
const dockerCompose = runQuiet('docker-compose', ['version'], { env, spawn });
|
|
133
|
+
if (dockerCompose.status === 0) return { command: 'docker-compose', argsPrefix: [] };
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function startManagedPostgres({ homeDir = HOME, env = process.env, spawn = spawnSync } = {}) {
|
|
138
|
+
const composeRunner = detectDockerCompose({ env, spawn });
|
|
139
|
+
if (!composeRunner) {
|
|
140
|
+
return {
|
|
141
|
+
status: 'skipped',
|
|
142
|
+
databaseUrl: env.DATABASE_URL || '',
|
|
143
|
+
message: 'Docker is not available; using existing DATABASE_URL if configured.',
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const composePath = writeLocalPostgresCompose(homeDir);
|
|
148
|
+
const result = runQuiet(
|
|
149
|
+
composeRunner.command,
|
|
150
|
+
[...composeRunner.argsPrefix, '-f', composePath, 'up', '-d', 'postgres'],
|
|
151
|
+
{ env, spawn },
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
if (result.status !== 0) {
|
|
155
|
+
return {
|
|
156
|
+
status: 'degraded',
|
|
157
|
+
databaseUrl: env.DATABASE_URL || '',
|
|
158
|
+
composePath,
|
|
159
|
+
message: (result.stderr || result.stdout || 'Docker compose failed').trim(),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
status: 'ok',
|
|
165
|
+
databaseUrl: LOCAL_DATABASE_URL,
|
|
166
|
+
composePath,
|
|
167
|
+
message: 'Managed local Postgres is running.',
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function waitForSqlReady(env, { attempts = 20, delayMs = 500 } = {}) {
|
|
172
|
+
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
173
|
+
const client = createSqlClient(env);
|
|
174
|
+
try {
|
|
175
|
+
await client`select 1 as ok`;
|
|
176
|
+
await closeSqlClient(client);
|
|
177
|
+
return true;
|
|
178
|
+
} catch {
|
|
179
|
+
await closeSqlClient(client).catch(() => {});
|
|
180
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function warnIfGlobalCommandIsUnavailable() {
|
|
187
|
+
const globalConstruct = findCommand('construct');
|
|
188
|
+
if (!globalConstruct) {
|
|
189
|
+
console.log('\nInstall warning: `construct` is not on PATH yet.');
|
|
190
|
+
console.log(' From this checkout, run: npm install -g .');
|
|
191
|
+
console.log(' Without cloning, run: npm install -g github:geraldmaron/construct');
|
|
192
|
+
console.log(' Do not use `npm install -g construct`; that npm name belongs to another project.');
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const version = spawnSync(globalConstruct, ['version'], {
|
|
197
|
+
encoding: 'utf8',
|
|
198
|
+
env: process.env,
|
|
199
|
+
});
|
|
200
|
+
if (version.status !== 0 || !/^construct v\d+\./.test(version.stdout.trim())) {
|
|
201
|
+
console.log(`\nInstall warning: PATH resolves \`construct\` to ${globalConstruct}, but it does not look like this CLI.`);
|
|
202
|
+
console.log(' Reinstall from this checkout with: npm install -g .');
|
|
203
|
+
console.log(' Or install from GitHub with: npm install -g github:geraldmaron/construct');
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function ensureOpenCodeConfig() {
|
|
208
|
+
const current = readOpenCodeConfig();
|
|
209
|
+
if (current.config) return current.file;
|
|
210
|
+
writeOpenCodeConfig({
|
|
211
|
+
$schema: 'https://opencode.ai/config.json',
|
|
212
|
+
mcp: {},
|
|
213
|
+
agent: {},
|
|
214
|
+
}, getCanonicalOpenCodeConfigPath());
|
|
215
|
+
return getCanonicalOpenCodeConfigPath();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export async function runSetup({ rootDir = ROOT_DIR, args = [], homeDir = HOME } = {}) {
|
|
219
|
+
const argSet = new Set(args);
|
|
220
|
+
const isYes = argSet.has('--yes');
|
|
221
|
+
const skipDocker = argSet.has('--no-docker');
|
|
222
|
+
|
|
223
|
+
if (argSet.has('--help') || argSet.has('-h')) {
|
|
224
|
+
printHelp();
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
console.log('Construct setup');
|
|
229
|
+
console.log('────────────────');
|
|
230
|
+
|
|
231
|
+
const envPath = ensureUserConfig(homeDir);
|
|
232
|
+
const opencodePath = ensureOpenCodeConfig();
|
|
233
|
+
|
|
234
|
+
console.log(`User config: ${envPath}`);
|
|
235
|
+
console.log(`OpenCode config: ${opencodePath}`);
|
|
236
|
+
warnIfGlobalCommandIsUnavailable();
|
|
237
|
+
|
|
238
|
+
const serviceResult = isYes && !skipDocker && !process.env.DATABASE_URL
|
|
239
|
+
? startManagedPostgres({ homeDir, env: process.env })
|
|
240
|
+
: { status: 'skipped', databaseUrl: process.env.DATABASE_URL || '', message: skipDocker ? 'Docker service setup skipped by flag.' : 'Using existing DATABASE_URL if configured.' };
|
|
241
|
+
|
|
242
|
+
fs.mkdirSync(path.dirname(defaultVectorIndexPath(homeDir)), { recursive: true });
|
|
243
|
+
const managedValues = buildManagedSetupValues({
|
|
244
|
+
homeDir,
|
|
245
|
+
env: process.env,
|
|
246
|
+
databaseUrl: serviceResult.databaseUrl,
|
|
247
|
+
});
|
|
248
|
+
writeEnvValues(envPath, managedValues);
|
|
249
|
+
|
|
250
|
+
if (isYes) {
|
|
251
|
+
console.log('\nManaged setup:');
|
|
252
|
+
console.log(` Vector index: ${managedValues.CONSTRUCT_VECTOR_INDEX_PATH}`);
|
|
253
|
+
console.log(` Vector model: ${managedValues.CONSTRUCT_VECTOR_MODEL}`);
|
|
254
|
+
console.log(` Trace backend: ${managedValues.CONSTRUCT_TRACE_BACKEND}`);
|
|
255
|
+
console.log(` Langfuse URL: ${managedValues.LANGFUSE_BASEURL}`);
|
|
256
|
+
if (serviceResult.status === 'ok') {
|
|
257
|
+
console.log(` Postgres: ${serviceResult.databaseUrl}`);
|
|
258
|
+
console.log(` Compose file: ${serviceResult.composePath}`);
|
|
259
|
+
} else {
|
|
260
|
+
console.log(` Postgres: ${serviceResult.message}`);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const sqlClient = createSqlClient({ ...process.env, ...managedValues });
|
|
264
|
+
if (sqlClient) {
|
|
265
|
+
let readyClient = null;
|
|
266
|
+
let sqlClientClosed = false;
|
|
267
|
+
try {
|
|
268
|
+
if (serviceResult.status === 'ok') {
|
|
269
|
+
await closeSqlClient(sqlClient);
|
|
270
|
+
sqlClientClosed = true;
|
|
271
|
+
await waitForSqlReady({ ...process.env, ...managedValues });
|
|
272
|
+
}
|
|
273
|
+
readyClient = createSqlClient({ ...process.env, ...managedValues });
|
|
274
|
+
const migration = await import('node:fs/promises').then((fs) => fs.readFile(new URL('../db/migrations/001_init.sql', import.meta.url), 'utf8'));
|
|
275
|
+
await readyClient.unsafe(migration);
|
|
276
|
+
console.log('\nPostgres schema initialized.');
|
|
277
|
+
const syncResult = await syncFileStateToSql(rootDir, { env: { ...process.env, ...managedValues }, project: 'construct' });
|
|
278
|
+
console.log(`Hybrid storage sync: ${syncResult.status}`);
|
|
279
|
+
if (syncResult.embeddingModel) console.log(`Embedding model: ${syncResult.embeddingModel}`);
|
|
280
|
+
} catch (error) {
|
|
281
|
+
console.log(`Hybrid storage init failed: ${error?.message || 'unknown error'}`);
|
|
282
|
+
} finally {
|
|
283
|
+
await closeSqlClient(readyClient).catch(() => {});
|
|
284
|
+
if (!sqlClientClosed) await closeSqlClient(sqlClient);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (isYes) {
|
|
290
|
+
runConstruct(['mcp', 'add', 'memory', '--auto'], { optional: true });
|
|
291
|
+
runConstruct(['mcp', 'add', 'github', '--auto'], { optional: true });
|
|
292
|
+
if (!managedValues.LANGFUSE_PUBLIC_KEY || !managedValues.LANGFUSE_SECRET_KEY) {
|
|
293
|
+
console.log('\nLangfuse is prewired but needs account credentials:');
|
|
294
|
+
console.log(' Set LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY in ~/.construct/config.env');
|
|
295
|
+
console.log(' Set these in ~/.construct/config.env or run: cd langfuse && docker compose up -d');
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
} else {
|
|
299
|
+
console.log('\nManaged defaults written:');
|
|
300
|
+
console.log(` Vector index: ${managedValues.CONSTRUCT_VECTOR_INDEX_PATH}`);
|
|
301
|
+
console.log(` Trace backend: ${managedValues.CONSTRUCT_TRACE_BACKEND} (${managedValues.LANGFUSE_BASEURL})`);
|
|
302
|
+
console.log('\nFor unattended setup, including local Postgres when Docker is running:');
|
|
303
|
+
console.log(' construct setup --yes');
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
runConstruct(['sync']);
|
|
307
|
+
runConstruct(['doctor']);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function ensureUserConfig(homeDir = HOME) {
|
|
311
|
+
ensureUserConfigDir(homeDir);
|
|
312
|
+
const envPath = getUserEnvPath(homeDir);
|
|
313
|
+
if (!fs.existsSync(envPath)) writeEnvValues(envPath, {});
|
|
314
|
+
return envPath;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
318
|
+
runSetup({ args: process.argv.slice(2) });
|
|
319
|
+
}
|