@geraldmaron/construct 1.0.14 → 1.0.16
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 +21 -2
- package/bin/construct +146 -83
- package/commands/build/feature.md +0 -6
- package/commands/build/fix.md +0 -6
- package/commands/design/access.md +0 -6
- package/commands/design/flow.md +0 -6
- package/commands/design/ui.md +0 -6
- package/commands/measure/experiment.md +0 -6
- package/commands/measure/metrics.md +0 -6
- package/commands/measure/results.md +0 -6
- package/commands/plan/api.md +0 -6
- package/commands/plan/challenge.md +0 -6
- package/commands/plan/decide.md +0 -6
- package/commands/plan/feature.md +0 -7
- package/commands/plan/requirements.md +0 -6
- package/commands/remember/context.md +0 -6
- package/commands/remember/handoff.md +0 -6
- package/commands/remember/runbook.md +0 -6
- package/commands/review/code.md +0 -6
- package/commands/review/quality.md +0 -6
- package/commands/review/security.md +0 -6
- package/commands/ship/ready.md +0 -6
- package/commands/ship/release.md +0 -6
- package/commands/ship/status.md +0 -6
- package/commands/understand/docs.md +0 -6
- package/commands/understand/research.md +0 -6
- package/commands/understand/this.md +0 -6
- package/commands/understand/why.md +0 -6
- package/commands/work/clean.md +0 -6
- package/commands/work/drive.md +0 -6
- package/commands/work/optimize-prompts.md +0 -6
- package/commands/work/parallel-review.md +0 -6
- package/db/schema/010_cx_scores.sql +51 -0
- package/lib/beads-client.mjs +19 -1
- package/lib/cli-commands.mjs +280 -146
- package/lib/comment-lint.mjs +5 -1
- package/lib/context-state.mjs +15 -3
- package/lib/contracts/validate.mjs +72 -18
- package/lib/contracts/violation-log.mjs +169 -0
- package/lib/document-extract/docling-client.mjs +114 -0
- package/lib/document-extract/docling-sidecar.py +122 -0
- package/lib/document-extract/whisper-client.mjs +79 -0
- package/lib/document-extract.mjs +73 -8
- package/lib/document-ingest.mjs +26 -4
- package/lib/embed/daemon.mjs +6 -1
- package/lib/flavors/loader.mjs +1 -1
- package/lib/handoffs/contract.mjs +2 -2
- package/lib/hooks/session-start.mjs +1 -1
- package/lib/hooks/stop-notify.mjs +7 -0
- package/lib/ingest/chunker.mjs +94 -0
- package/lib/ingest/pipeline.mjs +53 -0
- package/lib/ingest/store.mjs +82 -0
- package/lib/install/first-invocation.mjs +5 -1
- package/lib/intake/prepare.mjs +22 -4
- package/lib/integrations/intake-integrations.mjs +28 -2
- package/lib/knowledge/search.mjs +12 -0
- package/lib/mcp/server.mjs +142 -1
- package/lib/mcp/tools/skills.mjs +6 -3
- package/lib/mcp/tools/telemetry.mjs +30 -0
- package/lib/mcp/tools/workflow.mjs +6 -2
- package/lib/observation-store.mjs +14 -5
- package/lib/ollama-manager.mjs +32 -28
- package/lib/orchestration-policy.mjs +15 -0
- package/lib/prompt-composer.js +11 -1
- package/lib/roles/gateway.mjs +30 -1
- package/lib/runtime/uv-bootstrap.mjs +129 -0
- package/lib/runtime/whisper-bootstrap.mjs +102 -0
- package/lib/scheduler/index.mjs +24 -4
- package/lib/server/index.mjs +22 -1
- package/lib/server/insights.mjs +12 -0
- package/lib/server/static/index.html +1 -1
- package/lib/setup.mjs +54 -9
- package/lib/specialists/postconditions.mjs +1 -1
- package/lib/sync/skill-frontmatter.mjs +113 -21
- package/lib/tracking-surfaces.mjs +2 -0
- package/lib/update.mjs +31 -3
- package/lib/upgrade.mjs +31 -4
- package/lib/validators/skills.mjs +86 -54
- package/package.json +5 -5
- package/personas/construct.md +6 -7
- package/platforms/claude/CLAUDE.md +43 -15
- package/platforms/claude/settings.template.json +1 -1
- package/rules/common/beads-hygiene.md +3 -9
- package/rules/common/code-review.md +3 -6
- package/rules/common/coding-style.md +3 -6
- package/rules/common/comments.md +3 -7
- package/rules/common/commit-approval.md +3 -6
- package/rules/common/cx-agent-routing.md +3 -7
- package/rules/common/cx-skill-routing.md +3 -3
- package/rules/common/doc-ownership.md +3 -8
- package/rules/common/efficiency.md +3 -8
- package/rules/common/framing.md +3 -8
- package/rules/common/git-workflow.md +3 -5
- package/rules/common/no-fabrication.md +4 -12
- package/rules/common/patterns.md +3 -5
- package/rules/common/release-gates.md +3 -8
- package/rules/common/research.md +3 -8
- package/rules/common/review-before-change.md +3 -9
- package/rules/common/security.md +3 -6
- package/rules/common/skill-composition.md +3 -7
- package/rules/common/testing.md +3 -6
- package/rules/golang/coding-style.md +1 -5
- package/rules/golang/hooks.md +1 -5
- package/rules/golang/patterns.md +1 -5
- package/rules/golang/security.md +1 -5
- package/rules/golang/testing.md +1 -5
- package/rules/python/coding-style.md +1 -5
- package/rules/python/hooks.md +1 -5
- package/rules/python/patterns.md +1 -5
- package/rules/python/security.md +1 -5
- package/rules/python/testing.md +1 -5
- package/rules/swift/coding-style.md +1 -5
- package/rules/swift/hooks.md +1 -5
- package/rules/swift/patterns.md +1 -5
- package/rules/swift/security.md +1 -5
- package/rules/swift/testing.md +1 -5
- package/rules/typescript/coding-style.md +1 -5
- package/rules/typescript/hooks.md +1 -5
- package/rules/typescript/patterns.md +1 -5
- package/rules/typescript/security.md +1 -5
- package/rules/typescript/testing.md +1 -5
- package/rules/web/coding-style.md +3 -5
- package/rules/web/design-quality.md +3 -5
- package/rules/web/hooks.md +3 -5
- package/rules/web/patterns.md +3 -5
- package/rules/web/performance.md +3 -5
- package/rules/web/security.md +3 -5
- package/rules/web/testing.md +3 -5
- package/scripts/sync-specialists.mjs +20 -2
- package/skills/ai/agent-dev.md +4 -5
- package/skills/ai/llm-security.md +4 -5
- package/skills/ai/ml-ops.md +4 -5
- package/skills/ai/orchestration-workflow.md +4 -5
- package/skills/ai/prompt-and-eval.md +4 -5
- package/skills/ai/prompt-optimizer.md +4 -6
- package/skills/ai/rag-system.md +4 -5
- package/skills/architecture/api-design.md +4 -5
- package/skills/architecture/caching.md +4 -5
- package/skills/architecture/cloud-native.md +4 -5
- package/skills/architecture/message-queue.md +4 -5
- package/skills/architecture/security-arch.md +4 -5
- package/skills/compliance/ai-disclosure.md +4 -5
- package/skills/compliance/data-privacy.md +4 -5
- package/skills/compliance/license-audit.md +4 -5
- package/skills/compliance/regulatory-review.md +4 -5
- package/skills/development/cpp.md +4 -5
- package/skills/development/go.md +4 -5
- package/skills/development/java.md +4 -5
- package/skills/development/kotlin.md +4 -5
- package/skills/development/mobile-crossplatform.md +4 -5
- package/skills/development/python.md +4 -5
- package/skills/development/rust.md +4 -5
- package/skills/development/shell.md +4 -5
- package/skills/development/swift.md +4 -5
- package/skills/development/typescript.md +4 -5
- package/skills/devops/ci-cd.md +4 -5
- package/skills/devops/containerization.md +4 -5
- package/skills/devops/cost-optimization.md +4 -5
- package/skills/devops/data-engineering.md +4 -5
- package/skills/devops/database.md +4 -5
- package/skills/devops/dependency-management.md +4 -5
- package/skills/devops/devsecops.md +4 -5
- package/skills/devops/git-workflow.md +4 -5
- package/skills/devops/incident-response.md +4 -5
- package/skills/devops/monorepo.md +4 -5
- package/skills/devops/observability.md +4 -5
- package/skills/devops/performance.md +4 -5
- package/skills/devops/testing.md +4 -5
- package/skills/docs/adr-workflow.md +4 -7
- package/skills/docs/backlog-proposal-workflow.md +4 -3
- package/skills/docs/customer-profile-workflow.md +4 -3
- package/skills/docs/document-ingest-workflow.md +4 -3
- package/skills/docs/evidence-ingest-workflow.md +4 -3
- package/skills/docs/init-docs.md +4 -5
- package/skills/docs/init-project.md +4 -5
- package/skills/docs/prd-workflow.md +4 -7
- package/skills/docs/prfaq-workflow.md +4 -3
- package/skills/docs/product-intelligence-review.md +4 -3
- package/skills/docs/product-intelligence-workflow.md +4 -6
- package/skills/docs/product-signal-workflow.md +4 -5
- package/skills/docs/research-workflow.md +4 -5
- package/skills/docs/runbook-workflow.md +4 -5
- package/skills/docs/strategy-workflow.md +4 -5
- package/skills/exploration/dependency-graph-reading.md +4 -7
- package/skills/exploration/repo-map.md +4 -5
- package/skills/exploration/tracer-bullet-method.md +4 -7
- package/skills/exploration/unknown-codebase-onboarding.md +4 -7
- package/skills/frameworks/django.md +4 -5
- package/skills/frameworks/nextjs.md +4 -5
- package/skills/frameworks/react.md +4 -5
- package/skills/frameworks/spring-boot.md +4 -5
- package/skills/frontend-design/accessibility.md +4 -5
- package/skills/frontend-design/component-patterns.md +4 -5
- package/skills/frontend-design/engineering.md +4 -5
- package/skills/frontend-design/state-management.md +4 -5
- package/skills/frontend-design/ui-aesthetics.md +4 -5
- package/skills/frontend-design/ux-principles.md +4 -5
- package/skills/operating/change-management.md +4 -8
- package/skills/operating/incident-response.md +4 -8
- package/skills/operating/oncall-rotation.md +4 -7
- package/skills/operating/orchestration-reference.md +4 -10
- package/skills/quality-gates/review-work.md +4 -5
- package/skills/quality-gates/verify-change.md +4 -5
- package/skills/quality-gates/verify-module.md +4 -5
- package/skills/quality-gates/verify-quality.md +4 -5
- package/skills/quality-gates/verify-security.md +4 -5
- package/skills/roles/architect.ai-systems.md +6 -9
- package/skills/roles/architect.data.md +6 -9
- package/skills/roles/architect.enterprise.md +6 -9
- package/skills/roles/architect.integration.md +6 -9
- package/skills/roles/architect.md +7 -14
- package/skills/roles/architect.platform.md +6 -9
- package/skills/roles/data-analyst.experiment.md +6 -9
- package/skills/roles/data-analyst.md +6 -9
- package/skills/roles/data-analyst.product-intelligence.md +7 -9
- package/skills/roles/data-analyst.product.md +6 -9
- package/skills/roles/data-analyst.telemetry.md +7 -9
- package/skills/roles/data-engineer.pipeline.md +6 -9
- package/skills/roles/data-engineer.vector-retrieval.md +7 -9
- package/skills/roles/data-engineer.warehouse.md +6 -9
- package/skills/roles/debugger.md +6 -9
- package/skills/roles/designer.accessibility.md +6 -9
- package/skills/roles/designer.md +7 -9
- package/skills/roles/engineer.ai.md +6 -9
- package/skills/roles/engineer.data.md +6 -9
- package/skills/roles/engineer.md +9 -9
- package/skills/roles/engineer.platform.md +6 -9
- package/skills/roles/operator.docs.md +6 -9
- package/skills/roles/operator.md +9 -9
- package/skills/roles/operator.release.md +6 -9
- package/skills/roles/operator.sre.md +6 -9
- package/skills/roles/orchestrator.md +6 -9
- package/skills/roles/product-manager.ai-product.md +6 -9
- package/skills/roles/product-manager.business-strategy.md +6 -9
- package/skills/roles/product-manager.enterprise.md +6 -9
- package/skills/roles/product-manager.growth.md +7 -9
- package/skills/roles/product-manager.md +7 -9
- package/skills/roles/product-manager.platform.md +6 -9
- package/skills/roles/product-manager.product.md +6 -9
- package/skills/roles/qa.ai-eval.md +8 -9
- package/skills/roles/qa.api-contract.md +7 -9
- package/skills/roles/qa.data-pipeline.md +7 -9
- package/skills/roles/qa.md +7 -9
- package/skills/roles/qa.test-automation.md +6 -9
- package/skills/roles/qa.web-ui.md +7 -9
- package/skills/roles/researcher.explorer.md +6 -9
- package/skills/roles/researcher.md +8 -9
- package/skills/roles/researcher.ux.md +6 -9
- package/skills/roles/reviewer.devil-advocate.md +6 -9
- package/skills/roles/reviewer.evaluator.md +6 -9
- package/skills/roles/reviewer.md +9 -9
- package/skills/roles/reviewer.trace.md +6 -9
- package/skills/roles/security.ai.md +7 -9
- package/skills/roles/security.appsec.md +6 -9
- package/skills/roles/security.cloud.md +6 -9
- package/skills/roles/security.legal-compliance.md +6 -9
- package/skills/roles/security.md +7 -9
- package/skills/roles/security.privacy.md +7 -9
- package/skills/roles/security.supply-chain.md +7 -9
- package/skills/security/blue-team.md +4 -5
- package/skills/security/code-audit.md +4 -5
- package/skills/security/pentest.md +4 -5
- package/skills/security/red-team.md +4 -5
- package/skills/security/threat-intel.md +4 -5
- package/skills/security/vuln-research.md +4 -5
- package/skills/strategy/competitive-landscape.md +4 -8
- package/skills/strategy/market-research-methods.md +4 -8
- package/skills/strategy/narrative-arc.md +4 -8
- package/skills/strategy/pricing-positioning.md +4 -8
- package/skills/utility/clean-code.md +4 -5
- package/specialists/policy-inventory.json +14 -0
- package/lib/specialist-contracts-enforce.mjs +0 -172
- package/rules/common/agents.md +0 -28
- package/rules/common/development-workflow.md +0 -32
- package/rules/common/performance.md +0 -55
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/runtime/whisper-bootstrap.mjs — provision whisper.cpp binary + ASR model.
|
|
3
|
+
*
|
|
4
|
+
* Strategy (2026-06):
|
|
5
|
+
* 1. Prefer system-installed whisper-cli (or legacy `main`) on PATH —
|
|
6
|
+
* `brew install whisper-cpp` (macOS, Metal-accelerated) or distro
|
|
7
|
+
* package on Linux.
|
|
8
|
+
* 2. Fall back to bundled `whisper-cli` cached at <runtimeDir>/bin/ if a
|
|
9
|
+
* previous bootstrap fetched one.
|
|
10
|
+
* 3. If neither, return a structured "not-available" descriptor — caller
|
|
11
|
+
* surfaces a user-actionable install hint rather than crashing.
|
|
12
|
+
*
|
|
13
|
+
* Model files are downloaded directly from Hugging Face the first time the
|
|
14
|
+
* default `base.en` (or env-overridden) model is requested. Models cache at
|
|
15
|
+
* <runtimeDir>/models/. base.en is ~150MB; smaller than the small.en or
|
|
16
|
+
* medium.en model that callers can opt into via CONSTRUCT_WHISPER_MODEL.
|
|
17
|
+
*
|
|
18
|
+
* No bundled distribution: whisper.cpp upstream does not publish stable
|
|
19
|
+
* pre-built CLI binaries across platforms, and building from source via
|
|
20
|
+
* cmake adds a heavyweight first-run path. Defer that to a future opt-in.
|
|
21
|
+
*/
|
|
22
|
+
import { existsSync, mkdirSync, statSync, createWriteStream } from 'node:fs';
|
|
23
|
+
import { spawnSync } from 'node:child_process';
|
|
24
|
+
import { pipeline } from 'node:stream/promises';
|
|
25
|
+
import path from 'node:path';
|
|
26
|
+
import os from 'node:os';
|
|
27
|
+
|
|
28
|
+
const HF_MODEL_BASE_URL = 'https://huggingface.co/ggerganov/whisper.cpp/resolve/main';
|
|
29
|
+
const DEFAULT_MODEL = process.env.CONSTRUCT_WHISPER_MODEL || 'base.en';
|
|
30
|
+
const KNOWN_MODELS = new Set(['tiny', 'tiny.en', 'base', 'base.en', 'small', 'small.en', 'medium', 'medium.en', 'large-v3', 'large-v3-turbo']);
|
|
31
|
+
|
|
32
|
+
export function defaultRuntimeDir(cwd = process.cwd()) {
|
|
33
|
+
return path.join(cwd, '.cx', 'runtime', 'whisper');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function which(bin) {
|
|
37
|
+
const result = spawnSync(process.platform === 'win32' ? 'where' : 'which', [bin], { encoding: 'utf8' });
|
|
38
|
+
if (result.status !== 0) return null;
|
|
39
|
+
return result.stdout.trim().split('\n')[0] || null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function locateWhisperBinary({ runtimeDir = defaultRuntimeDir() } = {}) {
|
|
43
|
+
for (const candidate of ['whisper-cli', 'whisper-cpp', 'main']) {
|
|
44
|
+
const found = which(candidate);
|
|
45
|
+
if (found) return { binary: found, source: 'system', name: candidate };
|
|
46
|
+
}
|
|
47
|
+
const cached = path.join(runtimeDir, 'bin', 'whisper-cli');
|
|
48
|
+
if (existsSync(cached)) return { binary: cached, source: 'cached', name: 'whisper-cli' };
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function describeWhisperRuntime({ runtimeDir = defaultRuntimeDir() } = {}) {
|
|
53
|
+
const located = locateWhisperBinary({ runtimeDir });
|
|
54
|
+
const modelPath = modelPathFor(DEFAULT_MODEL, runtimeDir);
|
|
55
|
+
return {
|
|
56
|
+
runtimeDir,
|
|
57
|
+
binary: located,
|
|
58
|
+
defaultModel: DEFAULT_MODEL,
|
|
59
|
+
modelPath,
|
|
60
|
+
modelInstalled: existsSync(modelPath),
|
|
61
|
+
available: !!located,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function installHint() {
|
|
66
|
+
if (process.platform === 'darwin') return 'brew install whisper-cpp';
|
|
67
|
+
if (process.platform === 'linux') return 'See https://github.com/ggml-org/whisper.cpp#quick-start (build from source via cmake or install via your distro package manager).';
|
|
68
|
+
return 'See https://github.com/ggml-org/whisper.cpp#quick-start';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function modelPathFor(modelName, runtimeDir) {
|
|
72
|
+
return path.join(runtimeDir, 'models', `ggml-${modelName}.bin`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export async function ensureWhisperModel({ runtimeDir = defaultRuntimeDir(), model = DEFAULT_MODEL } = {}) {
|
|
76
|
+
if (!KNOWN_MODELS.has(model)) {
|
|
77
|
+
throw new Error(`unknown whisper model: ${model} (allowed: ${[...KNOWN_MODELS].join(', ')})`);
|
|
78
|
+
}
|
|
79
|
+
const target = modelPathFor(model, runtimeDir);
|
|
80
|
+
if (existsSync(target) && statSync(target).size > 1024) return target;
|
|
81
|
+
mkdirSync(path.dirname(target), { recursive: true });
|
|
82
|
+
const url = `${HF_MODEL_BASE_URL}/ggml-${model}.bin`;
|
|
83
|
+
const response = await fetch(url, { redirect: 'follow' });
|
|
84
|
+
if (!response.ok) {
|
|
85
|
+
throw new Error(`failed to download whisper model ${model}: ${response.status} ${response.statusText} (${url})`);
|
|
86
|
+
}
|
|
87
|
+
await pipeline(response.body, createWriteStream(target));
|
|
88
|
+
return target;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function ensureWhisperRuntime({ runtimeDir = defaultRuntimeDir(), model = DEFAULT_MODEL } = {}) {
|
|
92
|
+
mkdirSync(runtimeDir, { recursive: true });
|
|
93
|
+
const located = locateWhisperBinary({ runtimeDir });
|
|
94
|
+
if (!located) {
|
|
95
|
+
const err = new Error(`whisper-cli not found; install via: ${installHint()}`);
|
|
96
|
+
err.code = 'WHISPER_BINARY_MISSING';
|
|
97
|
+
err.installHint = installHint();
|
|
98
|
+
throw err;
|
|
99
|
+
}
|
|
100
|
+
const modelPath = await ensureWhisperModel({ runtimeDir, model });
|
|
101
|
+
return { binary: located.binary, source: located.source, modelPath, model };
|
|
102
|
+
}
|
package/lib/scheduler/index.mjs
CHANGED
|
@@ -94,14 +94,34 @@ registerJob({
|
|
|
94
94
|
handler: async () => ({ status: 'noop', reason: 'not yet implemented' }),
|
|
95
95
|
});
|
|
96
96
|
|
|
97
|
+
// Doc hygiene scan cadence is deployment-aware. Solo runs nightly because
|
|
98
|
+
// a single contributor's doc drift accumulates slowly. Team and enterprise
|
|
99
|
+
// runs hourly because many writers can shift the surface within a workday,
|
|
100
|
+
// and the higher limit (50 vs 25) gives the reconcile worker enough headroom
|
|
101
|
+
// to keep up. Mode/schedule resolved at module-load so a single registry
|
|
102
|
+
// entry covers both topologies.
|
|
103
|
+
|
|
104
|
+
const DOC_HYGIENE_SOLO_CRON = '0 2 * * *';
|
|
105
|
+
const DOC_HYGIENE_TEAM_CRON = '0 * * * *';
|
|
106
|
+
|
|
107
|
+
export function resolveDocHygieneSchedule(env = process.env) {
|
|
108
|
+
const mode = getDeploymentMode(env);
|
|
109
|
+
const isTeamish = mode === 'team' || mode === 'enterprise';
|
|
110
|
+
return {
|
|
111
|
+
mode: isTeamish ? 'team' : 'solo',
|
|
112
|
+
schedule: isTeamish ? DOC_HYGIENE_TEAM_CRON : DOC_HYGIENE_SOLO_CRON,
|
|
113
|
+
limit: isTeamish ? 50 : 25,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const docHygiene = resolveDocHygieneSchedule();
|
|
97
118
|
registerJob({
|
|
98
119
|
id: 'doc-hygiene-scan',
|
|
99
|
-
schedule:
|
|
100
|
-
mode:
|
|
120
|
+
schedule: docHygiene.schedule,
|
|
121
|
+
mode: docHygiene.mode,
|
|
101
122
|
handler: async ({ cwd, env }) => {
|
|
102
123
|
const { findHygieneCandidates } = await import('../hygiene/scan.mjs');
|
|
103
|
-
const
|
|
104
|
-
const limit = mode === 'solo' ? 25 : 50;
|
|
124
|
+
const { limit } = resolveDocHygieneSchedule(env);
|
|
105
125
|
const candidates = findHygieneCandidates({ cwd, limit });
|
|
106
126
|
return {
|
|
107
127
|
status: 'ok',
|
package/lib/server/index.mjs
CHANGED
|
@@ -33,7 +33,27 @@ const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
|
33
33
|
const ROOT_DIR = join(__dirname, '..', '..');
|
|
34
34
|
const HOME = homedir();
|
|
35
35
|
const PORT = parseInt(process.env.PORT ?? '4242', 10);
|
|
36
|
-
|
|
36
|
+
|
|
37
|
+
// Default bind is loopback regardless of NODE_ENV. Exposing the dashboard to
|
|
38
|
+
// non-loopback interfaces is opt-in via BIND_HOST and gated on configured
|
|
39
|
+
// auth (see assertSafeBind below).
|
|
40
|
+
|
|
41
|
+
const BIND_HOST = process.env.BIND_HOST ?? '127.0.0.1';
|
|
42
|
+
|
|
43
|
+
function isLoopbackHost(host) {
|
|
44
|
+
return host === '127.0.0.1' || host === 'localhost' || host === '::1';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function assertSafeBind() {
|
|
48
|
+
if (isLoopbackHost(BIND_HOST)) return;
|
|
49
|
+
if (!isAuthConfigured()) {
|
|
50
|
+
process.stderr.write(
|
|
51
|
+
`[dashboard] refuses to bind to non-loopback host ${BIND_HOST} without auth configured. ` +
|
|
52
|
+
`Set CONSTRUCT_DASHBOARD_TOKEN (or run \`construct dashboard auth init\`) before exposing the dashboard.\n`,
|
|
53
|
+
);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
37
57
|
|
|
38
58
|
const STATIC_DIR = join(__dirname, 'static');
|
|
39
59
|
const REGISTRY_FILE = join(ROOT_DIR, 'specialists', 'registry.json');
|
|
@@ -2237,6 +2257,7 @@ if (process.env.CX_AUTO_EMBED === '1') {
|
|
|
2237
2257
|
setInterval(runEmbedSync, intervalMs).unref();
|
|
2238
2258
|
console.log(`Embed scheduler active — syncing every ${intervalMs / 60_000} min`);
|
|
2239
2259
|
}
|
|
2260
|
+
assertSafeBind();
|
|
2240
2261
|
server.listen(PORT, BIND_HOST, () => {
|
|
2241
2262
|
console.log(`Construct dashboard running at http://${BIND_HOST}:${PORT}`);
|
|
2242
2263
|
});
|
package/lib/server/insights.mjs
CHANGED
|
@@ -14,6 +14,7 @@ import os from 'node:os';
|
|
|
14
14
|
import path from 'node:path';
|
|
15
15
|
import { spawnSync } from 'node:child_process';
|
|
16
16
|
import { readCostLog, summarizeCostData } from '../cost.mjs';
|
|
17
|
+
import { getRebrand } from '../profiles/rebrand.mjs';
|
|
17
18
|
|
|
18
19
|
const TELEMETRY_TIMEOUT_MS = 3500;
|
|
19
20
|
|
|
@@ -309,12 +310,23 @@ function summarizeIntakeQueue(env, cwd = process.cwd()) {
|
|
|
309
310
|
}
|
|
310
311
|
} catch { /* skip */ }
|
|
311
312
|
|
|
313
|
+
// Profile-aware labels: the active profile's rebrand block (e.g.
|
|
314
|
+
// rnd → "Intake queue"/"signal"; sales-ops → "Lead inbox"/"lead")
|
|
315
|
+
// travels in the payload so the dashboard renders the right noun
|
|
316
|
+
// without hardcoding "intake" anywhere user-facing.
|
|
317
|
+
|
|
318
|
+
let rebrand;
|
|
319
|
+
try { rebrand = getRebrand(cwd); }
|
|
320
|
+
catch { rebrand = { intakeQueueLabel: 'Intake queue', signalNoun: 'signal' }; }
|
|
321
|
+
|
|
312
322
|
return {
|
|
313
323
|
state: pendingCount === 0 && processedCount === 0 && skippedCount === 0 ? 'empty' : 'ok',
|
|
314
324
|
pending: pendingCount,
|
|
315
325
|
processed: processedCount,
|
|
316
326
|
skipped: skippedCount,
|
|
317
327
|
customerLinked,
|
|
328
|
+
label: rebrand.intakeQueueLabel,
|
|
329
|
+
itemNoun: rebrand.signalNoun,
|
|
318
330
|
};
|
|
319
331
|
} catch (err) {
|
|
320
332
|
return { state: 'unreachable', message: err.message };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<!DOCTYPE html><!--02hC8wDNvTNKwi4iN6D_t--><html lang="en" data-theme="dark" data-density="comfortable" data-motion="normal" class="__variable_629dad __variable_246ccd __variable_3c557b"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" href="/_next/static/media/22a5144ee8d83bca-s.p.woff2" as="font" crossorigin="" type="font/woff2"/><link rel="preload" href="/_next/static/media/bb3ef058b751a6ad-s.p.woff2" as="font" crossorigin="" type="font/woff2"/><link rel="stylesheet" href="/_next/static/css/47a506ef57811fb9.css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-8e0503f8a36190b3.js"/><script src="/_next/static/chunks/87c73c54-24122e7b92478d00.js" async=""></script><script src="/_next/static/chunks/18-1e25b5692ec1d26d.js" async=""></script><script src="/_next/static/chunks/main-app-07d82ec6c92d7402.js" async=""></script><script src="/_next/static/chunks/9664-af80478aa73ba424.js" async=""></script><script src="/_next/static/chunks/8144-da56a4a291c0b21a.js" async=""></script><script src="/_next/static/chunks/app/layout-1a2d2674aa220229.js" async=""></script><script src="/_next/static/chunks/app/page-2568fbdd2b0e1eca.js" async=""></script><meta name="next-size-adjust" content=""/><title>Construct — Dashboard</title><meta name="description" content="Local operations dashboard for Construct — approvals, health, knowledge, models, providers."/><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body><div hidden=""><!--$--><!--/$--></div><div class="shell"><header class="topbar"><a class="brand" href="/"><div class="mark"></div><div class="name">Construct<em>dashboard</em></div></a><div class="search-wrap"><label class="search"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"></circle><line x1="21" y1="21" x2="16.5" y2="16.5"></line></svg><input readOnly="" placeholder="Search dashboard…"/><span class="kbd">⌘K</span></label></div><div class="top-actions"><button class="icon-btn" title="Toggle theme" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="4"></circle><line x1="12" y1="2" x2="12" y2="5"></line><line x1="12" y1="19" x2="12" y2="22"></line><line x1="2" y1="12" x2="5" y2="12"></line><line x1="19" y1="12" x2="22" y2="12"></line><line x1="4.5" y1="4.5" x2="6.5" y2="6.5"></line><line x1="17.5" y1="17.5" x2="19.5" y2="19.5"></line><line x1="4.5" y1="19.5" x2="6.5" y2="17.5"></line><line x1="17.5" y1="6.5" x2="19.5" y2="4.5"></line></svg></button><button class="icon-btn" title="Toggle density" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="7" x2="21" y2="7"></line><line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="17" x2="21" y2="17"></line></svg></button><a class="icon-btn outlined" href="https://github.com/geraldmaron/construct" target="_blank" rel="noreferrer"><svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor"><path d="M12 .5C5.65.5.5 5.66.5 12.02c0 5.09 3.29 9.4 7.86 10.92.58.1.79-.25.79-.55v-2.13c-3.2.7-3.87-1.37-3.87-1.37-.52-1.32-1.27-1.67-1.27-1.67-1.04-.71.08-.7.08-.7 1.15.08 1.76 1.18 1.76 1.18 1.02 1.76 2.69 1.25 3.35.96.1-.74.4-1.25.73-1.54-2.55-.29-5.24-1.28-5.24-5.7 0-1.26.45-2.29 1.18-3.1-.12-.29-.51-1.46.11-3.04 0 0 .96-.31 3.16 1.18a10.9 10.9 0 0 1 5.74 0c2.2-1.49 3.16-1.18 3.16-1.18.62 1.58.23 2.75.12 3.04.74.81 1.18 1.84 1.18 3.1 0 4.43-2.69 5.41-5.25 5.69.41.36.78 1.05.78 2.13v3.16c0 .31.21.66.79.55A11.52 11.52 0 0 0 23.5 12.02C23.5 5.66 18.35.5 12 .5Z"></path></svg> <span style="font-size:11px">repo</span></a></div><div class="progress"></div></header><div class="body-grid"><aside class="sidebar"><div class="side-group"><div class="side-label">Overview</div><a class="side-link active" href="/"><span class="num">01</span><span>Home</span></a><a class="side-link" href="/doctor/"><span class="num">02</span><span>Doctor</span></a><a class="side-link" href="/resources/"><span class="num">03</span><span>Services</span></a></div><div class="side-group"><div class="side-label">Activity</div><a class="side-link" href="/audit/"><span class="num">01</span><span>Audit trail</span></a><a class="side-link" href="/snapshots/"><span class="num">02</span><span>Snapshots</span></a><a class="side-link" href="/performance/"><span class="num">03</span><span>Performance</span></a></div><div class="side-group"><div class="side-label">Work</div><a class="side-link" href="/approvals/"><span class="num">01</span><span>Approvals</span></a><a class="side-link" href="/workflow/"><span class="num">02</span><span>Workflow</span></a><a class="side-link" href="/beads/"><span class="num">03</span><span>Beads</span></a><a class="side-link" href="/artifacts/"><span class="num">04</span><span>Artifacts</span></a><a class="side-link" href="/intake/"><span class="num">05</span><span>Intake</span></a></div><div class="side-group"><div class="side-label">Specialists</div><a class="side-link" href="/agents/"><span class="num">01</span><span>Specialists</span></a><a class="side-link" href="/skills/"><span class="num">02</span><span>Skills</span></a><a class="side-link" href="/commands/"><span class="num">03</span><span>Slash commands</span></a><a class="side-link" href="/hooks/"><span class="num">04</span><span>Hooks</span></a><a class="side-link" href="/plugins/"><span class="num">05</span><span>Plugins</span></a></div><div class="side-group"><div class="side-label">Models & Providers</div><a class="side-link" href="/models/"><span class="num">01</span><span>Models</span></a><a class="side-link" href="/providers/"><span class="num">02</span><span>Providers</span></a><a class="side-link" href="/mcp/"><span class="num">03</span><span>MCP servers</span></a></div><div class="side-group"><div class="side-label">Knowledge</div><a class="side-link" href="/knowledge/"><span class="num">01</span><span>Knowledge</span></a><a class="side-link" href="/editor/"><span class="num">02</span><span>Editor</span></a></div><div class="side-group"><div class="side-label">System</div><a class="side-link" href="/config/"><span class="num">01</span><span>Config</span></a><a class="side-link" href="/infrastructure/"><span class="num">02</span><span>Infrastructure</span></a></div><div class="side-foot">Local dashboard for Construct. Theme + density preferences persist.</div></aside><main class="main"><div class="page"><div class="eyebrow"><span class="dot"></span><span>overview · construct dashboard</span></div><h1 class="page-title">System state</h1><p class="page-lede">The runtime in one place. Approvals, services, model providers, knowledge corpus, and intake — each section deep-links to its dedicated surface.</p><div class="meta-strip"><span class="pill">mode: <!-- -->…</span><span>instance: <!-- -->…</span><span class="sep">·</span><span>embed: <!-- -->stopped</span></div><div class="body"><div class="hero-stats" style="margin-top:0;margin-bottom:24px"><div class="hero-stat"><span class="k">Services</span><span class="v">…</span><span style="display:block;font-size:11.5px;color:var(--muted);margin-top:4px">running</span></div><div class="hero-stat"><span class="k">Approvals</span><span class="v">…</span><span style="display:block;font-size:11.5px;color:var(--muted);margin-top:4px">pending</span></div><div class="hero-stat"><span class="k">Skills</span><span class="v">0</span><span style="display:block;font-size:11.5px;color:var(--muted);margin-top:4px">registered</span></div><div class="hero-stat"><span class="k">Commands</span><span class="v">0</span><span style="display:block;font-size:11.5px;color:var(--muted);margin-top:4px">slash</span></div></div><section class="section open"><button class="section-head" aria-expanded="true" type="button"><span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="9 6 15 12 9 18"></polyline></svg></span><span style="display:flex;flex-direction:column;gap:6px"><span class="section-num">01</span><span class="section-title">Pending approvals</span></span><span class="section-aside"><span>empty</span></span></button><div class="tldr-row"><div class="tldr"><b>TL;DR</b>Items waiting on a human gate. High-risk mutations (work item creation, merge, doc publish, config changes) land here.</div></div><div class="section-body-wrap"><div class="section-body"><div class="section-body-inner"><div class="body"><div style="padding:24px;text-align:center;color:var(--muted);font-family:var(--mono);font-size:12px">Loading…</div></div></div></div></div></section><section class="section"><button class="section-head" aria-expanded="false" type="button"><span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="9 6 15 12 9 18"></polyline></svg></span><span style="display:flex;flex-direction:column;gap:6px"><span class="section-num">02</span><span class="section-title">Services</span></span><span class="section-aside"><span>0/0 running</span></span></button><div class="tldr-row"><div class="tldr"><b>TL;DR</b>Local processes that back the runtime: Postgres, memory bridge, OpenCode bridge, dashboard, embed daemon.</div></div><div class="section-body-wrap"><div class="section-body"><div class="section-body-inner"><div class="body"><div style="padding:24px;text-align:center;color:var(--muted);font-family:var(--mono);font-size:12px">Loading…</div><p style="margin-top:12px"><a class="link" href="/resources/">Open services dashboard →</a></p></div></div></div></div></section><section class="section"><button class="section-head" aria-expanded="false" type="button"><span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="9 6 15 12 9 18"></polyline></svg></span><span style="display:flex;flex-direction:column;gap:6px"><span class="section-num">03</span><span class="section-title">Credentials</span></span><span class="section-aside"><span>0 tracked</span></span></button><div class="tldr-row"><div class="tldr"><b>TL;DR</b>Provider API keys, billing posture, and 1Password references. Set them in /providers.</div></div><div class="section-body-wrap"><div class="section-body"><div class="section-body-inner"><div class="body"><p style="margin-top:12px"><a class="link" href="/providers/">Manage providers →</a></p></div></div></div></div></section><section class="section"><button class="section-head" aria-expanded="false" type="button"><span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="9 6 15 12 9 18"></polyline></svg></span><span style="display:flex;flex-direction:column;gap:6px"><span class="section-num">04</span><span class="section-title">Quick links</span></span><span class="section-aside"></span></button><div class="tldr-row"><div class="tldr"><b>TL;DR</b>Deep links into every dashboard surface.</div></div><div class="section-body-wrap"><div class="section-body"><div class="section-body-inner"><div class="body"><div class="callout"><span class="clt-label">Where to next</span><div><p><a class="link" href="/doctor/">Doctor</a>,<!-- --> <a class="link" href="/knowledge/">Knowledge</a>,<!-- --> <a class="link" href="/intake/">Intake</a>,<!-- --> <a class="link" href="/workflow/">Workflow</a>,<!-- --> <a class="link" href="/agents/">Specialists</a>,<!-- --> <a class="link" href="/models/">Models</a>,<!-- --> <a class="link" href="/audit/">Audit</a>.</p></div></div></div></div></div></div></section></div></div><!--$--><!--/$--></main></div></div><script src="/_next/static/chunks/webpack-8e0503f8a36190b3.js" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[105,[\"9664\",\"static/chunks/9664-af80478aa73ba424.js\",\"8144\",\"static/chunks/8144-da56a4a291c0b21a.js\",\"7177\",\"static/chunks/app/layout-1a2d2674aa220229.js\"],\"AppShellClient\"]\n3:I[5341,[],\"\"]\n4:I[25,[],\"\"]\n5:I[1012,[],\"ClientPageRoot\"]\n6:I[9008,[\"9664\",\"static/chunks/9664-af80478aa73ba424.js\",\"8144\",\"static/chunks/8144-da56a4a291c0b21a.js\",\"8974\",\"static/chunks/app/page-2568fbdd2b0e1eca.js\"],\"default\"]\n9:I[5104,[],\"OutletBoundary\"]\nb:I[7158,[],\"AsyncMetadataOutlet\"]\nd:I[5104,[],\"ViewportBoundary\"]\nf:I[5104,[],\"MetadataBoundary\"]\n10:\"$Sreact.suspense\"\n12:I[4431,[],\"\"]\n:HL[\"/_next/static/media/22a5144ee8d83bca-s.p.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n:HL[\"/_next/static/media/bb3ef058b751a6ad-s.p.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n:HL[\"/_next/static/css/47a506ef57811fb9.css\",\"style\"]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"b\":\"02hC8wDNvTNKwi4iN6D_t\",\"p\":\"\",\"c\":[\"\",\"\"],\"i\":false,\"f\":[[[\"\",{\"children\":[\"__PAGE__\",{}]},\"$undefined\",\"$undefined\",true],[\"\",[\"$\",\"$1\",\"c\",{\"children\":[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/47a506ef57811fb9.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}]],[\"$\",\"html\",null,{\"lang\":\"en\",\"data-theme\":\"dark\",\"data-density\":\"comfortable\",\"data-motion\":\"normal\",\"suppressHydrationWarning\":true,\"className\":\"__variable_629dad __variable_246ccd __variable_3c557b\",\"children\":[\"$\",\"body\",null,{\"children\":[\"$\",\"$L2\",null,{\"nav\":[{\"label\":\"Overview\",\"items\":[{\"id\":\"home\",\"title\":\"Home\",\"href\":\"/\"},{\"id\":\"doctor\",\"title\":\"Doctor\",\"href\":\"/doctor\"},{\"id\":\"resources\",\"title\":\"Services\",\"href\":\"/resources\"}]},{\"label\":\"Activity\",\"items\":[{\"id\":\"audit\",\"title\":\"Audit trail\",\"href\":\"/audit\"},{\"id\":\"snapshots\",\"title\":\"Snapshots\",\"href\":\"/snapshots\"},{\"id\":\"performance\",\"title\":\"Performance\",\"href\":\"/performance\"}]},{\"label\":\"Work\",\"items\":[{\"id\":\"approvals\",\"title\":\"Approvals\",\"href\":\"/approvals\"},{\"id\":\"workflow\",\"title\":\"Workflow\",\"href\":\"/workflow\"},{\"id\":\"beads\",\"title\":\"Beads\",\"href\":\"/beads\"},{\"id\":\"artifacts\",\"title\":\"Artifacts\",\"href\":\"/artifacts\"},{\"id\":\"intake\",\"title\":\"Intake\",\"href\":\"/intake\"}]},{\"label\":\"Specialists\",\"items\":[{\"id\":\"agents\",\"title\":\"Specialists\",\"href\":\"/agents\"},{\"id\":\"skills\",\"title\":\"Skills\",\"href\":\"/skills\"},{\"id\":\"commands\",\"title\":\"Slash commands\",\"href\":\"/commands\"},{\"id\":\"hooks\",\"title\":\"Hooks\",\"href\":\"/hooks\"},{\"id\":\"plugins\",\"title\":\"Plugins\",\"href\":\"/plugins\"}]},{\"label\":\"Models \u0026 Providers\",\"items\":[{\"id\":\"models\",\"title\":\"Models\",\"href\":\"/models\"},{\"id\":\"providers\",\"title\":\"Providers\",\"href\":\"/providers\"},{\"id\":\"mcp\",\"title\":\"MCP servers\",\"href\":\"/mcp\"}]},{\"label\":\"Knowledge\",\"items\":[{\"id\":\"knowledge\",\"title\":\"Knowledge\",\"href\":\"/knowledge\"},{\"id\":\"editor\",\"title\":\"Editor\",\"href\":\"/editor\"}]},{\"label\":\"System\",\"items\":[{\"id\":\"config\",\"title\":\"Config\",\"href\":\"/config\"},{\"id\":\"infrastructure\",\"title\":\"Infrastructure\",\"href\":\"/infrastructure\"}]}],\"children\":[\"$\",\"$L3\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L4\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":[[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":404}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],[]],\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]}]}]}]]}],{\"children\":[\"__PAGE__\",[\"$\",\"$1\",\"c\",{\"children\":[[\"$\",\"$L5\",null,{\"Component\":\"$6\",\"searchParams\":{},\"params\":{},\"promises\":[\"$@7\",\"$@8\"]}],null,[\"$\",\"$L9\",null,{\"children\":[\"$La\",[\"$\",\"$Lb\",null,{\"promise\":\"$@c\"}]]}]]}],{},null,false]},null,false],[\"$\",\"$1\",\"h\",{\"children\":[null,[[\"$\",\"$Ld\",null,{\"children\":\"$Le\"}],[\"$\",\"meta\",null,{\"name\":\"next-size-adjust\",\"content\":\"\"}]],[\"$\",\"$Lf\",null,{\"children\":[\"$\",\"div\",null,{\"hidden\":true,\"children\":[\"$\",\"$10\",null,{\"fallback\":null,\"children\":\"$L11\"}]}]}]]}],false]],\"m\":\"$undefined\",\"G\":[\"$12\",[]],\"s\":false,\"S\":true}\n"])</script><script>self.__next_f.push([1,"7:{}\n8:\"$0:f:0:1:2:children:1:props:children:0:props:params\"\n"])</script><script>self.__next_f.push([1,"e:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}]]\na:null\n"])</script><script>self.__next_f.push([1,"c:{\"metadata\":[[\"$\",\"title\",\"0\",{\"children\":\"Construct — Dashboard\"}],[\"$\",\"meta\",\"1\",{\"name\":\"description\",\"content\":\"Local operations dashboard for Construct — approvals, health, knowledge, models, providers.\"}]],\"error\":null,\"digest\":\"$undefined\"}\n"])</script><script>self.__next_f.push([1,"11:\"$c:metadata\"\n"])</script></body></html>
|
|
1
|
+
<!DOCTYPE html><!--qA_CTDVxjnG_0_bArvNYn--><html lang="en" data-theme="dark" data-density="comfortable" data-motion="normal" class="__variable_629dad __variable_246ccd __variable_3c557b"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" href="/_next/static/media/22a5144ee8d83bca-s.p.woff2" as="font" crossorigin="" type="font/woff2"/><link rel="preload" href="/_next/static/media/bb3ef058b751a6ad-s.p.woff2" as="font" crossorigin="" type="font/woff2"/><link rel="stylesheet" href="/_next/static/css/47a506ef57811fb9.css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-8e0503f8a36190b3.js"/><script src="/_next/static/chunks/87c73c54-24122e7b92478d00.js" async=""></script><script src="/_next/static/chunks/18-1e25b5692ec1d26d.js" async=""></script><script src="/_next/static/chunks/main-app-07d82ec6c92d7402.js" async=""></script><script src="/_next/static/chunks/9664-af80478aa73ba424.js" async=""></script><script src="/_next/static/chunks/8144-da56a4a291c0b21a.js" async=""></script><script src="/_next/static/chunks/app/layout-1a2d2674aa220229.js" async=""></script><script src="/_next/static/chunks/app/page-2568fbdd2b0e1eca.js" async=""></script><meta name="next-size-adjust" content=""/><title>Construct — Dashboard</title><meta name="description" content="Local operations dashboard for Construct — approvals, health, knowledge, models, providers."/><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body><div hidden=""><!--$--><!--/$--></div><div class="shell"><header class="topbar"><a class="brand" href="/"><div class="mark"></div><div class="name">Construct<em>dashboard</em></div></a><div class="search-wrap"><label class="search"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"></circle><line x1="21" y1="21" x2="16.5" y2="16.5"></line></svg><input readOnly="" placeholder="Search dashboard…"/><span class="kbd">⌘K</span></label></div><div class="top-actions"><button class="icon-btn" title="Toggle theme" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="4"></circle><line x1="12" y1="2" x2="12" y2="5"></line><line x1="12" y1="19" x2="12" y2="22"></line><line x1="2" y1="12" x2="5" y2="12"></line><line x1="19" y1="12" x2="22" y2="12"></line><line x1="4.5" y1="4.5" x2="6.5" y2="6.5"></line><line x1="17.5" y1="17.5" x2="19.5" y2="19.5"></line><line x1="4.5" y1="19.5" x2="6.5" y2="17.5"></line><line x1="17.5" y1="6.5" x2="19.5" y2="4.5"></line></svg></button><button class="icon-btn" title="Toggle density" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="7" x2="21" y2="7"></line><line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="17" x2="21" y2="17"></line></svg></button><a class="icon-btn outlined" href="https://github.com/geraldmaron/construct" target="_blank" rel="noreferrer"><svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor"><path d="M12 .5C5.65.5.5 5.66.5 12.02c0 5.09 3.29 9.4 7.86 10.92.58.1.79-.25.79-.55v-2.13c-3.2.7-3.87-1.37-3.87-1.37-.52-1.32-1.27-1.67-1.27-1.67-1.04-.71.08-.7.08-.7 1.15.08 1.76 1.18 1.76 1.18 1.02 1.76 2.69 1.25 3.35.96.1-.74.4-1.25.73-1.54-2.55-.29-5.24-1.28-5.24-5.7 0-1.26.45-2.29 1.18-3.1-.12-.29-.51-1.46.11-3.04 0 0 .96-.31 3.16 1.18a10.9 10.9 0 0 1 5.74 0c2.2-1.49 3.16-1.18 3.16-1.18.62 1.58.23 2.75.12 3.04.74.81 1.18 1.84 1.18 3.1 0 4.43-2.69 5.41-5.25 5.69.41.36.78 1.05.78 2.13v3.16c0 .31.21.66.79.55A11.52 11.52 0 0 0 23.5 12.02C23.5 5.66 18.35.5 12 .5Z"></path></svg> <span style="font-size:11px">repo</span></a></div><div class="progress"></div></header><div class="body-grid"><aside class="sidebar"><div class="side-group"><div class="side-label">Overview</div><a class="side-link active" href="/"><span class="num">01</span><span>Home</span></a><a class="side-link" href="/doctor/"><span class="num">02</span><span>Doctor</span></a><a class="side-link" href="/resources/"><span class="num">03</span><span>Services</span></a></div><div class="side-group"><div class="side-label">Activity</div><a class="side-link" href="/audit/"><span class="num">01</span><span>Audit trail</span></a><a class="side-link" href="/snapshots/"><span class="num">02</span><span>Snapshots</span></a><a class="side-link" href="/performance/"><span class="num">03</span><span>Performance</span></a></div><div class="side-group"><div class="side-label">Work</div><a class="side-link" href="/approvals/"><span class="num">01</span><span>Approvals</span></a><a class="side-link" href="/workflow/"><span class="num">02</span><span>Workflow</span></a><a class="side-link" href="/beads/"><span class="num">03</span><span>Beads</span></a><a class="side-link" href="/artifacts/"><span class="num">04</span><span>Artifacts</span></a><a class="side-link" href="/intake/"><span class="num">05</span><span>Intake</span></a></div><div class="side-group"><div class="side-label">Specialists</div><a class="side-link" href="/agents/"><span class="num">01</span><span>Specialists</span></a><a class="side-link" href="/skills/"><span class="num">02</span><span>Skills</span></a><a class="side-link" href="/commands/"><span class="num">03</span><span>Slash commands</span></a><a class="side-link" href="/hooks/"><span class="num">04</span><span>Hooks</span></a><a class="side-link" href="/plugins/"><span class="num">05</span><span>Plugins</span></a></div><div class="side-group"><div class="side-label">Models & Providers</div><a class="side-link" href="/models/"><span class="num">01</span><span>Models</span></a><a class="side-link" href="/providers/"><span class="num">02</span><span>Providers</span></a><a class="side-link" href="/mcp/"><span class="num">03</span><span>MCP servers</span></a></div><div class="side-group"><div class="side-label">Knowledge</div><a class="side-link" href="/knowledge/"><span class="num">01</span><span>Knowledge</span></a><a class="side-link" href="/editor/"><span class="num">02</span><span>Editor</span></a></div><div class="side-group"><div class="side-label">System</div><a class="side-link" href="/config/"><span class="num">01</span><span>Config</span></a><a class="side-link" href="/infrastructure/"><span class="num">02</span><span>Infrastructure</span></a></div><div class="side-foot">Local dashboard for Construct. Theme + density preferences persist.</div></aside><main class="main"><div class="page"><div class="eyebrow"><span class="dot"></span><span>overview · construct dashboard</span></div><h1 class="page-title">System state</h1><p class="page-lede">The runtime in one place. Approvals, services, model providers, knowledge corpus, and intake — each section deep-links to its dedicated surface.</p><div class="meta-strip"><span class="pill">mode: <!-- -->…</span><span>instance: <!-- -->…</span><span class="sep">·</span><span>embed: <!-- -->stopped</span></div><div class="body"><div class="hero-stats" style="margin-top:0;margin-bottom:24px"><div class="hero-stat"><span class="k">Services</span><span class="v">…</span><span style="display:block;font-size:11.5px;color:var(--muted);margin-top:4px">running</span></div><div class="hero-stat"><span class="k">Approvals</span><span class="v">…</span><span style="display:block;font-size:11.5px;color:var(--muted);margin-top:4px">pending</span></div><div class="hero-stat"><span class="k">Skills</span><span class="v">0</span><span style="display:block;font-size:11.5px;color:var(--muted);margin-top:4px">registered</span></div><div class="hero-stat"><span class="k">Commands</span><span class="v">0</span><span style="display:block;font-size:11.5px;color:var(--muted);margin-top:4px">slash</span></div></div><section class="section open"><button class="section-head" aria-expanded="true" type="button"><span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="9 6 15 12 9 18"></polyline></svg></span><span style="display:flex;flex-direction:column;gap:6px"><span class="section-num">01</span><span class="section-title">Pending approvals</span></span><span class="section-aside"><span>empty</span></span></button><div class="tldr-row"><div class="tldr"><b>TL;DR</b>Items waiting on a human gate. High-risk mutations (work item creation, merge, doc publish, config changes) land here.</div></div><div class="section-body-wrap"><div class="section-body"><div class="section-body-inner"><div class="body"><div style="padding:24px;text-align:center;color:var(--muted);font-family:var(--mono);font-size:12px">Loading…</div></div></div></div></div></section><section class="section"><button class="section-head" aria-expanded="false" type="button"><span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="9 6 15 12 9 18"></polyline></svg></span><span style="display:flex;flex-direction:column;gap:6px"><span class="section-num">02</span><span class="section-title">Services</span></span><span class="section-aside"><span>0/0 running</span></span></button><div class="tldr-row"><div class="tldr"><b>TL;DR</b>Local processes that back the runtime: Postgres, memory bridge, OpenCode bridge, dashboard, embed daemon.</div></div><div class="section-body-wrap"><div class="section-body"><div class="section-body-inner"><div class="body"><div style="padding:24px;text-align:center;color:var(--muted);font-family:var(--mono);font-size:12px">Loading…</div><p style="margin-top:12px"><a class="link" href="/resources/">Open services dashboard →</a></p></div></div></div></div></section><section class="section"><button class="section-head" aria-expanded="false" type="button"><span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="9 6 15 12 9 18"></polyline></svg></span><span style="display:flex;flex-direction:column;gap:6px"><span class="section-num">03</span><span class="section-title">Credentials</span></span><span class="section-aside"><span>0 tracked</span></span></button><div class="tldr-row"><div class="tldr"><b>TL;DR</b>Provider API keys, billing posture, and 1Password references. Set them in /providers.</div></div><div class="section-body-wrap"><div class="section-body"><div class="section-body-inner"><div class="body"><p style="margin-top:12px"><a class="link" href="/providers/">Manage providers →</a></p></div></div></div></div></section><section class="section"><button class="section-head" aria-expanded="false" type="button"><span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="9 6 15 12 9 18"></polyline></svg></span><span style="display:flex;flex-direction:column;gap:6px"><span class="section-num">04</span><span class="section-title">Quick links</span></span><span class="section-aside"></span></button><div class="tldr-row"><div class="tldr"><b>TL;DR</b>Deep links into every dashboard surface.</div></div><div class="section-body-wrap"><div class="section-body"><div class="section-body-inner"><div class="body"><div class="callout"><span class="clt-label">Where to next</span><div><p><a class="link" href="/doctor/">Doctor</a>,<!-- --> <a class="link" href="/knowledge/">Knowledge</a>,<!-- --> <a class="link" href="/intake/">Intake</a>,<!-- --> <a class="link" href="/workflow/">Workflow</a>,<!-- --> <a class="link" href="/agents/">Specialists</a>,<!-- --> <a class="link" href="/models/">Models</a>,<!-- --> <a class="link" href="/audit/">Audit</a>.</p></div></div></div></div></div></div></section></div></div><!--$--><!--/$--></main></div></div><script src="/_next/static/chunks/webpack-8e0503f8a36190b3.js" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[105,[\"9664\",\"static/chunks/9664-af80478aa73ba424.js\",\"8144\",\"static/chunks/8144-da56a4a291c0b21a.js\",\"7177\",\"static/chunks/app/layout-1a2d2674aa220229.js\"],\"AppShellClient\"]\n3:I[5341,[],\"\"]\n4:I[25,[],\"\"]\n5:I[1012,[],\"ClientPageRoot\"]\n6:I[9008,[\"9664\",\"static/chunks/9664-af80478aa73ba424.js\",\"8144\",\"static/chunks/8144-da56a4a291c0b21a.js\",\"8974\",\"static/chunks/app/page-2568fbdd2b0e1eca.js\"],\"default\"]\n9:I[5104,[],\"OutletBoundary\"]\nb:I[7158,[],\"AsyncMetadataOutlet\"]\nd:I[5104,[],\"ViewportBoundary\"]\nf:I[5104,[],\"MetadataBoundary\"]\n10:\"$Sreact.suspense\"\n12:I[4431,[],\"\"]\n:HL[\"/_next/static/media/22a5144ee8d83bca-s.p.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n:HL[\"/_next/static/media/bb3ef058b751a6ad-s.p.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n:HL[\"/_next/static/css/47a506ef57811fb9.css\",\"style\"]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"b\":\"qA_CTDVxjnG_0_bArvNYn\",\"p\":\"\",\"c\":[\"\",\"\"],\"i\":false,\"f\":[[[\"\",{\"children\":[\"__PAGE__\",{}]},\"$undefined\",\"$undefined\",true],[\"\",[\"$\",\"$1\",\"c\",{\"children\":[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/47a506ef57811fb9.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}]],[\"$\",\"html\",null,{\"lang\":\"en\",\"data-theme\":\"dark\",\"data-density\":\"comfortable\",\"data-motion\":\"normal\",\"suppressHydrationWarning\":true,\"className\":\"__variable_629dad __variable_246ccd __variable_3c557b\",\"children\":[\"$\",\"body\",null,{\"children\":[\"$\",\"$L2\",null,{\"nav\":[{\"label\":\"Overview\",\"items\":[{\"id\":\"home\",\"title\":\"Home\",\"href\":\"/\"},{\"id\":\"doctor\",\"title\":\"Doctor\",\"href\":\"/doctor\"},{\"id\":\"resources\",\"title\":\"Services\",\"href\":\"/resources\"}]},{\"label\":\"Activity\",\"items\":[{\"id\":\"audit\",\"title\":\"Audit trail\",\"href\":\"/audit\"},{\"id\":\"snapshots\",\"title\":\"Snapshots\",\"href\":\"/snapshots\"},{\"id\":\"performance\",\"title\":\"Performance\",\"href\":\"/performance\"}]},{\"label\":\"Work\",\"items\":[{\"id\":\"approvals\",\"title\":\"Approvals\",\"href\":\"/approvals\"},{\"id\":\"workflow\",\"title\":\"Workflow\",\"href\":\"/workflow\"},{\"id\":\"beads\",\"title\":\"Beads\",\"href\":\"/beads\"},{\"id\":\"artifacts\",\"title\":\"Artifacts\",\"href\":\"/artifacts\"},{\"id\":\"intake\",\"title\":\"Intake\",\"href\":\"/intake\"}]},{\"label\":\"Specialists\",\"items\":[{\"id\":\"agents\",\"title\":\"Specialists\",\"href\":\"/agents\"},{\"id\":\"skills\",\"title\":\"Skills\",\"href\":\"/skills\"},{\"id\":\"commands\",\"title\":\"Slash commands\",\"href\":\"/commands\"},{\"id\":\"hooks\",\"title\":\"Hooks\",\"href\":\"/hooks\"},{\"id\":\"plugins\",\"title\":\"Plugins\",\"href\":\"/plugins\"}]},{\"label\":\"Models \u0026 Providers\",\"items\":[{\"id\":\"models\",\"title\":\"Models\",\"href\":\"/models\"},{\"id\":\"providers\",\"title\":\"Providers\",\"href\":\"/providers\"},{\"id\":\"mcp\",\"title\":\"MCP servers\",\"href\":\"/mcp\"}]},{\"label\":\"Knowledge\",\"items\":[{\"id\":\"knowledge\",\"title\":\"Knowledge\",\"href\":\"/knowledge\"},{\"id\":\"editor\",\"title\":\"Editor\",\"href\":\"/editor\"}]},{\"label\":\"System\",\"items\":[{\"id\":\"config\",\"title\":\"Config\",\"href\":\"/config\"},{\"id\":\"infrastructure\",\"title\":\"Infrastructure\",\"href\":\"/infrastructure\"}]}],\"children\":[\"$\",\"$L3\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L4\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":[[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":404}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],[]],\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]}]}]}]]}],{\"children\":[\"__PAGE__\",[\"$\",\"$1\",\"c\",{\"children\":[[\"$\",\"$L5\",null,{\"Component\":\"$6\",\"searchParams\":{},\"params\":{},\"promises\":[\"$@7\",\"$@8\"]}],null,[\"$\",\"$L9\",null,{\"children\":[\"$La\",[\"$\",\"$Lb\",null,{\"promise\":\"$@c\"}]]}]]}],{},null,false]},null,false],[\"$\",\"$1\",\"h\",{\"children\":[null,[[\"$\",\"$Ld\",null,{\"children\":\"$Le\"}],[\"$\",\"meta\",null,{\"name\":\"next-size-adjust\",\"content\":\"\"}]],[\"$\",\"$Lf\",null,{\"children\":[\"$\",\"div\",null,{\"hidden\":true,\"children\":[\"$\",\"$10\",null,{\"fallback\":null,\"children\":\"$L11\"}]}]}]]}],false]],\"m\":\"$undefined\",\"G\":[\"$12\",[]],\"s\":false,\"S\":true}\n"])</script><script>self.__next_f.push([1,"7:{}\n8:\"$0:f:0:1:2:children:1:props:children:0:props:params\"\n"])</script><script>self.__next_f.push([1,"e:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}]]\na:null\n"])</script><script>self.__next_f.push([1,"c:{\"metadata\":[[\"$\",\"title\",\"0\",{\"children\":\"Construct — Dashboard\"}],[\"$\",\"meta\",\"1\",{\"name\":\"description\",\"content\":\"Local operations dashboard for Construct — approvals, health, knowledge, models, providers.\"}]],\"error\":null,\"digest\":\"$undefined\"}\n"])</script><script>self.__next_f.push([1,"11:\"$c:metadata\"\n"])</script></body></html>
|
package/lib/setup.mjs
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* lib/setup.mjs —
|
|
3
|
+
* lib/setup.mjs — Machine-scoped first-run setup wizard for Construct.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Installs cm/cass, configures managed defaults, starts local Postgres
|
|
6
|
+
* (consent-driven), pre-warms the embedding model, wires MCP integrations,
|
|
7
|
+
* and writes ~/.construct/config.env. Invoked by `construct install` and
|
|
8
|
+
* by lib/install/first-invocation.mjs when a TTY user is missing resources.
|
|
9
|
+
*
|
|
10
|
+
* Project-scoped scaffolding (`.cx/`, AGENTS.md, plan.md, adapters) lives
|
|
11
|
+
* in lib/init-unified.mjs — invoked by `construct init`. These two flows
|
|
12
|
+
* are intentionally separate: install runs once per machine, init runs
|
|
13
|
+
* once per repo.
|
|
8
14
|
*/
|
|
9
15
|
|
|
10
16
|
import fs from 'node:fs';
|
|
@@ -36,10 +42,10 @@ const LOCAL_POSTGRES_DB = 'construct';
|
|
|
36
42
|
const LOCAL_DATABASE_URL = `postgresql://${LOCAL_POSTGRES_USER}:${LOCAL_POSTGRES_PASSWORD}@127.0.0.1:${LOCAL_POSTGRES_PORT}/${LOCAL_POSTGRES_DB}`;
|
|
37
43
|
|
|
38
44
|
function printHelp() {
|
|
39
|
-
console.log(`Construct setup
|
|
45
|
+
console.log(`Construct install — machine setup (once per machine)
|
|
40
46
|
|
|
41
47
|
Usage:
|
|
42
|
-
construct
|
|
48
|
+
construct install [--yes] [--no-docker]
|
|
43
49
|
|
|
44
50
|
What it does:
|
|
45
51
|
- creates ~/.construct/config.env
|
|
@@ -52,6 +58,7 @@ What it does:
|
|
|
52
58
|
- runs construct doctor
|
|
53
59
|
- detects the project tech stack and writes .cx/project-profile.json
|
|
54
60
|
|
|
61
|
+
For project setup (once per repo): construct init
|
|
55
62
|
Use --yes to run without prompts and accept detected environment defaults.`);
|
|
56
63
|
}
|
|
57
64
|
|
|
@@ -465,6 +472,35 @@ export function ensureGitHooksPath({ cwd = process.cwd() } = {}) {
|
|
|
465
472
|
};
|
|
466
473
|
}
|
|
467
474
|
|
|
475
|
+
// $HOME/.construct/lib/hooks/* is the resolution path for Claude Code
|
|
476
|
+
// Stop-hook commands. Absent symlink → every Stop hook fails silently,
|
|
477
|
+
// including stop-notify (dashboard cost feed). Idempotent: matching
|
|
478
|
+
// symlink left alone, stale symlink replaced, real dir refused.
|
|
479
|
+
|
|
480
|
+
export function ensureLibSymlink({ homeDir = HOME, rootDir = ROOT_DIR } = {}) {
|
|
481
|
+
const target = path.join(homeDir, '.construct', 'lib');
|
|
482
|
+
const source = path.join(rootDir, 'lib');
|
|
483
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
484
|
+
let stat = null;
|
|
485
|
+
try { stat = fs.lstatSync(target); } catch { stat = null; }
|
|
486
|
+
if (!stat) {
|
|
487
|
+
fs.symlinkSync(source, target, 'dir');
|
|
488
|
+
return { status: 'created', target, source };
|
|
489
|
+
}
|
|
490
|
+
if (stat.isSymbolicLink()) {
|
|
491
|
+
const current = fs.readlinkSync(target);
|
|
492
|
+
if (current === source) return { status: 'kept', target, source };
|
|
493
|
+
fs.unlinkSync(target);
|
|
494
|
+
fs.symlinkSync(source, target, 'dir');
|
|
495
|
+
return { status: 'replaced', target, source, previous: current };
|
|
496
|
+
}
|
|
497
|
+
return {
|
|
498
|
+
status: 'conflict',
|
|
499
|
+
target,
|
|
500
|
+
message: `${target} exists and is not a symlink — leaving alone. Move or remove it, then re-run \`construct install\` to wire the hooks.`,
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
|
|
468
504
|
export async function runSetup({ rootDir = ROOT_DIR, args = [], homeDir = HOME } = {}) {
|
|
469
505
|
const argSet = new Set(args);
|
|
470
506
|
const isYes = argSet.has('--yes');
|
|
@@ -480,9 +516,17 @@ export async function runSetup({ rootDir = ROOT_DIR, args = [], homeDir = HOME }
|
|
|
480
516
|
|
|
481
517
|
const envPath = ensureUserConfig(homeDir);
|
|
482
518
|
const opencodePath = ensureOpenCodeConfig();
|
|
519
|
+
const libLink = ensureLibSymlink({ homeDir, rootDir });
|
|
483
520
|
|
|
484
521
|
console.log(`User config: ${envPath}`);
|
|
485
522
|
console.log(`OpenCode config: ${opencodePath}`);
|
|
523
|
+
if (libLink.status === 'created' || libLink.status === 'replaced') {
|
|
524
|
+
console.log(`Hook lib link: ${libLink.target} → ${libLink.source} (${libLink.status})`);
|
|
525
|
+
} else if (libLink.status === 'kept') {
|
|
526
|
+
console.log(`Hook lib link: ${libLink.target} already in place`);
|
|
527
|
+
} else if (libLink.status === 'conflict') {
|
|
528
|
+
console.log(`Hook lib link: ${libLink.message}`);
|
|
529
|
+
}
|
|
486
530
|
warnIfGlobalCommandIsUnavailable();
|
|
487
531
|
|
|
488
532
|
const cmInstall = ensureCmInstalled({ env: process.env });
|
|
@@ -582,6 +626,7 @@ export async function runSetup({ rootDir = ROOT_DIR, args = [], homeDir = HOME }
|
|
|
582
626
|
});
|
|
583
627
|
if (cheapestConsent.decision) {
|
|
584
628
|
try {
|
|
629
|
+
const { applyToEnv } = await import('./model-router.mjs');
|
|
585
630
|
const selections = await selectCheapestForAllTiers({ env: process.env });
|
|
586
631
|
const applied = {};
|
|
587
632
|
for (const tier of ['reasoning', 'standard', 'fast']) {
|
|
@@ -637,7 +682,7 @@ export async function runSetup({ rootDir = ROOT_DIR, args = [], homeDir = HOME }
|
|
|
637
682
|
|
|
638
683
|
if (isYes) {
|
|
639
684
|
console.log('\nManaged setup:');
|
|
640
|
-
console.log(` Deployment mode: ${getDeploymentMode(env) || DEFAULT_DEPLOYMENT_MODE} (set in construct.config.json — runtime env override available via ${DEPLOYMENT_MODE_ENV_KEY})`);
|
|
685
|
+
console.log(` Deployment mode: ${getDeploymentMode(process.env) || DEFAULT_DEPLOYMENT_MODE} (set in construct.config.json — runtime env override available via ${DEPLOYMENT_MODE_ENV_KEY})`);
|
|
641
686
|
console.log(` Vector index: ${managedValues.CONSTRUCT_VECTOR_INDEX_PATH}`);
|
|
642
687
|
console.log(` Vector model: ${managedValues.CONSTRUCT_VECTOR_MODEL}`);
|
|
643
688
|
console.log(` Trace backend: ${managedValues.CONSTRUCT_TRACE_BACKEND}`);
|
|
@@ -723,12 +768,12 @@ export async function runSetup({ rootDir = ROOT_DIR, args = [], homeDir = HOME }
|
|
|
723
768
|
runConstruct(['mcp', 'add', 'github', '--auto'], { optional: true });
|
|
724
769
|
} else {
|
|
725
770
|
console.log('\nManaged defaults written:');
|
|
726
|
-
console.log(` Deployment mode: ${getDeploymentMode(env) || DEFAULT_DEPLOYMENT_MODE} (set in construct.config.json — runtime env override available via ${DEPLOYMENT_MODE_ENV_KEY})`);
|
|
771
|
+
console.log(` Deployment mode: ${getDeploymentMode(process.env) || DEFAULT_DEPLOYMENT_MODE} (set in construct.config.json — runtime env override available via ${DEPLOYMENT_MODE_ENV_KEY})`);
|
|
727
772
|
console.log(` Vector index: ${managedValues.CONSTRUCT_VECTOR_INDEX_PATH}`);
|
|
728
773
|
console.log(` Trace backend: ${managedValues.CONSTRUCT_TRACE_BACKEND}${managedValues.CONSTRUCT_TELEMETRY_URL ? ` (${managedValues.CONSTRUCT_TELEMETRY_URL})` : ''}`);
|
|
729
774
|
console.log(` Pressure guard: swap ${managedValues.CONSTRUCT_PRESSURE_GUARD_SWAP_GB} GiB, opencode max ${managedValues.CONSTRUCT_PRESSURE_GUARD_MAX_OPENCODE}`);
|
|
730
775
|
console.log('\nFor unattended setup, including local Postgres when Docker is running:');
|
|
731
|
-
console.log(' construct
|
|
776
|
+
console.log(' construct install --yes');
|
|
732
777
|
}
|
|
733
778
|
|
|
734
779
|
runConstruct(['sync']);
|
|
@@ -100,7 +100,7 @@ export const POSTCONDITIONS = {
|
|
|
100
100
|
* @param {object} packet — the output packet about to hand off
|
|
101
101
|
* @returns {{ ok: boolean, producer: string, failures: Array<{id, reason}> }}
|
|
102
102
|
*/
|
|
103
|
-
export function
|
|
103
|
+
export function validateBinaryPostconditions(producer, packet) {
|
|
104
104
|
const rules = POSTCONDITIONS[producer] || [];
|
|
105
105
|
if (rules.length === 0) return { ok: true, producer, failures: [] };
|
|
106
106
|
const failures = [];
|