@rubytech/create-maxy-code 0.1.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/dist/__tests__/account-id-env.test.js +48 -0
- package/dist/__tests__/apt-resolve.test.js +179 -0
- package/dist/__tests__/brew-install.test.js +141 -0
- package/dist/__tests__/brew-resolve.test.js +103 -0
- package/dist/__tests__/cdp-port-no-silent-fallback.test.js +53 -0
- package/dist/__tests__/launchd-plist.test.js +149 -0
- package/dist/__tests__/macos-version.test.js +96 -0
- package/dist/__tests__/peer-brand-detect.test.js +103 -0
- package/dist/__tests__/platform-detect.test.js +50 -0
- package/dist/__tests__/port-canonicalisation.test.js +200 -0
- package/dist/__tests__/preflight-port-classifier.test.js +330 -0
- package/dist/__tests__/snap-chromium.test.js +115 -0
- package/dist/apt-resolve.js +73 -0
- package/dist/brew-install.js +175 -0
- package/dist/brew-resolve.js +68 -0
- package/dist/index.js +3325 -0
- package/dist/launchd-plist.js +68 -0
- package/dist/macos-version.js +53 -0
- package/dist/peer-brand-detect.js +39 -0
- package/dist/pinned-binaries.js +12 -0
- package/dist/platform-detect.js +36 -0
- package/dist/port-resolution.js +153 -0
- package/dist/preflight-port-classifier.js +222 -0
- package/dist/snap-chromium.js +89 -0
- package/dist/uninstall.js +861 -0
- package/package.json +32 -0
- package/payload/platform/config/brand-registry.json +20 -0
- package/payload/platform/config/brand.json +54 -0
- package/payload/platform/lib/account-enumeration/dist/__tests__/enumerate.test.d.ts +2 -0
- package/payload/platform/lib/account-enumeration/dist/__tests__/enumerate.test.d.ts.map +1 -0
- package/payload/platform/lib/account-enumeration/dist/__tests__/enumerate.test.js +88 -0
- package/payload/platform/lib/account-enumeration/dist/__tests__/enumerate.test.js.map +1 -0
- package/payload/platform/lib/account-enumeration/dist/__tests__/validate-env.test.d.ts +2 -0
- package/payload/platform/lib/account-enumeration/dist/__tests__/validate-env.test.d.ts.map +1 -0
- package/payload/platform/lib/account-enumeration/dist/__tests__/validate-env.test.js +55 -0
- package/payload/platform/lib/account-enumeration/dist/__tests__/validate-env.test.js.map +1 -0
- package/payload/platform/lib/account-enumeration/dist/index.d.ts +49 -0
- package/payload/platform/lib/account-enumeration/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/account-enumeration/dist/index.js +109 -0
- package/payload/platform/lib/account-enumeration/dist/index.js.map +1 -0
- package/payload/platform/lib/account-enumeration/src/__tests__/enumerate.test.ts +94 -0
- package/payload/platform/lib/account-enumeration/src/__tests__/validate-env.test.ts +57 -0
- package/payload/platform/lib/account-enumeration/src/index.ts +140 -0
- package/payload/platform/lib/account-enumeration/tsconfig.json +8 -0
- package/payload/platform/lib/admins-write/dist/index.d.ts +87 -0
- package/payload/platform/lib/admins-write/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/admins-write/dist/index.js +248 -0
- package/payload/platform/lib/admins-write/dist/index.js.map +1 -0
- package/payload/platform/lib/admins-write/src/index.ts +311 -0
- package/payload/platform/lib/admins-write/tsconfig.json +8 -0
- package/payload/platform/lib/anthropic-key/dist/index.d.ts +22 -0
- package/payload/platform/lib/anthropic-key/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/anthropic-key/dist/index.js +232 -0
- package/payload/platform/lib/anthropic-key/dist/index.js.map +1 -0
- package/payload/platform/lib/brand-templating/dist/index.d.ts +18 -0
- package/payload/platform/lib/brand-templating/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/brand-templating/dist/index.js +69 -0
- package/payload/platform/lib/brand-templating/dist/index.js.map +1 -0
- package/payload/platform/lib/brand-templating/src/index.ts +76 -0
- package/payload/platform/lib/brand-templating/tsconfig.json +8 -0
- package/payload/platform/lib/device-url/dist/index.d.ts +44 -0
- package/payload/platform/lib/device-url/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/device-url/dist/index.js +68 -0
- package/payload/platform/lib/device-url/dist/index.js.map +1 -0
- package/payload/platform/lib/device-url/src/index.ts +78 -0
- package/payload/platform/lib/device-url/tsconfig.json +8 -0
- package/payload/platform/lib/entitlement/PUBKEY-HASH.txt +1 -0
- package/payload/platform/lib/entitlement/dist/canonicalize.d.ts +26 -0
- package/payload/platform/lib/entitlement/dist/canonicalize.d.ts.map +1 -0
- package/payload/platform/lib/entitlement/dist/canonicalize.js +54 -0
- package/payload/platform/lib/entitlement/dist/canonicalize.js.map +1 -0
- package/payload/platform/lib/entitlement/dist/index.d.ts +76 -0
- package/payload/platform/lib/entitlement/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/entitlement/dist/index.js +293 -0
- package/payload/platform/lib/entitlement/dist/index.js.map +1 -0
- package/payload/platform/lib/entitlement/rubytech-pubkey.pem +3 -0
- package/payload/platform/lib/graph-mcp/dist/__tests__/cypher-validate-write.test.d.ts +2 -0
- package/payload/platform/lib/graph-mcp/dist/__tests__/cypher-validate-write.test.d.ts.map +1 -0
- package/payload/platform/lib/graph-mcp/dist/__tests__/cypher-validate-write.test.js +97 -0
- package/payload/platform/lib/graph-mcp/dist/__tests__/cypher-validate-write.test.js.map +1 -0
- package/payload/platform/lib/graph-mcp/dist/__tests__/cypher-validate.test.d.ts +2 -0
- package/payload/platform/lib/graph-mcp/dist/__tests__/cypher-validate.test.d.ts.map +1 -0
- package/payload/platform/lib/graph-mcp/dist/__tests__/cypher-validate.test.js +112 -0
- package/payload/platform/lib/graph-mcp/dist/__tests__/cypher-validate.test.js.map +1 -0
- package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cache.test.d.ts +2 -0
- package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cache.test.d.ts.map +1 -0
- package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cache.test.js +163 -0
- package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cache.test.js.map +1 -0
- package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cypher-parser.test.d.ts +2 -0
- package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cypher-parser.test.d.ts.map +1 -0
- package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cypher-parser.test.js +89 -0
- package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cypher-parser.test.js.map +1 -0
- package/payload/platform/lib/graph-mcp/dist/__tests__/warnings-envelope.test.d.ts +2 -0
- package/payload/platform/lib/graph-mcp/dist/__tests__/warnings-envelope.test.d.ts.map +1 -0
- package/payload/platform/lib/graph-mcp/dist/__tests__/warnings-envelope.test.js +140 -0
- package/payload/platform/lib/graph-mcp/dist/__tests__/warnings-envelope.test.js.map +1 -0
- package/payload/platform/lib/graph-mcp/dist/cypher-rewrite-stamp.d.ts +37 -0
- package/payload/platform/lib/graph-mcp/dist/cypher-rewrite-stamp.d.ts.map +1 -0
- package/payload/platform/lib/graph-mcp/dist/cypher-rewrite-stamp.js +333 -0
- package/payload/platform/lib/graph-mcp/dist/cypher-rewrite-stamp.js.map +1 -0
- package/payload/platform/lib/graph-mcp/dist/cypher-shim-read.d.ts +85 -0
- package/payload/platform/lib/graph-mcp/dist/cypher-shim-read.d.ts.map +1 -0
- package/payload/platform/lib/graph-mcp/dist/cypher-shim-read.js +93 -0
- package/payload/platform/lib/graph-mcp/dist/cypher-shim-read.js.map +1 -0
- package/payload/platform/lib/graph-mcp/dist/cypher-shim-write.d.ts +71 -0
- package/payload/platform/lib/graph-mcp/dist/cypher-shim-write.d.ts.map +1 -0
- package/payload/platform/lib/graph-mcp/dist/cypher-shim-write.js +168 -0
- package/payload/platform/lib/graph-mcp/dist/cypher-shim-write.js.map +1 -0
- package/payload/platform/lib/graph-mcp/dist/cypher-validate.d.ts +50 -0
- package/payload/platform/lib/graph-mcp/dist/cypher-validate.d.ts.map +1 -0
- package/payload/platform/lib/graph-mcp/dist/cypher-validate.js +197 -0
- package/payload/platform/lib/graph-mcp/dist/cypher-validate.js.map +1 -0
- package/payload/platform/lib/graph-mcp/dist/index.d.ts +26 -0
- package/payload/platform/lib/graph-mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/graph-mcp/dist/index.js +847 -0
- package/payload/platform/lib/graph-mcp/dist/index.js.map +1 -0
- package/payload/platform/lib/graph-mcp/dist/schema-cache.d.ts +75 -0
- package/payload/platform/lib/graph-mcp/dist/schema-cache.d.ts.map +1 -0
- package/payload/platform/lib/graph-mcp/dist/schema-cache.js +217 -0
- package/payload/platform/lib/graph-mcp/dist/schema-cache.js.map +1 -0
- package/payload/platform/lib/graph-mcp/dist/schema-cypher-parser.d.ts +42 -0
- package/payload/platform/lib/graph-mcp/dist/schema-cypher-parser.d.ts.map +1 -0
- package/payload/platform/lib/graph-mcp/dist/schema-cypher-parser.js +87 -0
- package/payload/platform/lib/graph-mcp/dist/schema-cypher-parser.js.map +1 -0
- package/payload/platform/lib/graph-mcp/src/__tests__/cypher-validate-write.test.ts +150 -0
- package/payload/platform/lib/graph-mcp/src/__tests__/cypher-validate.test.ts +141 -0
- package/payload/platform/lib/graph-mcp/src/__tests__/schema-cache.test.ts +169 -0
- package/payload/platform/lib/graph-mcp/src/__tests__/schema-cypher-parser.test.ts +99 -0
- package/payload/platform/lib/graph-mcp/src/__tests__/warnings-envelope.test.ts +151 -0
- package/payload/platform/lib/graph-mcp/src/cypher-rewrite-stamp.ts +349 -0
- package/payload/platform/lib/graph-mcp/src/cypher-shim-read.ts +141 -0
- package/payload/platform/lib/graph-mcp/src/cypher-shim-write.ts +240 -0
- package/payload/platform/lib/graph-mcp/src/cypher-validate.ts +249 -0
- package/payload/platform/lib/graph-mcp/src/index.ts +1076 -0
- package/payload/platform/lib/graph-mcp/src/schema-cache.ts +242 -0
- package/payload/platform/lib/graph-mcp/src/schema-cypher-parser.ts +84 -0
- package/payload/platform/lib/graph-mcp/tsconfig.json +8 -0
- package/payload/platform/lib/graph-search/dist/index.d.ts +227 -0
- package/payload/platform/lib/graph-search/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/graph-search/dist/index.js +525 -0
- package/payload/platform/lib/graph-search/dist/index.js.map +1 -0
- package/payload/platform/lib/graph-search/src/__tests__/bm25-label-gate.test.ts +88 -0
- package/payload/platform/lib/graph-search/src/__tests__/bm25-only.test.ts +129 -0
- package/payload/platform/lib/graph-search/src/__tests__/bm25-strong-bypass-threshold.test.ts +126 -0
- package/payload/platform/lib/graph-search/src/__tests__/brochure-threshold.test.ts +136 -0
- package/payload/platform/lib/graph-search/src/__tests__/escape-and-normalise.test.ts +53 -0
- package/payload/platform/lib/graph-search/src/__tests__/expand-batch.test.ts +206 -0
- package/payload/platform/lib/graph-search/src/__tests__/fulltext-coverage.test.ts +280 -0
- package/payload/platform/lib/graph-search/src/__tests__/hybrid.test.ts +262 -0
- package/payload/platform/lib/graph-search/src/__tests__/vector-threshold.test.ts +170 -0
- package/payload/platform/lib/graph-search/src/index.ts +718 -0
- package/payload/platform/lib/graph-search/tsconfig.json +9 -0
- package/payload/platform/lib/graph-search/vitest.config.ts +9 -0
- package/payload/platform/lib/graph-trash/dist/index.d.ts +99 -0
- package/payload/platform/lib/graph-trash/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/graph-trash/dist/index.js +333 -0
- package/payload/platform/lib/graph-trash/dist/index.js.map +1 -0
- package/payload/platform/lib/graph-trash/src/index.ts +475 -0
- package/payload/platform/lib/graph-trash/tsconfig.json +8 -0
- package/payload/platform/lib/graph-write/dist/__tests__/account-id-gate.test.d.ts +2 -0
- package/payload/platform/lib/graph-write/dist/__tests__/account-id-gate.test.d.ts.map +1 -0
- package/payload/platform/lib/graph-write/dist/__tests__/account-id-gate.test.js +165 -0
- package/payload/platform/lib/graph-write/dist/__tests__/account-id-gate.test.js.map +1 -0
- package/payload/platform/lib/graph-write/dist/__tests__/action-provenance-gate.test.d.ts +2 -0
- package/payload/platform/lib/graph-write/dist/__tests__/action-provenance-gate.test.d.ts.map +1 -0
- package/payload/platform/lib/graph-write/dist/__tests__/action-provenance-gate.test.js +226 -0
- package/payload/platform/lib/graph-write/dist/__tests__/action-provenance-gate.test.js.map +1 -0
- package/payload/platform/lib/graph-write/dist/__tests__/audit.test.d.ts +2 -0
- package/payload/platform/lib/graph-write/dist/__tests__/audit.test.d.ts.map +1 -0
- package/payload/platform/lib/graph-write/dist/__tests__/audit.test.js +147 -0
- package/payload/platform/lib/graph-write/dist/__tests__/audit.test.js.map +1 -0
- package/payload/platform/lib/graph-write/dist/audit.d.ts +84 -0
- package/payload/platform/lib/graph-write/dist/audit.d.ts.map +1 -0
- package/payload/platform/lib/graph-write/dist/audit.js +129 -0
- package/payload/platform/lib/graph-write/dist/audit.js.map +1 -0
- package/payload/platform/lib/graph-write/dist/conversation-provenance.d.ts +26 -0
- package/payload/platform/lib/graph-write/dist/conversation-provenance.d.ts.map +1 -0
- package/payload/platform/lib/graph-write/dist/conversation-provenance.js +81 -0
- package/payload/platform/lib/graph-write/dist/conversation-provenance.js.map +1 -0
- package/payload/platform/lib/graph-write/dist/index.d.ts +124 -0
- package/payload/platform/lib/graph-write/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/graph-write/dist/index.js +288 -0
- package/payload/platform/lib/graph-write/dist/index.js.map +1 -0
- package/payload/platform/lib/graph-write/src/__tests__/account-id-gate.test.ts +189 -0
- package/payload/platform/lib/graph-write/src/__tests__/action-provenance-gate.test.ts +252 -0
- package/payload/platform/lib/graph-write/src/__tests__/audit.test.ts +162 -0
- package/payload/platform/lib/graph-write/src/audit.ts +182 -0
- package/payload/platform/lib/graph-write/src/conversation-provenance.ts +140 -0
- package/payload/platform/lib/graph-write/src/index.ts +386 -0
- package/payload/platform/lib/graph-write/tsconfig.json +8 -0
- package/payload/platform/lib/mcp-eager/dist/index.d.ts +61 -0
- package/payload/platform/lib/mcp-eager/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/mcp-eager/dist/index.js +49 -0
- package/payload/platform/lib/mcp-eager/dist/index.js.map +1 -0
- package/payload/platform/lib/mcp-eager/src/index.ts +78 -0
- package/payload/platform/lib/mcp-eager/tsconfig.json +8 -0
- package/payload/platform/lib/mcp-spawn-tee/dist/index.d.ts +53 -0
- package/payload/platform/lib/mcp-spawn-tee/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/mcp-spawn-tee/dist/index.js +132 -0
- package/payload/platform/lib/mcp-spawn-tee/dist/index.js.map +1 -0
- package/payload/platform/lib/mcp-spawn-tee/src/index.ts +134 -0
- package/payload/platform/lib/mcp-spawn-tee/tsconfig.json +8 -0
- package/payload/platform/lib/mcp-stderr-tee/dist/index.d.ts +51 -0
- package/payload/platform/lib/mcp-stderr-tee/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/mcp-stderr-tee/dist/index.js +196 -0
- package/payload/platform/lib/mcp-stderr-tee/dist/index.js.map +1 -0
- package/payload/platform/lib/mcp-stderr-tee/src/index.ts +206 -0
- package/payload/platform/lib/mcp-stderr-tee/tsconfig.json +8 -0
- package/payload/platform/lib/models/dist/index.d.ts +7 -0
- package/payload/platform/lib/models/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/models/dist/index.js +20 -0
- package/payload/platform/lib/models/dist/index.js.map +1 -0
- package/payload/platform/lib/models/src/index.ts +18 -0
- package/payload/platform/lib/models/tsconfig.json +8 -0
- package/payload/platform/lib/oauth-llm/dist/index.d.ts +118 -0
- package/payload/platform/lib/oauth-llm/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/oauth-llm/dist/index.js +388 -0
- package/payload/platform/lib/oauth-llm/dist/index.js.map +1 -0
- package/payload/platform/lib/oauth-llm/src/index.ts +585 -0
- package/payload/platform/lib/oauth-llm/tsconfig.json +8 -0
- package/payload/platform/lib/persistent-components/dist/index.d.ts +21 -0
- package/payload/platform/lib/persistent-components/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/persistent-components/dist/index.js +32 -0
- package/payload/platform/lib/persistent-components/dist/index.js.map +1 -0
- package/payload/platform/lib/persistent-components/src/index.ts +28 -0
- package/payload/platform/lib/persistent-components/tsconfig.json +8 -0
- package/payload/platform/lib/screening-patterns/dist/index.d.ts +29 -0
- package/payload/platform/lib/screening-patterns/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/screening-patterns/dist/index.js +48 -0
- package/payload/platform/lib/screening-patterns/dist/index.js.map +1 -0
- package/payload/platform/lib/screening-patterns/src/index.ts +51 -0
- package/payload/platform/lib/screening-patterns/tsconfig.json +8 -0
- package/payload/platform/lib/task-secrets/dist/index.d.ts +40 -0
- package/payload/platform/lib/task-secrets/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/task-secrets/dist/index.js +44 -0
- package/payload/platform/lib/task-secrets/dist/index.js.map +1 -0
- package/payload/platform/lib/task-secrets/src/__tests__/redact-secrets.test.ts +127 -0
- package/payload/platform/lib/task-secrets/src/index.ts +77 -0
- package/payload/platform/lib/task-secrets/tsconfig.json +9 -0
- package/payload/platform/lib/task-secrets/vitest.config.ts +9 -0
- package/payload/platform/neo4j/edge-annotations.json +154 -0
- package/payload/platform/neo4j/schema.cypher +1104 -0
- package/payload/platform/package-lock.json +3576 -0
- package/payload/platform/package.json +25 -0
- package/payload/platform/plugins/admin/PLUGIN.md +81 -0
- package/payload/platform/plugins/admin/hooks/__tests__/archive-ingest-surface-gate.test.sh +191 -0
- package/payload/platform/plugins/admin/hooks/__tests__/playwright-file-guard.test.sh +278 -0
- package/payload/platform/plugins/admin/hooks/__tests__/pre-tool-use-base64-guard.test.sh +204 -0
- package/payload/platform/plugins/admin/hooks/archive-ingest-surface-gate.sh +225 -0
- package/payload/platform/plugins/admin/hooks/playwright-file-guard.sh +214 -0
- package/payload/platform/plugins/admin/hooks/pre-tool-use.sh +294 -0
- package/payload/platform/plugins/admin/hooks/webfetch-preflight.mjs +363 -0
- package/payload/platform/plugins/admin/mcp/dist/__tests__/plugin-read-skill-resolution.test.d.ts +2 -0
- package/payload/platform/plugins/admin/mcp/dist/__tests__/plugin-read-skill-resolution.test.d.ts.map +1 -0
- package/payload/platform/plugins/admin/mcp/dist/__tests__/plugin-read-skill-resolution.test.js +91 -0
- package/payload/platform/plugins/admin/mcp/dist/__tests__/plugin-read-skill-resolution.test.js.map +1 -0
- package/payload/platform/plugins/admin/mcp/dist/__tests__/public-hostname.test.d.ts +2 -0
- package/payload/platform/plugins/admin/mcp/dist/__tests__/public-hostname.test.d.ts.map +1 -0
- package/payload/platform/plugins/admin/mcp/dist/__tests__/public-hostname.test.js +98 -0
- package/payload/platform/plugins/admin/mcp/dist/__tests__/public-hostname.test.js.map +1 -0
- package/payload/platform/plugins/admin/mcp/dist/__tests__/skill-load-required-inputs.test.d.ts +2 -0
- package/payload/platform/plugins/admin/mcp/dist/__tests__/skill-load-required-inputs.test.d.ts.map +1 -0
- package/payload/platform/plugins/admin/mcp/dist/__tests__/skill-load-required-inputs.test.js +141 -0
- package/payload/platform/plugins/admin/mcp/dist/__tests__/skill-load-required-inputs.test.js.map +1 -0
- package/payload/platform/plugins/admin/mcp/dist/__tests__/skill-load.test.d.ts +2 -0
- package/payload/platform/plugins/admin/mcp/dist/__tests__/skill-load.test.d.ts.map +1 -0
- package/payload/platform/plugins/admin/mcp/dist/__tests__/skill-load.test.js +88 -0
- package/payload/platform/plugins/admin/mcp/dist/__tests__/skill-load.test.js.map +1 -0
- package/payload/platform/plugins/admin/mcp/dist/index.d.ts +2 -0
- package/payload/platform/plugins/admin/mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/plugins/admin/mcp/dist/index.js +3495 -0
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -0
- package/payload/platform/plugins/admin/mcp/dist/lib/neo4j.d.ts +5 -0
- package/payload/platform/plugins/admin/mcp/dist/lib/neo4j.d.ts.map +1 -0
- package/payload/platform/plugins/admin/mcp/dist/lib/neo4j.js +40 -0
- package/payload/platform/plugins/admin/mcp/dist/lib/neo4j.js.map +1 -0
- package/payload/platform/plugins/admin/mcp/dist/lib/onboarding.d.ts +39 -0
- package/payload/platform/plugins/admin/mcp/dist/lib/onboarding.d.ts.map +1 -0
- package/payload/platform/plugins/admin/mcp/dist/lib/onboarding.js +249 -0
- package/payload/platform/plugins/admin/mcp/dist/lib/onboarding.js.map +1 -0
- package/payload/platform/plugins/admin/mcp/dist/lib/public-hostname.d.ts +15 -0
- package/payload/platform/plugins/admin/mcp/dist/lib/public-hostname.d.ts.map +1 -0
- package/payload/platform/plugins/admin/mcp/dist/lib/public-hostname.js +73 -0
- package/payload/platform/plugins/admin/mcp/dist/lib/public-hostname.js.map +1 -0
- package/payload/platform/plugins/admin/mcp/dist/skill-resolution.d.ts +35 -0
- package/payload/platform/plugins/admin/mcp/dist/skill-resolution.d.ts.map +1 -0
- package/payload/platform/plugins/admin/mcp/dist/skill-resolution.js +140 -0
- package/payload/platform/plugins/admin/mcp/dist/skill-resolution.js.map +1 -0
- package/payload/platform/plugins/admin/mcp/package.json +23 -0
- package/payload/platform/plugins/admin/mcp/vitest.config.ts +9 -0
- package/payload/platform/plugins/admin/references/chat-ui-guide.md +31 -0
- package/payload/platform/plugins/admin/references/contextual-ui.md +107 -0
- package/payload/platform/plugins/admin/skills/a4-print-documents/SKILL.md +241 -0
- package/payload/platform/plugins/admin/skills/access-manager/SKILL.md +28 -0
- package/payload/platform/plugins/admin/skills/access-manager/references/operations.md +197 -0
- package/payload/platform/plugins/admin/skills/business-profile/SKILL.md +53 -0
- package/payload/platform/plugins/admin/skills/datetime/SKILL.md +91 -0
- package/payload/platform/plugins/admin/skills/deck-pages/SKILL.md +418 -0
- package/payload/platform/plugins/admin/skills/onboarding/SKILL.md +255 -0
- package/payload/platform/plugins/admin/skills/plainly/SKILL.md +105 -0
- package/payload/platform/plugins/admin/skills/plainly/references/worked-examples.md +301 -0
- package/payload/platform/plugins/admin/skills/plugin-management/SKILL.md +99 -0
- package/payload/platform/plugins/admin/skills/public-agent-manager/SKILL.md +277 -0
- package/payload/platform/plugins/admin/skills/publish-site/SKILL.md +72 -0
- package/payload/platform/plugins/admin/skills/qr-code/SKILL.md +35 -0
- package/payload/platform/plugins/admin/skills/qr-code/references/data-formats.md +113 -0
- package/payload/platform/plugins/admin/skills/skill-builder/SKILL.md +113 -0
- package/payload/platform/plugins/admin/skills/skill-builder/references/lean-pattern.md +110 -0
- package/payload/platform/plugins/admin/skills/skill-builder/references/pdf-generation.md +30 -0
- package/payload/platform/plugins/admin/skills/specialist-management/SKILL.md +44 -0
- package/payload/platform/plugins/admin/skills/stream-log-review/SKILL.md +71 -0
- package/payload/platform/plugins/admin/skills/stream-log-review/references/analysis-patterns.md +189 -0
- package/payload/platform/plugins/admin/skills/unzip-attachment/SKILL.md +79 -0
- package/payload/platform/plugins/admin/skills/unzip-attachment/__tests__/preflight.sh +148 -0
- package/payload/platform/plugins/admin/skills/unzip-attachment/references/safety.md +116 -0
- package/payload/platform/plugins/admin/skills/update-knowledge/SKILL.md +47 -0
- package/payload/platform/plugins/anthropic/PLUGIN.md +40 -0
- package/payload/platform/plugins/anthropic/references/console-api.md +186 -0
- package/payload/platform/plugins/anthropic/references/setup-guide.md +36 -0
- package/payload/platform/plugins/anthropic/skills/get-api-key/SKILL.md +138 -0
- package/payload/platform/plugins/business-assistant/PLUGIN.md +59 -0
- package/payload/platform/plugins/business-assistant/references/crm.md +112 -0
- package/payload/platform/plugins/business-assistant/references/document-management.md +96 -0
- package/payload/platform/plugins/business-assistant/references/escalation.md +126 -0
- package/payload/platform/plugins/business-assistant/references/invoicing.md +163 -0
- package/payload/platform/plugins/business-assistant/references/profiling.md +50 -0
- package/payload/platform/plugins/business-assistant/references/quoting.md +56 -0
- package/payload/platform/plugins/business-assistant/references/scheduling.md +127 -0
- package/payload/platform/plugins/business-assistant/references/task-management.md +163 -0
- package/payload/platform/plugins/cloudflare/PLUGIN.md +73 -0
- package/payload/platform/plugins/cloudflare/mcp/dist/index.d.ts +2 -0
- package/payload/platform/plugins/cloudflare/mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/plugins/cloudflare/mcp/dist/index.js +29 -0
- package/payload/platform/plugins/cloudflare/mcp/dist/index.js.map +1 -0
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.d.ts +283 -0
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.d.ts.map +1 -0
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.js +1155 -0
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.js.map +1 -0
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/setup-orchestrator.d.ts +90 -0
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/setup-orchestrator.d.ts.map +1 -0
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/setup-orchestrator.js +550 -0
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/setup-orchestrator.js.map +1 -0
- package/payload/platform/plugins/cloudflare/mcp/package.json +18 -0
- package/payload/platform/plugins/cloudflare/mcp/vitest.config.ts +10 -0
- package/payload/platform/plugins/cloudflare/references/dashboard-guide.md +108 -0
- package/payload/platform/plugins/cloudflare/references/manual-setup.md +481 -0
- package/payload/platform/plugins/cloudflare/references/reset-guide.md +118 -0
- package/payload/platform/plugins/cloudflare/scripts/_stream-log.sh +154 -0
- package/payload/platform/plugins/cloudflare/scripts/list-cf-domains.sh +98 -0
- package/payload/platform/plugins/cloudflare/scripts/list-cf-domains.ts +751 -0
- package/payload/platform/plugins/cloudflare/scripts/reset-tunnel.sh +107 -0
- package/payload/platform/plugins/cloudflare/scripts/setup-tunnel.sh +826 -0
- package/payload/platform/plugins/cloudflare/skills/setup-tunnel/SKILL.md +107 -0
- package/payload/platform/plugins/contacts/PLUGIN.md +31 -0
- package/payload/platform/plugins/contacts/mcp/dist/index.d.ts +2 -0
- package/payload/platform/plugins/contacts/mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/index.js +433 -0
- package/payload/platform/plugins/contacts/mcp/dist/index.js.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/lib/neo4j.d.ts +5 -0
- package/payload/platform/plugins/contacts/mcp/dist/lib/neo4j.d.ts.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/lib/neo4j.js +40 -0
- package/payload/platform/plugins/contacts/mcp/dist/lib/neo4j.js.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/lib/resolve-person.d.ts +33 -0
- package/payload/platform/plugins/contacts/mcp/dist/lib/resolve-person.d.ts.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/lib/resolve-person.js +53 -0
- package/payload/platform/plugins/contacts/mcp/dist/lib/resolve-person.js.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.d.ts +23 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.d.ts.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.js +123 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.js.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-delete.d.ts +28 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-delete.d.ts.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-delete.js +39 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-delete.js.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-erase.d.ts +41 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-erase.d.ts.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-erase.js +142 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-erase.js.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-export.d.ts +52 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-export.d.ts.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-export.js +119 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-export.js.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-list.d.ts +23 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-list.d.ts.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-list.js +49 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-list.js.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.d.ts +21 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.d.ts.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.js +70 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.js.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-update.d.ts +14 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-update.d.ts.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-update.js +43 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-update.js.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/group-create.d.ts +18 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/group-create.d.ts.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/group-create.js +95 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/group-create.js.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/group-manage.d.ts +15 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/group-manage.d.ts.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/group-manage.js +72 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/group-manage.js.map +1 -0
- package/payload/platform/plugins/contacts/mcp/package.json +19 -0
- package/payload/platform/plugins/deep-research/PLUGIN.md +13 -0
- package/payload/platform/plugins/deep-research/skills/deep-research/SKILL.md +46 -0
- package/payload/platform/plugins/deep-research/skills/deep-research/references/citation-styles.md +52 -0
- package/payload/platform/plugins/deep-research/skills/deep-research/references/research-modes.md +22 -0
- package/payload/platform/plugins/deep-research/skills/deep-research/references/search-strategy.md +24 -0
- package/payload/platform/plugins/docs/PLUGIN.md +52 -0
- package/payload/platform/plugins/docs/references/access-control.md +73 -0
- package/payload/platform/plugins/docs/references/admin-session.md +80 -0
- package/payload/platform/plugins/docs/references/attachments.md +44 -0
- package/payload/platform/plugins/docs/references/cloudflare.md +111 -0
- package/payload/platform/plugins/docs/references/contacts-guide.md +102 -0
- package/payload/platform/plugins/docs/references/deployment.md +150 -0
- package/payload/platform/plugins/docs/references/getting-started.md +82 -0
- package/payload/platform/plugins/docs/references/graph.md +149 -0
- package/payload/platform/plugins/docs/references/internals.md +512 -0
- package/payload/platform/plugins/docs/references/memory-guide.md +119 -0
- package/payload/platform/plugins/docs/references/migration-guide.md +90 -0
- package/payload/platform/plugins/docs/references/outlook-guide.md +69 -0
- package/payload/platform/plugins/docs/references/platform.md +111 -0
- package/payload/platform/plugins/docs/references/plugins-guide.md +174 -0
- package/payload/platform/plugins/docs/references/projects-guide.md +73 -0
- package/payload/platform/plugins/docs/references/settings.md +82 -0
- package/payload/platform/plugins/docs/references/telegram-guide.md +58 -0
- package/payload/platform/plugins/docs/references/troubleshooting.md +532 -0
- package/payload/platform/plugins/email/PLUGIN.md +49 -0
- package/payload/platform/plugins/email/mcp/dist/index.d.ts +2 -0
- package/payload/platform/plugins/email/mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/index.js +291 -0
- package/payload/platform/plugins/email/mcp/dist/index.js.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/lib/credentials.d.ts +118 -0
- package/payload/platform/plugins/email/mcp/dist/lib/credentials.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/lib/credentials.js +364 -0
- package/payload/platform/plugins/email/mcp/dist/lib/credentials.js.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/lib/embedding.d.ts +2 -0
- package/payload/platform/plugins/email/mcp/dist/lib/embedding.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/lib/embedding.js +24 -0
- package/payload/platform/plugins/email/mcp/dist/lib/embedding.js.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/lib/graph.d.ts +87 -0
- package/payload/platform/plugins/email/mcp/dist/lib/graph.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/lib/graph.js +324 -0
- package/payload/platform/plugins/email/mcp/dist/lib/graph.js.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts +215 -0
- package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/lib/imap.js +735 -0
- package/payload/platform/plugins/email/mcp/dist/lib/imap.js.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/lib/neo4j.d.ts +5 -0
- package/payload/platform/plugins/email/mcp/dist/lib/neo4j.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/lib/neo4j.js +40 -0
- package/payload/platform/plugins/email/mcp/dist/lib/neo4j.js.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/lib/providers.d.ts +32 -0
- package/payload/platform/plugins/email/mcp/dist/lib/providers.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/lib/providers.js +569 -0
- package/payload/platform/plugins/email/mcp/dist/lib/providers.js.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/lib/screening.d.ts +29 -0
- package/payload/platform/plugins/email/mcp/dist/lib/screening.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/lib/screening.js +105 -0
- package/payload/platform/plugins/email/mcp/dist/lib/screening.js.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/lib/smtp.d.ts +21 -0
- package/payload/platform/plugins/email/mcp/dist/lib/smtp.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/lib/smtp.js +77 -0
- package/payload/platform/plugins/email/mcp/dist/lib/smtp.js.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/scripts/email-auto-respond.d.ts +38 -0
- package/payload/platform/plugins/email/mcp/dist/scripts/email-auto-respond.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/scripts/email-auto-respond.js +894 -0
- package/payload/platform/plugins/email/mcp/dist/scripts/email-auto-respond.js.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/scripts/email-fetch.d.ts +25 -0
- package/payload/platform/plugins/email/mcp/dist/scripts/email-fetch.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/scripts/email-fetch.js +227 -0
- package/payload/platform/plugins/email/mcp/dist/scripts/email-fetch.js.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/tools/email-auto-respond-config.d.ts +19 -0
- package/payload/platform/plugins/email/mcp/dist/tools/email-auto-respond-config.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/tools/email-auto-respond-config.js +151 -0
- package/payload/platform/plugins/email/mcp/dist/tools/email-auto-respond-config.js.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/tools/email-graph-query.d.ts +22 -0
- package/payload/platform/plugins/email/mcp/dist/tools/email-graph-query.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/tools/email-graph-query.js +188 -0
- package/payload/platform/plugins/email/mcp/dist/tools/email-graph-query.js.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/tools/email-otp-extract.d.ts +15 -0
- package/payload/platform/plugins/email/mcp/dist/tools/email-otp-extract.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/tools/email-otp-extract.js +142 -0
- package/payload/platform/plugins/email/mcp/dist/tools/email-otp-extract.js.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/tools/email-read.d.ts +14 -0
- package/payload/platform/plugins/email/mcp/dist/tools/email-read.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/tools/email-read.js +75 -0
- package/payload/platform/plugins/email/mcp/dist/tools/email-read.js.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/tools/email-reply.d.ts +10 -0
- package/payload/platform/plugins/email/mcp/dist/tools/email-reply.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/tools/email-reply.js +83 -0
- package/payload/platform/plugins/email/mcp/dist/tools/email-reply.js.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/tools/email-search.d.ts +15 -0
- package/payload/platform/plugins/email/mcp/dist/tools/email-search.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/tools/email-search.js +63 -0
- package/payload/platform/plugins/email/mcp/dist/tools/email-search.js.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/tools/email-send.d.ts +10 -0
- package/payload/platform/plugins/email/mcp/dist/tools/email-send.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/tools/email-send.js +31 -0
- package/payload/platform/plugins/email/mcp/dist/tools/email-send.js.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/tools/email-setup.d.ts +22 -0
- package/payload/platform/plugins/email/mcp/dist/tools/email-setup.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/tools/email-setup.js +183 -0
- package/payload/platform/plugins/email/mcp/dist/tools/email-setup.js.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/tools/email-status.d.ts +6 -0
- package/payload/platform/plugins/email/mcp/dist/tools/email-status.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/tools/email-status.js +43 -0
- package/payload/platform/plugins/email/mcp/dist/tools/email-status.js.map +1 -0
- package/payload/platform/plugins/email/mcp/package.json +23 -0
- package/payload/platform/plugins/email/references/email-reference.md +204 -0
- package/payload/platform/plugins/linkedin-import/PLUGIN.md +27 -0
- package/payload/platform/plugins/linkedin-import/skills/linkedin-import/SKILL.md +142 -0
- package/payload/platform/plugins/linkedin-import/skills/linkedin-import/references/connections.md +135 -0
- package/payload/platform/plugins/linkedin-import/skills/linkedin-import/references/profile.md +95 -0
- package/payload/platform/plugins/memory/PLUGIN.md +146 -0
- package/payload/platform/plugins/memory/bin/conversation-archive-ingest.mjs +879 -0
- package/payload/platform/plugins/memory/bin/conversation-archive-ingest.sh +138 -0
- package/payload/platform/plugins/memory/mcp/dist/index.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/index.js +1813 -0
- package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/live-schema-source.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/live-schema-source.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/live-schema-source.test.js +92 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/live-schema-source.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/llm-classifier.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/llm-classifier.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/llm-classifier.test.js +225 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/llm-classifier.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-loader.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-loader.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-loader.test.js +100 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-loader.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-validator.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-validator.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-validator.test.js +448 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-validator.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/attachments.d.ts +37 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/attachments.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/attachments.js +69 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/attachments.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/index.d.ts +5 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/index.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/index.js +30 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/index.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/timestamp-scanner.d.ts +49 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/timestamp-scanner.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/timestamp-scanner.js +35 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/timestamp-scanner.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/types.d.ts +47 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/types.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/types.js +31 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/types.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/whatsapp-text.d.ts +3 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/whatsapp-text.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/whatsapp-text.js +155 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/whatsapp-text.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/delta-cursor.d.ts +11 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/delta-cursor.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/delta-cursor.js +20 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/delta-cursor.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/derive-keys.d.ts +14 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/derive-keys.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/derive-keys.js +38 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/derive-keys.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/sender-bind.d.ts +16 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/sender-bind.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/sender-bind.js +59 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/sender-bind.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/sessionize.d.ts +9 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/sessionize.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/sessionize.js +32 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/sessionize.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/to-turn-text.d.ts +3 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/to-turn-text.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/to-turn-text.js +29 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/to-turn-text.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/document-chunker.d.ts +45 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/document-chunker.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/document-chunker.js +125 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/document-chunker.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/document-hierarchy.d.ts +9 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/document-hierarchy.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/document-hierarchy.js +61 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/document-hierarchy.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/embeddings.d.ts +3 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/embeddings.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/embeddings.js +29 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/embeddings.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/filter-token.d.ts +36 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/filter-token.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/filter-token.js +86 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/filter-token.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/graph-prune.d.ts +42 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/graph-prune.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/graph-prune.js +114 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/graph-prune.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/graph-write-gate.d.ts +38 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/graph-write-gate.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/graph-write-gate.js +89 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/graph-write-gate.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/live-schema-source.d.ts +136 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/live-schema-source.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/live-schema-source.js +180 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/live-schema-source.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.d.ts +246 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.js +828 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-ranker.d.ts +63 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-ranker.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-ranker.js +210 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-ranker.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/neo4j.d.ts +5 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/neo4j.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/neo4j.js +40 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/neo4j.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.d.ts +113 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.js +455 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.d.ts +83 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.js +209 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/uuid.d.ts +3 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/uuid.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/uuid.js +12 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/uuid.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-derive-insights.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-derive-insights.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-derive-insights.test.js +97 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-derive-insights.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-enrich-rejection.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-enrich-rejection.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-enrich-rejection.test.js +184 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-enrich-rejection.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-normalisers-source-agnosticism.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-normalisers-source-agnosticism.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-normalisers-source-agnosticism.test.js +73 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-normalisers-source-agnosticism.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-normalisers-whatsapp-text.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-normalisers-whatsapp-text.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-normalisers-whatsapp-text.test.js +109 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-normalisers-whatsapp-text.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-archive-write.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-archive-write.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-archive-write.test.js +84 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-archive-write.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest.test.js +106 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-not-applicable.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-not-applicable.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-not-applicable.test.js +87 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-not-applicable.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-personfields-open.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-personfields-open.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-personfields-open.test.js +148 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-personfields-open.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.d.ts +89 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.js +542 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-enrich-rejection.d.ts +41 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-enrich-rejection.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-enrich-rejection.js +116 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-enrich-rejection.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-memory-expunge.d.ts +8 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-memory-expunge.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-memory-expunge.js +7 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-memory-expunge.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-add.d.ts +7 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-add.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-add.js +28 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-add.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-list.d.ts +7 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-list.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-list.js +7 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-list.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-remove.d.ts +7 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-remove.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-remove.js +27 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-remove.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.d.ts +54 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.js +231 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-classify.d.ts +34 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-classify.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-classify.js +58 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-classify.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.d.ts +57 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.js +106 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-edit-attachment.d.ts +16 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-edit-attachment.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-edit-attachment.js +91 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-edit-attachment.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-empty-trash.d.ts +22 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-empty-trash.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-empty-trash.js +36 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-empty-trash.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-find-candidates.d.ts +58 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-find-candidates.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-find-candidates.js +125 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-find-candidates.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-extract.d.ts +28 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-extract.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-extract.js +93 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-extract.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-web.d.ts +20 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-web.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-web.js +87 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-web.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.d.ts +129 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js +827 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-list-attachments.d.ts +19 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-list-attachments.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-list-attachments.js +125 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-list-attachments.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-rank.d.ts +61 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-rank.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-rank.js +102 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-rank.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-read-attachment.d.ts +12 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-read-attachment.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-read-attachment.js +100 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-read-attachment.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-reindex.d.ts +9 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-reindex.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-reindex.js +84 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-reindex.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-rename-attachment.d.ts +13 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-rename-attachment.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-rename-attachment.js +63 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-rename-attachment.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-restore.d.ts +24 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-restore.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-restore.js +40 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-restore.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.d.ts +5 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.js +42 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.d.ts +13 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.js +77 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts +47 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js +173 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/profile-delete.d.ts +24 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/profile-delete.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/profile-delete.js +31 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/profile-delete.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/profile-read.d.ts +44 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/profile-read.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/profile-read.js +322 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/profile-read.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.d.ts +65 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.js +369 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/package.json +24 -0
- package/payload/platform/plugins/memory/mcp/scripts/boot-smoke.sh +69 -0
- package/payload/platform/plugins/memory/mcp/scripts/graph/accept.sh +217 -0
- package/payload/platform/plugins/memory/mcp/scripts/graph/fixture.cypher +59 -0
- package/payload/platform/plugins/memory/mcp/vitest.config.ts +15 -0
- package/payload/platform/plugins/memory/references/graph-primitives.md +342 -0
- package/payload/platform/plugins/memory/references/schema-base.md +234 -0
- package/payload/platform/plugins/memory/references/schema-creator.md +35 -0
- package/payload/platform/plugins/memory/references/schema-estate-agent.md +35 -0
- package/payload/platform/plugins/memory/references/schema-food-beverage.md +32 -0
- package/payload/platform/plugins/memory/references/schema-hospitality.md +31 -0
- package/payload/platform/plugins/memory/references/schema-logistics.md +30 -0
- package/payload/platform/plugins/memory/references/schema-professional-services.md +33 -0
- package/payload/platform/plugins/memory/references/schema-retail.md +33 -0
- package/payload/platform/plugins/memory/references/schema-trades.md +36 -0
- package/payload/platform/plugins/memory/skills/conversation-archive/SKILL.md +202 -0
- package/payload/platform/plugins/memory/skills/conversation-archive-enrich/SKILL.md +159 -0
- package/payload/platform/plugins/memory/skills/conversational-memory/SKILL.md +108 -0
- package/payload/platform/plugins/memory/skills/document-ingest/SKILL.md +267 -0
- package/payload/platform/plugins/outlook/PLUGIN.md +48 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client.test.js +94 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/log.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/log.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/log.test.js +31 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/log.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/pkce-flow.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/pkce-flow.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/pkce-flow.test.js +213 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/pkce-flow.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/token-store.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/token-store.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/token-store.test.js +130 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/token-store.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/pkce-flow.d.ts +65 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/pkce-flow.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/pkce-flow.js +261 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/pkce-flow.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.d.ts +61 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.js +170 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/index.d.ts +18 -0
- package/payload/platform/plugins/outlook/mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/index.js +152 -0
- package/payload/platform/plugins/outlook/mcp/dist/index.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.d.ts +60 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.js +189 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/log.d.ts +23 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/log.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/log.js +53 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/log.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.d.ts +26 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.js +50 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-event.d.ts +12 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-event.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-event.js +32 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-event.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-list.d.ts +59 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-list.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-list.js +54 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-list.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/contacts-list.d.ts +14 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/contacts-list.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/contacts-list.js +45 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/contacts-list.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.d.ts +15 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.js +48 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-search.d.ts +8 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-search.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-search.js +49 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-search.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-info.d.ts +19 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-info.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-info.js +58 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-info.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/package.json +20 -0
- package/payload/platform/plugins/outlook/mcp/scripts/verify-doc-impl.sh +109 -0
- package/payload/platform/plugins/outlook/references/auth.md +118 -0
- package/payload/platform/plugins/outlook/references/graph-surfaces.md +114 -0
- package/payload/platform/plugins/outlook/skills/outlook/SKILL.md +65 -0
- package/payload/platform/plugins/projects/PLUGIN.md +90 -0
- package/payload/platform/plugins/projects/references/investigation.md +63 -0
- package/payload/platform/plugins/projects/references/retrospective.md +71 -0
- package/payload/platform/plugins/projects/references/review.md +51 -0
- package/payload/platform/plugins/projects/references/sprint.md +168 -0
- package/payload/platform/plugins/replicate/PLUGIN.md +23 -0
- package/payload/platform/plugins/replicate/mcp/dist/index.d.ts +2 -0
- package/payload/platform/plugins/replicate/mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/plugins/replicate/mcp/dist/index.js +106 -0
- package/payload/platform/plugins/replicate/mcp/dist/index.js.map +1 -0
- package/payload/platform/plugins/replicate/mcp/dist/lib/replicate-key.d.ts +15 -0
- package/payload/platform/plugins/replicate/mcp/dist/lib/replicate-key.d.ts.map +1 -0
- package/payload/platform/plugins/replicate/mcp/dist/lib/replicate-key.js +73 -0
- package/payload/platform/plugins/replicate/mcp/dist/lib/replicate-key.js.map +1 -0
- package/payload/platform/plugins/replicate/mcp/dist/tools/image-generate.d.ts +14 -0
- package/payload/platform/plugins/replicate/mcp/dist/tools/image-generate.d.ts.map +1 -0
- package/payload/platform/plugins/replicate/mcp/dist/tools/image-generate.js +191 -0
- package/payload/platform/plugins/replicate/mcp/dist/tools/image-generate.js.map +1 -0
- package/payload/platform/plugins/replicate/mcp/package.json +21 -0
- package/payload/platform/plugins/sales/PLUGIN.md +106 -0
- package/payload/platform/plugins/sales/references/close-tracking.md +69 -0
- package/payload/platform/plugins/sales/references/comparisons.md +99 -0
- package/payload/platform/plugins/sales/references/competitive-positioning.md +51 -0
- package/payload/platform/plugins/sales/references/faq.md +77 -0
- package/payload/platform/plugins/sales/references/objection-handling.md +157 -0
- package/payload/platform/plugins/sales/references/pricing.md +101 -0
- package/payload/platform/plugins/scheduling/PLUGIN.md +103 -0
- package/payload/platform/plugins/scheduling/mcp/dist/__tests__/time-resolve-description.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/__tests__/time-resolve-description.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/__tests__/time-resolve-description.test.js +16 -0
- package/payload/platform/plugins/scheduling/mcp/dist/__tests__/time-resolve-description.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/index.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/index.js +315 -0
- package/payload/platform/plugins/scheduling/mcp/dist/index.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/getUserTimezone.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/getUserTimezone.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/getUserTimezone.test.js +119 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/getUserTimezone.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/embedding.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/embedding.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/embedding.js +19 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/embedding.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/ics.d.ts +47 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/ics.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/ics.js +362 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/ics.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/neo4j.d.ts +30 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/neo4j.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/neo4j.js +89 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/neo4j.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/time-format.d.ts +48 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/time-format.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/time-format.js +140 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/time-format.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.d.ts +20 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +568 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-cancel.d.ts +7 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-cancel.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-cancel.js +23 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-cancel.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.d.ts +25 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js +121 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-export-ics.d.ts +9 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-export-ics.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-export-ics.js +76 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-export-ics.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-get.d.ts +25 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-get.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-get.js +53 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-get.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-import-ics.d.ts +8 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-import-ics.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-import-ics.js +48 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-import-ics.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-list.d.ts +20 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-list.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-list.js +76 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-list.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.d.ts +18 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.js +167 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/package.json +23 -0
- package/payload/platform/plugins/scheduling/mcp/vitest.config.ts +9 -0
- package/payload/platform/plugins/tasks/.claude-plugin/plugin.json +17 -0
- package/payload/platform/plugins/tasks/.mcp.json +13 -0
- package/payload/platform/plugins/tasks/PLUGIN.md +81 -0
- package/payload/platform/plugins/tasks/mcp/dist/index.d.ts +2 -0
- package/payload/platform/plugins/tasks/mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/plugins/tasks/mcp/dist/index.js +503 -0
- package/payload/platform/plugins/tasks/mcp/dist/index.js.map +1 -0
- package/payload/platform/plugins/tasks/mcp/dist/lib/embeddings.d.ts +7 -0
- package/payload/platform/plugins/tasks/mcp/dist/lib/embeddings.d.ts.map +1 -0
- package/payload/platform/plugins/tasks/mcp/dist/lib/embeddings.js +24 -0
- package/payload/platform/plugins/tasks/mcp/dist/lib/embeddings.js.map +1 -0
- package/payload/platform/plugins/tasks/mcp/dist/lib/neo4j.d.ts +5 -0
- package/payload/platform/plugins/tasks/mcp/dist/lib/neo4j.d.ts.map +1 -0
- package/payload/platform/plugins/tasks/mcp/dist/lib/neo4j.js +40 -0
- package/payload/platform/plugins/tasks/mcp/dist/lib/neo4j.js.map +1 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/project-complete.d.ts +17 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/project-complete.d.ts.map +1 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/project-complete.js +76 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/project-complete.js.map +1 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/project-create.d.ts +29 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/project-create.d.ts.map +1 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/project-create.js +235 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/project-create.js.map +1 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/project-get.d.ts +40 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/project-get.d.ts.map +1 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/project-get.js +125 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/project-get.js.map +1 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/project-list.d.ts +26 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/project-list.d.ts.map +1 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/project-list.js +81 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/project-list.js.map +1 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/project-update.d.ts +19 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/project-update.d.ts.map +1 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/project-update.js +102 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/project-update.js.map +1 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/session-list.d.ts +20 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/session-list.d.ts.map +1 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/session-list.js +37 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/session-list.js.map +1 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/session-name.d.ts +12 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/session-name.d.ts.map +1 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/session-name.js +28 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/session-name.js.map +1 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/task-complete.d.ts +16 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/task-complete.d.ts.map +1 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/task-complete.js +33 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/task-complete.js.map +1 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/task-create.d.ts +63 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/task-create.d.ts.map +1 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/task-create.js +141 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/task-create.js.map +1 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/task-get.d.ts +19 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/task-get.d.ts.map +1 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/task-get.js +51 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/task-get.js.map +1 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/task-list.d.ts +18 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/task-list.d.ts.map +1 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/task-list.js +66 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/task-list.js.map +1 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/task-ready.d.ts +21 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/task-ready.d.ts.map +1 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/task-ready.js +54 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/task-ready.js.map +1 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/task-relate.d.ts +12 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/task-relate.d.ts.map +1 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/task-relate.js +59 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/task-relate.js.map +1 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/task-update.d.ts +32 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/task-update.d.ts.map +1 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/task-update.js +112 -0
- package/payload/platform/plugins/tasks/mcp/dist/tools/task-update.js.map +1 -0
- package/payload/platform/plugins/tasks/mcp/package.json +20 -0
- package/payload/platform/plugins/telegram/PLUGIN.md +35 -0
- package/payload/platform/plugins/telegram/mcp/dist/index.d.ts +2 -0
- package/payload/platform/plugins/telegram/mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/index.js +198 -0
- package/payload/platform/plugins/telegram/mcp/dist/index.js.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/lib/telegram.d.ts +41 -0
- package/payload/platform/plugins/telegram/mcp/dist/lib/telegram.d.ts.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/lib/telegram.js +70 -0
- package/payload/platform/plugins/telegram/mcp/dist/lib/telegram.js.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/tools/message-history.d.ts +16 -0
- package/payload/platform/plugins/telegram/mcp/dist/tools/message-history.d.ts.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/tools/message-history.js +68 -0
- package/payload/platform/plugins/telegram/mcp/dist/tools/message-history.js.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/tools/message.d.ts +20 -0
- package/payload/platform/plugins/telegram/mcp/dist/tools/message.d.ts.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/tools/message.js +34 -0
- package/payload/platform/plugins/telegram/mcp/dist/tools/message.js.map +1 -0
- package/payload/platform/plugins/telegram/mcp/package.json +19 -0
- package/payload/platform/plugins/telegram/references/setup-guide.md +50 -0
- package/payload/platform/plugins/waitlist/PLUGIN.md +63 -0
- package/payload/platform/plugins/waitlist/mcp/dist/index.d.ts +2 -0
- package/payload/platform/plugins/waitlist/mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/plugins/waitlist/mcp/dist/index.js +328 -0
- package/payload/platform/plugins/waitlist/mcp/dist/index.js.map +1 -0
- package/payload/platform/plugins/waitlist/mcp/dist/lib/embedding.d.ts +3 -0
- package/payload/platform/plugins/waitlist/mcp/dist/lib/embedding.d.ts.map +1 -0
- package/payload/platform/plugins/waitlist/mcp/dist/lib/embedding.js +48 -0
- package/payload/platform/plugins/waitlist/mcp/dist/lib/embedding.js.map +1 -0
- package/payload/platform/plugins/waitlist/mcp/dist/lib/neo4j.d.ts +5 -0
- package/payload/platform/plugins/waitlist/mcp/dist/lib/neo4j.d.ts.map +1 -0
- package/payload/platform/plugins/waitlist/mcp/dist/lib/neo4j.js +40 -0
- package/payload/platform/plugins/waitlist/mcp/dist/lib/neo4j.js.map +1 -0
- package/payload/platform/plugins/waitlist/mcp/dist/tools/__tests__/waitlist-persist.test.d.ts +2 -0
- package/payload/platform/plugins/waitlist/mcp/dist/tools/__tests__/waitlist-persist.test.d.ts.map +1 -0
- package/payload/platform/plugins/waitlist/mcp/dist/tools/__tests__/waitlist-persist.test.js +70 -0
- package/payload/platform/plugins/waitlist/mcp/dist/tools/__tests__/waitlist-persist.test.js.map +1 -0
- package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-heal.d.ts +33 -0
- package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-heal.d.ts.map +1 -0
- package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-heal.js +124 -0
- package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-heal.js.map +1 -0
- package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-list.d.ts +23 -0
- package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-list.d.ts.map +1 -0
- package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-list.js +58 -0
- package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-list.js.map +1 -0
- package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-persist.d.ts +83 -0
- package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-persist.d.ts.map +1 -0
- package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-persist.js +433 -0
- package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-persist.js.map +1 -0
- package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-review.d.ts +19 -0
- package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-review.d.ts.map +1 -0
- package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-review.js +81 -0
- package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-review.js.map +1 -0
- package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-scan.d.ts +28 -0
- package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-scan.d.ts.map +1 -0
- package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-scan.js +50 -0
- package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-scan.js.map +1 -0
- package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-setup.d.ts +33 -0
- package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-setup.d.ts.map +1 -0
- package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-setup.js +335 -0
- package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-setup.js.map +1 -0
- package/payload/platform/plugins/waitlist/mcp/package.json +23 -0
- package/payload/platform/plugins/waitlist/mcp/vitest.config.ts +9 -0
- package/payload/platform/plugins/whatsapp/PLUGIN.md +71 -0
- package/payload/platform/plugins/whatsapp/mcp/dist/call-api.d.ts +14 -0
- package/payload/platform/plugins/whatsapp/mcp/dist/call-api.d.ts.map +1 -0
- package/payload/platform/plugins/whatsapp/mcp/dist/call-api.js +42 -0
- package/payload/platform/plugins/whatsapp/mcp/dist/call-api.js.map +1 -0
- package/payload/platform/plugins/whatsapp/mcp/dist/index.d.ts +8 -0
- package/payload/platform/plugins/whatsapp/mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/plugins/whatsapp/mcp/dist/index.js +469 -0
- package/payload/platform/plugins/whatsapp/mcp/dist/index.js.map +1 -0
- package/payload/platform/plugins/whatsapp/mcp/package.json +19 -0
- package/payload/platform/plugins/whatsapp/references/channels-whatsapp.md +257 -0
- package/payload/platform/plugins/whatsapp/skills/connect-whatsapp/SKILL.md +82 -0
- package/payload/platform/plugins/whatsapp/skills/manage-whatsapp-config/SKILL.md +99 -0
- package/payload/platform/plugins/workflows/.claude-plugin/plugin.json +16 -0
- package/payload/platform/plugins/workflows/.mcp.json +12 -0
- package/payload/platform/plugins/workflows/PLUGIN.md +297 -0
- package/payload/platform/plugins/workflows/mcp/dist/index.d.ts +2 -0
- package/payload/platform/plugins/workflows/mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/plugins/workflows/mcp/dist/index.js +536 -0
- package/payload/platform/plugins/workflows/mcp/dist/index.js.map +1 -0
- package/payload/platform/plugins/workflows/mcp/dist/lib/active-runs.d.ts +38 -0
- package/payload/platform/plugins/workflows/mcp/dist/lib/active-runs.d.ts.map +1 -0
- package/payload/platform/plugins/workflows/mcp/dist/lib/active-runs.js +83 -0
- package/payload/platform/plugins/workflows/mcp/dist/lib/active-runs.js.map +1 -0
- package/payload/platform/plugins/workflows/mcp/dist/lib/embeddings.d.ts +2 -0
- package/payload/platform/plugins/workflows/mcp/dist/lib/embeddings.d.ts.map +1 -0
- package/payload/platform/plugins/workflows/mcp/dist/lib/embeddings.js +19 -0
- package/payload/platform/plugins/workflows/mcp/dist/lib/embeddings.js.map +1 -0
- package/payload/platform/plugins/workflows/mcp/dist/lib/llm-call.d.ts +151 -0
- package/payload/platform/plugins/workflows/mcp/dist/lib/llm-call.d.ts.map +1 -0
- package/payload/platform/plugins/workflows/mcp/dist/lib/llm-call.js +299 -0
- package/payload/platform/plugins/workflows/mcp/dist/lib/llm-call.js.map +1 -0
- package/payload/platform/plugins/workflows/mcp/dist/lib/neo4j.d.ts +5 -0
- package/payload/platform/plugins/workflows/mcp/dist/lib/neo4j.d.ts.map +1 -0
- package/payload/platform/plugins/workflows/mcp/dist/lib/neo4j.js +40 -0
- package/payload/platform/plugins/workflows/mcp/dist/lib/neo4j.js.map +1 -0
- package/payload/platform/plugins/workflows/mcp/dist/lib/step-resolver.d.ts +153 -0
- package/payload/platform/plugins/workflows/mcp/dist/lib/step-resolver.d.ts.map +1 -0
- package/payload/platform/plugins/workflows/mcp/dist/lib/step-resolver.js +273 -0
- package/payload/platform/plugins/workflows/mcp/dist/lib/step-resolver.js.map +1 -0
- package/payload/platform/plugins/workflows/mcp/dist/lib/validate-capabilities.d.ts +32 -0
- package/payload/platform/plugins/workflows/mcp/dist/lib/validate-capabilities.d.ts.map +1 -0
- package/payload/platform/plugins/workflows/mcp/dist/lib/validate-capabilities.js +97 -0
- package/payload/platform/plugins/workflows/mcp/dist/lib/validate-capabilities.js.map +1 -0
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-create.d.ts +48 -0
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-create.d.ts.map +1 -0
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-create.js +166 -0
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-create.js.map +1 -0
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-delete.d.ts +28 -0
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-delete.d.ts.map +1 -0
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-delete.js +57 -0
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-delete.js.map +1 -0
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-execute.d.ts +64 -0
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-execute.d.ts.map +1 -0
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-execute.js +605 -0
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-execute.js.map +1 -0
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-get.d.ts +29 -0
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-get.d.ts.map +1 -0
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-get.js +55 -0
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-get.js.map +1 -0
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-list.d.ts +16 -0
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-list.d.ts.map +1 -0
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-list.js +44 -0
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-list.js.map +1 -0
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-runs.d.ts +28 -0
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-runs.d.ts.map +1 -0
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-runs.js +71 -0
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-runs.js.map +1 -0
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-update.d.ts +11 -0
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-update.d.ts.map +1 -0
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-update.js +198 -0
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-update.js.map +1 -0
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-validate.d.ts +13 -0
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-validate.d.ts.map +1 -0
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-validate.js +71 -0
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-validate.js.map +1 -0
- package/payload/platform/plugins/workflows/mcp/package.json +20 -0
- package/payload/platform/plugins/workflows/mcp/test-runner.mjs +710 -0
- package/payload/platform/plugins/workflows/mcp/test-workflows.sh +77 -0
- package/payload/platform/plugins/workflows/skills/workflow-manager/SKILL.md +88 -0
- package/payload/platform/scripts/__tests__/admin-persist-audit.test.ts +182 -0
- package/payload/platform/scripts/__tests__/first-token-creates-stream-log.test.sh +37 -0
- package/payload/platform/scripts/__tests__/logs-read-prefix.sh +237 -0
- package/payload/platform/scripts/admin-conversation-recover.mjs +386 -0
- package/payload/platform/scripts/admin-persist-audit.ts +217 -0
- package/payload/platform/scripts/check-no-conversation-id-leaks.mjs +165 -0
- package/payload/platform/scripts/check-no-task-id-leaks.mjs +110 -0
- package/payload/platform/scripts/check-sdk-oauth.mjs +185 -0
- package/payload/platform/scripts/check-skill-load-coverage.mjs +100 -0
- package/payload/platform/scripts/component-knowledgedoc-backfill.ts +214 -0
- package/payload/platform/scripts/conversation-id-allowlist.txt +151 -0
- package/payload/platform/scripts/dedupe-userprofile-ghosts.sh +388 -0
- package/payload/platform/scripts/generate-entitlement-fixture.mjs +152 -0
- package/payload/platform/scripts/installer-device-verify.sh +249 -0
- package/payload/platform/scripts/lib/resolve-account-dir.sh +186 -0
- package/payload/platform/scripts/log-adherence-check.sh +125 -0
- package/payload/platform/scripts/logs-read.sh +577 -0
- package/payload/platform/scripts/logs-read.test.sh +159 -0
- package/payload/platform/scripts/migrate-import.sh +437 -0
- package/payload/platform/scripts/redact-install-logs.sh +87 -0
- package/payload/platform/scripts/resume-tunnel.sh +117 -0
- package/payload/platform/scripts/seed-neo4j.sh +590 -0
- package/payload/platform/scripts/taskmaster-export.sh +388 -0
- package/payload/platform/scripts/test-laptop-vnc-boot.sh +88 -0
- package/payload/platform/scripts/verify-skill-tool-surface.sh +255 -0
- package/payload/platform/scripts/vnc.sh +475 -0
- package/payload/platform/scripts/wifi-provision-server/server.js +743 -0
- package/payload/platform/scripts/wifi-provision.sh +492 -0
- package/payload/platform/services/claude-session-manager/dist/config.d.ts +12 -0
- package/payload/platform/services/claude-session-manager/dist/config.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/config.js +27 -0
- package/payload/platform/services/claude-session-manager/dist/config.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts +10 -0
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/http-server.js +186 -0
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/index.d.ts +2 -0
- package/payload/platform/services/claude-session-manager/dist/index.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/index.js +64 -0
- package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/jsonl-path.d.ts +4 -0
- package/payload/platform/services/claude-session-manager/dist/jsonl-path.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/jsonl-path.js +31 -0
- package/payload/platform/services/claude-session-manager/dist/jsonl-path.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +34 -0
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +91 -0
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/session-store.d.ts +23 -0
- package/payload/platform/services/claude-session-manager/dist/session-store.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/session-store.js +31 -0
- package/payload/platform/services/claude-session-manager/dist/session-store.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/types.d.ts +33 -0
- package/payload/platform/services/claude-session-manager/dist/types.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/types.js +2 -0
- package/payload/platform/services/claude-session-manager/dist/types.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/url-capture.d.ts +12 -0
- package/payload/platform/services/claude-session-manager/dist/url-capture.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/url-capture.js +68 -0
- package/payload/platform/services/claude-session-manager/dist/url-capture.js.map +1 -0
- package/payload/platform/services/claude-session-manager/package.json +22 -0
- package/payload/platform/templates/account.json +12 -0
- package/payload/platform/templates/agents/admin/AGENTS.md +12 -0
- package/payload/platform/templates/agents/admin/IDENTITY.md +320 -0
- package/payload/platform/templates/agents/admin/LEARNINGS.md +3 -0
- package/payload/platform/templates/agents/admin/SOUL.md +23 -0
- package/payload/platform/templates/agents/public/IDENTITY.md +56 -0
- package/payload/platform/templates/agents/public/SOUL.md +19 -0
- package/payload/platform/templates/agents/public/config.json +9 -0
- package/payload/platform/templates/specialists/.claude-plugin/plugin.json +4 -0
- package/payload/platform/templates/specialists/agents/content-producer.md +104 -0
- package/payload/platform/templates/specialists/agents/database-operator.md +199 -0
- package/payload/platform/templates/specialists/agents/personal-assistant.md +209 -0
- package/payload/platform/templates/specialists/agents/project-manager.md +132 -0
- package/payload/platform/templates/specialists/agents/research-assistant.md +115 -0
- package/payload/platform/templates/systemd/edge.service.template +38 -0
- package/payload/platform/tsconfig.base.json +18 -0
- package/payload/premium-plugins/real-agency/BUNDLE.md +44 -0
- package/payload/premium-plugins/real-agency/agents/buyer-enquiry/IDENTITY.md +13 -0
- package/payload/premium-plugins/real-agency/agents/buyer-enquiry/SOUL.md +9 -0
- package/payload/premium-plugins/real-agency/agents/buyer-enquiry/template.json +9 -0
- package/payload/premium-plugins/real-agency/agents/compliance.md +303 -0
- package/payload/premium-plugins/real-agency/agents/negotiator.md +145 -0
- package/payload/premium-plugins/real-agency/agents/valuer.md +140 -0
- package/payload/premium-plugins/real-agency/plugins/brochures/PLUGIN.md +36 -0
- package/payload/premium-plugins/real-agency/plugins/brochures/commands/make-brochure.md +11 -0
- package/payload/premium-plugins/real-agency/plugins/brochures/skills/a4-print-documents/SKILL.md +478 -0
- package/payload/premium-plugins/real-agency/plugins/brochures/skills/brand-design/SKILL.md +192 -0
- package/payload/premium-plugins/real-agency/plugins/brochures/skills/make-brochure/SKILL.md +354 -0
- package/payload/premium-plugins/real-agency/plugins/brochures/skills/make-brochure/references/seller-brief-template.md +115 -0
- package/payload/premium-plugins/real-agency/plugins/brochures/skills/property-brochure/SKILL.md +119 -0
- package/payload/premium-plugins/real-agency/plugins/brochures/skills/property-brochure/references/build.md +270 -0
- package/payload/premium-plugins/real-agency/plugins/brochures/skills/property-brochure/references/copy.md +211 -0
- package/payload/premium-plugins/real-agency/plugins/brochures/skills/property-brochure/references/images.md +166 -0
- package/payload/premium-plugins/real-agency/plugins/brochures/skills/property-brochure/references/page-landing.md +376 -0
- package/payload/premium-plugins/real-agency/plugins/brochures/skills/property-brochure/references/page.html +1288 -0
- package/payload/premium-plugins/real-agency/plugins/brochures/skills/property-brochure/references/placeholders.md +250 -0
- package/payload/premium-plugins/real-agency/plugins/brochures/skills/property-brochure/references/registers.md +47 -0
- package/payload/premium-plugins/real-agency/plugins/brochures/skills/property-brochure/references/seller-brief.md +56 -0
- package/payload/premium-plugins/real-agency/plugins/brochures/skills/property-brochure/references/structure.md +249 -0
- package/payload/premium-plugins/real-agency/plugins/brochures/skills/property-brochure/references/template.html +2370 -0
- package/payload/premium-plugins/real-agency/plugins/brochures/skills/property-extract/SKILL.md +372 -0
- package/payload/premium-plugins/real-agency/plugins/buyers/PLUGIN.md +35 -0
- package/payload/premium-plugins/real-agency/plugins/buyers/skills/buyer-feedback/SKILL.md +109 -0
- package/payload/premium-plugins/real-agency/plugins/buyers/skills/buyer-management/SKILL.md +42 -0
- package/payload/premium-plugins/real-agency/plugins/buyers/skills/buyer-management/references/buyer-qualification-questions.md +16 -0
- package/payload/premium-plugins/real-agency/plugins/buyers/skills/buyer-management/references/buyer-qualification.md +59 -0
- package/payload/premium-plugins/real-agency/plugins/buyers/skills/buyer-management/references/buyer-scripts.md +63 -0
- package/payload/premium-plugins/real-agency/plugins/buyers/skills/buyer-management/references/buyer-working-scripts.md +54 -0
- package/payload/premium-plugins/real-agency/plugins/buyers/skills/buyer-management/references/feedback-collection.md +42 -0
- package/payload/premium-plugins/real-agency/plugins/buyers/skills/buyer-management/references/offer-capture.md +38 -0
- package/payload/premium-plugins/real-agency/plugins/buyers/skills/buyer-management/references/viewing-booking.md +32 -0
- package/payload/premium-plugins/real-agency/plugins/buyers/skills/buyer-management/references/viewing-management.md +52 -0
- package/payload/premium-plugins/real-agency/plugins/buyers/skills/buyer-seller-guides/SKILL.md +407 -0
- package/payload/premium-plugins/real-agency/plugins/buyers/skills/buyer-seller-guides/references/care-fees-guide.md +68 -0
- package/payload/premium-plugins/real-agency/plugins/buyers/skills/buyer-seller-guides/references/divorce-sales-guide.md +61 -0
- package/payload/premium-plugins/real-agency/plugins/buyers/skills/buyer-seller-guides/references/downsizing-guide.md +45 -0
- package/payload/premium-plugins/real-agency/plugins/buyers/skills/buyer-seller-guides/references/first-time-buyers.md +92 -0
- package/payload/premium-plugins/real-agency/plugins/buyers/skills/buyer-seller-guides/references/first-time-sellers.md +78 -0
- package/payload/premium-plugins/real-agency/plugins/buyers/skills/buyer-seller-guides/references/probate-guide.md +53 -0
- package/payload/premium-plugins/real-agency/plugins/buyers/skills/buyer-seller-guides/references/upsizing-guide.md +41 -0
- package/payload/premium-plugins/real-agency/plugins/buyers/skills/property-enquiry/SKILL.md +126 -0
- package/payload/premium-plugins/real-agency/plugins/buyers/skills/viewing-management/SKILL.md +111 -0
- package/payload/premium-plugins/real-agency/plugins/estate-business/PLUGIN.md +34 -0
- package/payload/premium-plugins/real-agency/plugins/estate-business/skills/business-growth/SKILL.md +133 -0
- package/payload/premium-plugins/real-agency/plugins/estate-business/skills/business-growth/references/buy-back-your-time.md +37 -0
- package/payload/premium-plugins/real-agency/plugins/estate-business/skills/business-growth/references/firewave-gost-scorecards.md +14 -0
- package/payload/premium-plugins/real-agency/plugins/estate-business/skills/business-growth/references/keller-org-model.md +17 -0
- package/payload/premium-plugins/real-agency/plugins/estate-business/skills/business-growth/references/lencioni-team-models.md +22 -0
- package/payload/premium-plugins/real-agency/plugins/estate-business/skills/business-growth/references/listing-management-system.md +11 -0
- package/payload/premium-plugins/real-agency/plugins/estate-business/skills/business-growth/references/net-figure-form.md +11 -0
- package/payload/premium-plugins/real-agency/plugins/estate-business/skills/business-growth/references/serhant-bizinbox-notes.md +13 -0
- package/payload/premium-plugins/real-agency/plugins/estate-business/skills/business-growth/references/team-roles-commission.md +14 -0
- package/payload/premium-plugins/real-agency/plugins/estate-business/skills/business-growth/references/va-2026-ops.md +43 -0
- package/payload/premium-plugins/real-agency/plugins/estate-business/skills/business-growth/references/wingman-structure.md +13 -0
- package/payload/premium-plugins/real-agency/plugins/estate-business/skills/business-operations/SKILL.md +32 -0
- package/payload/premium-plugins/real-agency/plugins/estate-business/skills/business-operations/references/crm-systems.md +57 -0
- package/payload/premium-plugins/real-agency/plugins/estate-business/skills/business-operations/references/hiring-guide.md +59 -0
- package/payload/premium-plugins/real-agency/plugins/estate-business/skills/business-operations/references/impact-framework.md +47 -0
- package/payload/premium-plugins/real-agency/plugins/estate-business/skills/business-operations/references/minutes-equal-money.md +55 -0
- package/payload/premium-plugins/real-agency/plugins/estate-business/skills/business-operations/references/team-management.md +48 -0
- package/payload/premium-plugins/real-agency/plugins/estate-business/skills/exp-partnership/SKILL.md +52 -0
- package/payload/premium-plugins/real-agency/plugins/estate-business/skills/exp-partnership/references/12-reasons.md +39 -0
- package/payload/premium-plugins/real-agency/plugins/estate-business/skills/exp-partnership/references/95-5-system.md +66 -0
- package/payload/premium-plugins/real-agency/plugins/estate-business/skills/exp-partnership/references/agent-attraction-scripts.md +90 -0
- package/payload/premium-plugins/real-agency/plugins/estate-business/skills/exp-partnership/references/business-partnership.md +92 -0
- package/payload/premium-plugins/real-agency/plugins/estate-business/skills/exp-partnership/references/exp-model-overview.md +66 -0
- package/payload/premium-plugins/real-agency/plugins/estate-business/skills/exp-partnership/references/model-comparison.md +66 -0
- package/payload/premium-plugins/real-agency/plugins/estate-business/skills/exp-partnership/references/revenue-share-explained.md +57 -0
- package/payload/premium-plugins/real-agency/plugins/estate-business/skills/personal-branding/SKILL.md +117 -0
- package/payload/premium-plugins/real-agency/plugins/estate-business/skills/personal-branding/references/attraction-agent-notes.md +31 -0
- package/payload/premium-plugins/real-agency/plugins/estate-business/skills/personal-branding/references/attraction-agent.md +58 -0
- package/payload/premium-plugins/real-agency/plugins/estate-business/skills/personal-branding/references/authenticity-boundaries.md +28 -0
- package/payload/premium-plugins/real-agency/plugins/estate-business/skills/personal-branding/references/become-a-brand-leader-notes.md +19 -0
- package/payload/premium-plugins/real-agency/plugins/estate-business/skills/personal-branding/references/blast-formula.md +42 -0
- package/payload/premium-plugins/real-agency/plugins/estate-business/skills/personal-branding/references/brand-leader.md +48 -0
- package/payload/premium-plugins/real-agency/plugins/estate-business/skills/personal-branding/references/brand-strategy-system.md +59 -0
- package/payload/premium-plugins/real-agency/plugins/estate-business/skills/personal-branding/references/content-engine.md +49 -0
- package/payload/premium-plugins/real-agency/plugins/estate-business/skills/personal-branding/references/firewave-blast-and-blogging.md +23 -0
- package/payload/premium-plugins/real-agency/plugins/estate-business/skills/personal-branding/references/gary-v-content.md +52 -0
- package/payload/premium-plugins/real-agency/plugins/estate-business/skills/personal-branding/references/gary-v-principles.md +20 -0
- package/payload/premium-plugins/real-agency/plugins/estate-business/skills/personal-branding/references/oversubscribed-positioning.md +18 -0
- package/payload/premium-plugins/real-agency/plugins/estate-business/skills/personal-branding/references/platforms.md +41 -0
- package/payload/premium-plugins/real-agency/plugins/estate-business/skills/personal-branding/references/priestley-oversubscribed.md +54 -0
- package/payload/premium-plugins/real-agency/plugins/estate-business/skills/personal-branding/references/storeys-style-examples.md +25 -0
- package/payload/premium-plugins/real-agency/plugins/estate-business/skills/personal-branding/references/visual-identity.md +27 -0
- package/payload/premium-plugins/real-agency/plugins/estate-coaching/PLUGIN.md +55 -0
- package/payload/premium-plugins/real-agency/plugins/estate-coaching/skills/agent-performance/SKILL.md +371 -0
- package/payload/premium-plugins/real-agency/plugins/estate-coaching/skills/agent-performance/references/atomic-habits.md +52 -0
- package/payload/premium-plugins/real-agency/plugins/estate-coaching/skills/agent-performance/references/daily-routine-scorecard.md +104 -0
- package/payload/premium-plugins/real-agency/plugins/estate-coaching/skills/agent-performance/references/hp6-model.md +63 -0
- package/payload/premium-plugins/real-agency/plugins/estate-coaching/skills/agent-performance/references/twelve-week-year.md +71 -0
- package/payload/premium-plugins/real-agency/plugins/estate-coaching/skills/bespoke-coaching/SKILL.md +36 -0
- package/payload/premium-plugins/real-agency/plugins/estate-coaching/skills/bespoke-coaching/references/coaching-boundaries.md +56 -0
- package/payload/premium-plugins/real-agency/plugins/estate-coaching/skills/bespoke-coaching/references/feedback-framework.md +61 -0
- package/payload/premium-plugins/real-agency/plugins/estate-coaching/skills/bespoke-coaching/references/performance-framework.md +109 -0
- package/payload/premium-plugins/real-agency/plugins/estate-coaching/skills/coaching-toolkit/SKILL.md +421 -0
- package/payload/premium-plugins/real-agency/plugins/estate-coaching/skills/coaching-toolkit/references/coaching-exercises.md +86 -0
- package/payload/premium-plugins/real-agency/plugins/estate-coaching/skills/coaching-toolkit/references/goal-setting.md +78 -0
- package/payload/premium-plugins/real-agency/plugins/estate-coaching/skills/coaching-toolkit/references/one-to-one-framework.md +92 -0
- package/payload/premium-plugins/real-agency/plugins/estate-coaching/skills/coaching-toolkit/references/soi-workbook.md +103 -0
- package/payload/premium-plugins/real-agency/plugins/estate-coaching/skills/serhant-training/SKILL.md +410 -0
- package/payload/premium-plugins/real-agency/plugins/estate-coaching/skills/serhant-training/references/agent-training-guide.md +70 -0
- package/payload/premium-plugins/real-agency/plugins/estate-coaching/skills/serhant-training/references/business-in-a-box.md +72 -0
- package/payload/premium-plugins/real-agency/plugins/estate-coaching/skills/serhant-training/references/buyers-guide.md +53 -0
- package/payload/premium-plugins/real-agency/plugins/estate-coaching/skills/serhant-training/references/codo-method.md +72 -0
- package/payload/premium-plugins/real-agency/plugins/estate-coaching/skills/serhant-training/references/website-planning-guide.md +79 -0
- package/payload/premium-plugins/real-agency/plugins/estate-onboarding/PLUGIN.md +31 -0
- package/payload/premium-plugins/real-agency/plugins/estate-onboarding/skills/bootstrap/SKILL.md +26 -0
- package/payload/premium-plugins/real-agency/plugins/estate-onboarding/skills/bootstrap/references/onboarding-flow.md +63 -0
- package/payload/premium-plugins/real-agency/plugins/estate-sales/PLUGIN.md +34 -0
- package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/negotiation/SKILL.md +35 -0
- package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/negotiation/references/deal-saving.md +47 -0
- package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/negotiation/references/negotiation-deep-guide.md +64 -0
- package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/negotiation/references/negotiation-prep-principles.md +29 -0
- package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/negotiation/references/negotiation-techniques.md +42 -0
- package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/negotiation/references/offer-presentation.md +43 -0
- package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/sales-closer/SKILL.md +24 -0
- package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/sales-closer/references/serhant-emotion-stages.md +36 -0
- package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/sales-discovery/SKILL.md +30 -0
- package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/sales-discovery/references/chris-voss-discovery.md +88 -0
- package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/sales-discovery/references/firewave-gost-journey.md +68 -0
- package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/sales-discovery/references/phil-jones-openers.md +78 -0
- package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/sales-discovery/references/pre-listing-checklist.md +77 -0
- package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/sales-discovery/references/serhant-improv.md +22 -0
- package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/sales-discovery/references/tom-ferry-discovery.md +103 -0
- package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/sales-discovery/references/vendor-motivation-competitor.md +52 -0
- package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/sales-negotiation/SKILL.md +29 -0
- package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/sales-negotiation/references/chris-voss-negotiation.md +70 -0
- package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/sales-negotiation/references/phil-jones-price-words.md +40 -0
- package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/sales-negotiation/references/serhant-negotiation-plus.md +55 -0
- package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/sales-negotiation/references/tom-panos-commission-pricing.md +57 -0
- package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/sales-negotiation/references/tony-morris-questioning.md +54 -0
- package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/sales-progression/SKILL.md +27 -0
- package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/sales-progression/references/conveyancing-guide.md +54 -0
- package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/sales-progression/references/transaction-tracking.md +66 -0
- package/payload/premium-plugins/real-agency/plugins/estate-teaching/PLUGIN.md +31 -0
- package/payload/premium-plugins/real-agency/plugins/estate-teaching/skills/content-directory/SKILL.md +39 -0
- package/payload/premium-plugins/real-agency/plugins/estate-teaching/skills/content-directory/references/module-delivery.md +65 -0
- package/payload/premium-plugins/real-agency/plugins/estate-teaching/skills/content-directory/references/progress-tracking.md +47 -0
- package/payload/premium-plugins/real-agency/plugins/leads/PLUGIN.md +32 -0
- package/payload/premium-plugins/real-agency/plugins/leads/skills/lead-nurturing/SKILL.md +137 -0
- package/payload/premium-plugins/real-agency/plugins/leads/skills/lead-nurturing/references/buyer-search-letter.md +28 -0
- package/payload/premium-plugins/real-agency/plugins/leads/skills/lead-nurturing/references/buyer-search-letters.md +37 -0
- package/payload/premium-plugins/real-agency/plugins/leads/skills/lead-nurturing/references/database-reactivation.md +30 -0
- package/payload/premium-plugins/real-agency/plugins/leads/skills/lead-nurturing/references/email-nurture-sequences.md +45 -0
- package/payload/premium-plugins/real-agency/plugins/leads/skills/lead-nurturing/references/facebook-referrals.md +30 -0
- package/payload/premium-plugins/real-agency/plugins/leads/skills/lead-nurturing/references/firewave-email-nurture-sequences.md +41 -0
- package/payload/premium-plugins/real-agency/plugins/leads/skills/lead-nurturing/references/keller-33-touch.md +34 -0
- package/payload/premium-plugins/real-agency/plugins/leads/skills/lead-nurturing/references/neighbour-letters.md +31 -0
- package/payload/premium-plugins/real-agency/plugins/leads/skills/lead-nurturing/references/neighbour-notification-letter.md +20 -0
- package/payload/premium-plugins/real-agency/plugins/leads/skills/lead-nurturing/references/ofi-follow-up-dialogue.md +22 -0
- package/payload/premium-plugins/real-agency/plugins/leads/skills/lead-nurturing/references/ofi-follow-up.md +26 -0
- package/payload/premium-plugins/real-agency/plugins/leads/skills/lead-nurturing/references/serhant-three-fs-plus.md +21 -0
- package/payload/premium-plugins/real-agency/plugins/leads/skills/lead-nurturing/references/sharran-10x10x10.md +18 -0
- package/payload/premium-plugins/real-agency/plugins/leads/skills/lead-nurturing/references/sms-templates.md +40 -0
- package/payload/premium-plugins/real-agency/plugins/leads/skills/lead-nurturing/references/sphere-of-influence-notes.md +34 -0
- package/payload/premium-plugins/real-agency/plugins/leads/skills/lead-nurturing/references/sphere-of-influence.md +60 -0
- package/payload/premium-plugins/real-agency/plugins/leads/skills/lead-nurturing/references/tom-panos-sms-templates.md +59 -0
- package/payload/premium-plugins/real-agency/plugins/leads/skills/prospecting/SKILL.md +33 -0
- package/payload/premium-plugins/real-agency/plugins/leads/skills/prospecting/references/database-matching.md +30 -0
- package/payload/premium-plugins/real-agency/plugins/leads/skills/prospecting/references/database-value.md +53 -0
- package/payload/premium-plugins/real-agency/plugins/leads/skills/prospecting/references/prospecting-dialogues.md +24 -0
- package/payload/premium-plugins/real-agency/plugins/leads/skills/prospecting/references/reactivation.md +34 -0
- package/payload/premium-plugins/real-agency/plugins/listings/PLUGIN.md +33 -0
- package/payload/premium-plugins/real-agency/plugins/listings/skills/home-preparation/SKILL.md +28 -0
- package/payload/premium-plugins/real-agency/plugins/listings/skills/home-preparation/references/kerb-appeal.md +38 -0
- package/payload/premium-plugins/real-agency/plugins/listings/skills/home-preparation/references/photo-day.md +59 -0
- package/payload/premium-plugins/real-agency/plugins/listings/skills/home-preparation/references/situational-tips.md +50 -0
- package/payload/premium-plugins/real-agency/plugins/listings/skills/home-preparation/references/staging-guide.md +52 -0
- package/payload/premium-plugins/real-agency/plugins/listings/skills/listing-presentation/SKILL.md +286 -0
- package/payload/premium-plugins/real-agency/plugins/listings/skills/listing-presentation/references/booking-script.md +51 -0
- package/payload/premium-plugins/real-agency/plugins/listings/skills/listing-presentation/references/objection-scripts.md +193 -0
- package/payload/premium-plugins/real-agency/plugins/listings/skills/listing-presentation/references/penhaul-presentation.md +123 -0
- package/payload/premium-plugins/real-agency/plugins/listings/skills/listing-presentation/references/pre-listing-kit.md +139 -0
- package/payload/premium-plugins/real-agency/plugins/listings/skills/listing-presentation/references/set-to-sell.md +55 -0
- package/payload/premium-plugins/real-agency/plugins/listings/skills/listing-presentation/references/sharran-frameworks.md +107 -0
- package/payload/premium-plugins/real-agency/plugins/listings/skills/property-marketing/SKILL.md +337 -0
- package/payload/premium-plugins/real-agency/plugins/listings/skills/property-marketing/references/auction-report-template.md +41 -0
- package/payload/premium-plugins/real-agency/plugins/listings/skills/property-marketing/references/coming-soon-campaign.md +43 -0
- package/payload/premium-plugins/real-agency/plugins/listings/skills/property-marketing/references/direct-mail-templates.md +121 -0
- package/payload/premium-plugins/real-agency/plugins/listings/skills/property-marketing/references/eoi-form-template.md +62 -0
- package/payload/premium-plugins/real-agency/plugins/listings/skills/property-marketing/references/monthly-scorecard.md +63 -0
- package/payload/premium-plugins/real-agency/plugins/loop/PLUGIN.md +73 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/index.d.ts +2 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/index.d.ts.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/index.js +293 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/index.js.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/lib/crypto.d.ts +10 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/lib/crypto.d.ts.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/lib/crypto.js +88 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/lib/crypto.js.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/lib/loop-api.d.ts +82 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/lib/loop-api.d.ts.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/lib/loop-api.js +427 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/lib/loop-api.js.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/lib/neo4j.d.ts +5 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/lib/neo4j.d.ts.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/lib/neo4j.js +40 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/lib/neo4j.js.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/customer-preferences.d.ts +10 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/customer-preferences.d.ts.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/customer-preferences.js +24 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/customer-preferences.js.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/feedback.d.ts +16 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/feedback.d.ts.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/feedback.js +35 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/feedback.js.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/key-deregister.d.ts +5 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/key-deregister.d.ts.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/key-deregister.js +19 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/key-deregister.js.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/key-list.d.ts +4 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/key-list.d.ts.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/key-list.js +14 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/key-list.js.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/key-register.d.ts +9 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/key-register.d.ts.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/key-register.js +60 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/key-register.js.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/marketing-enquiry.d.ts +13 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/marketing-enquiry.d.ts.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/marketing-enquiry.js +41 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/marketing-enquiry.js.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/marketing-match-batch.d.ts +9 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/marketing-match-batch.d.ts.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/marketing-match-batch.js +16 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/marketing-match-batch.js.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/marketing-match-request.d.ts +15 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/marketing-match-request.d.ts.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/marketing-match-request.js +11 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/marketing-match-request.js.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/marketing-match.d.ts +10 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/marketing-match.d.ts.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/marketing-match.js +39 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/marketing-match.js.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/people-detail.d.ts +9 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/people-detail.d.ts.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/people-detail.js +125 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/people-detail.js.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/people-search.d.ts +18 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/people-search.d.ts.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/people-search.js +87 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/people-search.js.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/property-detail.d.ts +10 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/property-detail.d.ts.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/property-detail.js +82 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/property-detail.js.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/property-listed.d.ts +12 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/property-listed.d.ts.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/property-listed.js +32 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/property-listed.js.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/property-request.d.ts +15 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/property-request.d.ts.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/property-request.js +11 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/property-request.js.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/property-search.d.ts +16 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/property-search.d.ts.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/property-search.js +41 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/property-search.js.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/supplier.d.ts +13 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/supplier.d.ts.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/supplier.js +49 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/supplier.js.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/team-availability.d.ts +7 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/team-availability.d.ts.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/team-availability.js +19 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/team-availability.js.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/team-info.d.ts +5 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/team-info.d.ts.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/team-info.js +32 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/team-info.js.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/viewing-create.d.ts +14 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/viewing-create.d.ts.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/viewing-create.js +11 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/viewing-create.js.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/viewing-detail.d.ts +9 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/viewing-detail.d.ts.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/viewing-detail.js +85 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/viewing-detail.js.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/viewing-search.d.ts +13 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/viewing-search.d.ts.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/viewing-search.js +44 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/viewing-search.js.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/viewing-update.d.ts +14 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/viewing-update.d.ts.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/viewing-update.js +18 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/viewing-update.js.map +1 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/package-lock.json +2549 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/package.json +21 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/__tests__/loop-swagger.snapshot.json +26467 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/__tests__/swagger-write-coverage.test.ts +153 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/index.ts +444 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/lib/crypto.ts +105 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/lib/loop-api.ts +604 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/lib/neo4j.ts +51 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/tools/customer-preferences.ts +66 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/tools/feedback.ts +86 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/tools/key-deregister.ts +27 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/tools/key-list.ts +19 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/tools/key-register.ts +95 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/tools/marketing-enquiry.ts +113 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/tools/marketing-match-batch.ts +53 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/tools/marketing-match-request.ts +42 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/tools/marketing-match.ts +84 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/tools/people-detail.ts +245 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/tools/people-search.ts +180 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/tools/property-detail.ts +145 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/tools/property-listed.ts +88 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/tools/property-request.ts +42 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/tools/property-search.ts +92 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/tools/supplier.ts +129 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/tools/team-availability.ts +52 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/tools/team-info.ts +95 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/tools/viewing-create.ts +41 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/tools/viewing-detail.ts +171 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/tools/viewing-search.ts +92 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/tools/viewing-update.ts +53 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/tsconfig.json +20 -0
- package/payload/premium-plugins/real-agency/plugins/loop/mcp/vitest.config.ts +9 -0
- package/payload/premium-plugins/real-agency/plugins/vendors/PLUGIN.md +34 -0
- package/payload/premium-plugins/real-agency/plugins/vendors/skills/vendor-communication/SKILL.md +42 -0
- package/payload/premium-plugins/real-agency/plugins/vendors/skills/vendor-communication/references/fee-protection-and-agenda.md +28 -0
- package/payload/premium-plugins/real-agency/plugins/vendors/skills/vendor-communication/references/listing-scripts.md +44 -0
- package/payload/premium-plugins/real-agency/plugins/vendors/skills/vendor-communication/references/negotiation-deep-guide.md +70 -0
- package/payload/premium-plugins/real-agency/plugins/vendors/skills/vendor-communication/references/price-alignment-scripts.md +33 -0
- package/payload/premium-plugins/real-agency/plugins/vendors/skills/vendor-communication/references/price-alignment.md +34 -0
- package/payload/premium-plugins/real-agency/plugins/vendors/skills/vendor-communication/references/scenario-scripts.md +38 -0
- package/payload/premium-plugins/real-agency/plugins/vendors/skills/vendor-communication/references/seller-engagement.md +51 -0
- package/payload/premium-plugins/real-agency/plugins/vendors/skills/vendor-communication/references/valuation-booking.md +76 -0
- package/payload/premium-plugins/real-agency/plugins/vendors/skills/vendor-communication/references/vendor-scripts.md +63 -0
- package/payload/premium-plugins/real-agency/plugins/vendors/skills/vendor-communication/references/vendor-updates.md +41 -0
- package/payload/premium-plugins/real-agency/plugins/vendors/skills/vendor-updates/SKILL.md +153 -0
- package/payload/premium-plugins/teaching/PLUGIN.md +57 -0
- package/payload/premium-plugins/teaching/skills/interactive-tutor/SKILL.md +59 -0
- package/payload/premium-plugins/teaching/skills/interactive-tutor/references/assessment.md +70 -0
- package/payload/premium-plugins/teaching/skills/interactive-tutor/references/classroom-conduct.md +43 -0
- package/payload/premium-plugins/teaching/skills/interactive-tutor/references/teaching-modes.md +83 -0
- package/payload/premium-plugins/teaching/skills/lesson-planner/SKILL.md +48 -0
- package/payload/premium-plugins/teaching/skills/lesson-planner/references/context-gathering.md +41 -0
- package/payload/premium-plugins/teaching/skills/lesson-planner/references/plan-structure.md +94 -0
- package/payload/premium-plugins/teaching/skills/study-pack-builder/SKILL.md +52 -0
- package/payload/premium-plugins/teaching/skills/study-pack-builder/references/disaggregation.md +49 -0
- package/payload/premium-plugins/teaching/skills/study-pack-builder/references/materials.md +116 -0
- package/payload/premium-plugins/writer-craft/PLUGIN.md +87 -0
- package/payload/premium-plugins/writer-craft/agents/writer-craft--manuscript-reviewer.md +92 -0
- package/payload/premium-plugins/writer-craft/skills/citation-style/SKILL.md +94 -0
- package/payload/premium-plugins/writer-craft/skills/citation-style/references/book-and-chapter-models.md +77 -0
- package/payload/premium-plugins/writer-craft/skills/citation-style/references/citation-rules.md +103 -0
- package/payload/premium-plugins/writer-craft/skills/citation-style/references/journal-article-models.md +74 -0
- package/payload/premium-plugins/writer-craft/skills/citation-style/references/other-source-models.md +146 -0
- package/payload/premium-plugins/writer-craft/skills/citation-style/references/reference-list-rules.md +70 -0
- package/payload/premium-plugins/writer-craft/skills/editorial-practice/SKILL.md +108 -0
- package/payload/premium-plugins/writer-craft/skills/editorial-practice/references/copyediting.md +73 -0
- package/payload/premium-plugins/writer-craft/skills/editorial-practice/references/developmental-editing.md +85 -0
- package/payload/premium-plugins/writer-craft/skills/editorial-practice/references/genre-specific-editing.md +78 -0
- package/payload/premium-plugins/writer-craft/skills/editorial-practice/references/line-editing.md +55 -0
- package/payload/premium-plugins/writer-craft/skills/editorial-practice/references/self-editing.md +89 -0
- package/payload/premium-plugins/writer-craft/skills/persuasive-storytelling/SKILL.md +114 -0
- package/payload/premium-plugins/writer-craft/skills/persuasive-storytelling/references/audience-analysis.md +73 -0
- package/payload/premium-plugins/writer-craft/skills/persuasive-storytelling/references/crafting-persuasive-story.md +76 -0
- package/payload/premium-plugins/writer-craft/skills/persuasive-storytelling/references/persuasion-case-studies.md +67 -0
- package/payload/premium-plugins/writer-craft/skills/persuasive-storytelling/references/transformation-framework.md +86 -0
- package/payload/premium-plugins/writer-craft/skills/point-of-view/SKILL.md +97 -0
- package/payload/premium-plugins/writer-craft/skills/point-of-view/references/indirect-narration.md +72 -0
- package/payload/premium-plugins/writer-craft/skills/point-of-view/references/pov-types-and-voice.md +91 -0
- package/payload/premium-plugins/writer-craft/skills/point-of-view/references/protagonist-filter.md +71 -0
- package/payload/premium-plugins/writer-craft/skills/point-of-view/references/tense-and-person.md +85 -0
- package/payload/premium-plugins/writer-craft/skills/prose-craft/SKILL.md +100 -0
- package/payload/premium-plugins/writer-craft/skills/prose-craft/references/punctuation-and-grammar.md +72 -0
- package/payload/premium-plugins/writer-craft/skills/prose-craft/references/repetition.md +71 -0
- package/payload/premium-plugins/writer-craft/skills/prose-craft/references/sound-and-rhythm.md +64 -0
- package/payload/premium-plugins/writer-craft/skills/prose-craft/references/word-economy.md +93 -0
- package/payload/premium-plugins/writer-craft/skills/reader-engagement/SKILL.md +100 -0
- package/payload/premium-plugins/writer-craft/skills/reader-engagement/references/cause-effect-setup-payoff.md +79 -0
- package/payload/premium-plugins/writer-craft/skills/reader-engagement/references/conflict-escalation.md +81 -0
- package/payload/premium-plugins/writer-craft/skills/reader-engagement/references/hooking-readers.md +67 -0
- package/payload/premium-plugins/writer-craft/skills/reader-engagement/references/neurochemistry-of-engagement.md +94 -0
- package/payload/premium-plugins/writer-craft/skills/review-manuscript/SKILL.md +111 -0
- package/payload/premium-plugins/writer-craft/skills/review-manuscript/references/review-manuscript-checklist.md +119 -0
- package/payload/premium-plugins/writer-craft/skills/review-prose/SKILL.md +99 -0
- package/payload/premium-plugins/writer-craft/skills/review-prose/references/prose-review-checklist.md +112 -0
- package/payload/premium-plugins/writer-craft/skills/review-scene/SKILL.md +99 -0
- package/payload/premium-plugins/writer-craft/skills/review-scene/references/scene-analysis-framework.md +95 -0
- package/payload/premium-plugins/writer-craft/skills/story-architecture/SKILL.md +106 -0
- package/payload/premium-plugins/writer-craft/skills/story-architecture/references/blueprinting-and-scene-cards.md +118 -0
- package/payload/premium-plugins/writer-craft/skills/story-architecture/references/inner-issue-and-protagonist-goal.md +66 -0
- package/payload/premium-plugins/writer-craft/skills/story-architecture/references/misbelief-desire-worldview.md +87 -0
- package/payload/premium-plugins/writer-craft/skills/story-architecture/references/origin-scenes-and-escalation.md +82 -0
- package/payload/premium-plugins/writer-craft/skills/story-blueprint/SKILL.md +133 -0
- package/payload/premium-plugins/writer-craft/skills/story-blueprint/references/blueprinting-exercises.md +118 -0
- package/payload/premium-plugins/writer-craft/skills/story-blueprint/references/blueprinting-process.md +128 -0
- package/payload/server/adminuser-self-heal-QAWOZ3JV.js +45 -0
- package/payload/server/chunk-5FM432JB.js +4148 -0
- package/payload/server/chunk-6S5JTXAN.js +1544 -0
- package/payload/server/chunk-JSBRDJBE.js +30 -0
- package/payload/server/chunk-RNW625CL.js +759 -0
- package/payload/server/cloudflare-task-tracker-VC7QVU5H.js +22 -0
- package/payload/server/maxy-edge.js +1021 -0
- package/payload/server/package.json +13 -0
- package/payload/server/public/assets/Checkbox-C6ZCsPvl.js +1 -0
- package/payload/server/public/assets/_baseFor-BHtDrjIo.js +1 -0
- package/payload/server/public/assets/admin-CWMpccrR.css +1 -0
- package/payload/server/public/assets/admin-DVGJmN-k.js +216 -0
- package/payload/server/public/assets/arc-DMDAZHAN.js +1 -0
- package/payload/server/public/assets/architecture-YZFGNWBL-COhEvUpo.js +1 -0
- package/payload/server/public/assets/architectureDiagram-Q4EWVU46-DwN6H0y2.js +36 -0
- package/payload/server/public/assets/array-DetWRiSa.js +1 -0
- package/payload/server/public/assets/blockDiagram-DXYQGD6D-TUk_F7H6.js +132 -0
- package/payload/server/public/assets/c4Diagram-AHTNJAMY-CTjGko0X.js +10 -0
- package/payload/server/public/assets/channel-Cv-65bLZ.js +1 -0
- package/payload/server/public/assets/chunk-2KRD3SAO-Di4bO8ir.js +1 -0
- package/payload/server/public/assets/chunk-336JU56O-DulT46bV.js +2 -0
- package/payload/server/public/assets/chunk-426QAEUC-BwKj8yqp.js +1 -0
- package/payload/server/public/assets/chunk-4BX2VUAB-DyEhFk-Z.js +1 -0
- package/payload/server/public/assets/chunk-4TB4RGXK-CewO8YaZ.js +206 -0
- package/payload/server/public/assets/chunk-55IACEB6-BRJOZLpU.js +1 -0
- package/payload/server/public/assets/chunk-5FUZZQ4R-B3IWYz-k.js +62 -0
- package/payload/server/public/assets/chunk-5PVQY5BW-DyiDEtXY.js +2 -0
- package/payload/server/public/assets/chunk-67CJDMHE-BG6-9r6c.js +1 -0
- package/payload/server/public/assets/chunk-7N4EOEYR-BvMbitX7.js +1 -0
- package/payload/server/public/assets/chunk-AA7GKIK3-CE8mGBD5.js +1 -0
- package/payload/server/public/assets/chunk-BSJP7CBP-DP7LTBll.js +1 -0
- package/payload/server/public/assets/chunk-CIAEETIT-VfnIdN-h.js +1 -0
- package/payload/server/public/assets/chunk-DD-I1_y5.js +1 -0
- package/payload/server/public/assets/chunk-EDXVE4YY-B3u7wU36.js +1 -0
- package/payload/server/public/assets/chunk-ENJZ2VHE-BesS5YY4.js +10 -0
- package/payload/server/public/assets/chunk-FMBD7UC4-w-yBZsN2.js +15 -0
- package/payload/server/public/assets/chunk-FOC6F5B3-f9cFywhd.js +1 -0
- package/payload/server/public/assets/chunk-ICPOFSXX-C5_hbB6H.js +122 -0
- package/payload/server/public/assets/chunk-K5T4RW27-B_ZUrFUq.js +94 -0
- package/payload/server/public/assets/chunk-KGLVRYIC-CcWTvRlI.js +1 -0
- package/payload/server/public/assets/chunk-LIHQZDEY-D-5-peQw.js +1 -0
- package/payload/server/public/assets/chunk-ORNJ4GCN-B4Z5L25I.js +1 -0
- package/payload/server/public/assets/chunk-OYMX7WX6-CrL4rhBa.js +231 -0
- package/payload/server/public/assets/chunk-QZHKN3VN-CWh_0JsP.js +1 -0
- package/payload/server/public/assets/chunk-U2HBQHQK-CQpbcqRS.js +70 -0
- package/payload/server/public/assets/chunk-X2U36JSP-1HG7T4gX.js +1 -0
- package/payload/server/public/assets/chunk-XPW4576I-m1Y_r88I.js +32 -0
- package/payload/server/public/assets/chunk-YZCP3GAM-boN5wjX9.js +1 -0
- package/payload/server/public/assets/chunk-ZZ45TVLE-D6CiPO0Q.js +1 -0
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-wQ2-BRyB.js +1 -0
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-B_YLNNHy.js +1 -0
- package/payload/server/public/assets/clone-VLK-GPZZ.js +1 -0
- package/payload/server/public/assets/cormorant-cyrillic-300-normal-CzPHYadL.woff +0 -0
- package/payload/server/public/assets/cormorant-cyrillic-300-normal-DFUoTmrg.woff2 +0 -0
- package/payload/server/public/assets/cormorant-cyrillic-400-normal-C8QS47vb.woff2 +0 -0
- package/payload/server/public/assets/cormorant-cyrillic-400-normal-D3EsxgFc.woff +0 -0
- package/payload/server/public/assets/cormorant-cyrillic-500-normal-B7dJQtg-.woff +0 -0
- package/payload/server/public/assets/cormorant-cyrillic-500-normal-BLlg2W5x.woff2 +0 -0
- package/payload/server/public/assets/cormorant-cyrillic-ext-300-normal-BXl3lXsi.woff2 +0 -0
- package/payload/server/public/assets/cormorant-cyrillic-ext-300-normal-DmxSOTe3.woff +0 -0
- package/payload/server/public/assets/cormorant-cyrillic-ext-400-normal-Bgrpe4p1.woff +0 -0
- package/payload/server/public/assets/cormorant-cyrillic-ext-400-normal-BlcaxZtM.woff2 +0 -0
- package/payload/server/public/assets/cormorant-cyrillic-ext-500-normal-CdQuyvtc.woff +0 -0
- package/payload/server/public/assets/cormorant-cyrillic-ext-500-normal-pZw22qtS.woff2 +0 -0
- package/payload/server/public/assets/cormorant-latin-300-normal-CJ5dfen0.woff2 +0 -0
- package/payload/server/public/assets/cormorant-latin-300-normal-DQZObO_3.woff +0 -0
- package/payload/server/public/assets/cormorant-latin-400-normal-BGH8Vunh.woff2 +0 -0
- package/payload/server/public/assets/cormorant-latin-400-normal-C3_-2Ua-.woff +0 -0
- package/payload/server/public/assets/cormorant-latin-500-normal-Dj3SQ6fR.woff +0 -0
- package/payload/server/public/assets/cormorant-latin-500-normal-EBdSCOD3.woff2 +0 -0
- package/payload/server/public/assets/cormorant-latin-ext-300-normal-CkiUx0UG.woff +0 -0
- package/payload/server/public/assets/cormorant-latin-ext-300-normal-De3D72RL.woff2 +0 -0
- package/payload/server/public/assets/cormorant-latin-ext-400-normal-DuQ88yz3.woff2 +0 -0
- package/payload/server/public/assets/cormorant-latin-ext-400-normal-DuXFa1Dr.woff +0 -0
- package/payload/server/public/assets/cormorant-latin-ext-500-normal-AH9qog1s.woff2 +0 -0
- package/payload/server/public/assets/cormorant-latin-ext-500-normal-DAuUCO41.woff +0 -0
- package/payload/server/public/assets/cormorant-vietnamese-300-normal-BVqIp_mg.woff2 +0 -0
- package/payload/server/public/assets/cormorant-vietnamese-300-normal-CEMS9Pw-.woff +0 -0
- package/payload/server/public/assets/cormorant-vietnamese-400-normal-C-RiYxEf.woff2 +0 -0
- package/payload/server/public/assets/cormorant-vietnamese-400-normal-DmUuA7Y2.woff +0 -0
- package/payload/server/public/assets/cormorant-vietnamese-500-normal-DsPuwQHi.woff2 +0 -0
- package/payload/server/public/assets/cormorant-vietnamese-500-normal-tGBW_mI7.woff +0 -0
- package/payload/server/public/assets/cose-bilkent-S5V4N54A-BhtgY3T7.js +1 -0
- package/payload/server/public/assets/cytoscape.esm-C9yNhe1u.js +321 -0
- package/payload/server/public/assets/dagre-CncXYNX1.js +1 -0
- package/payload/server/public/assets/dagre-KV5264BT-C0CcgCHW.js +4 -0
- package/payload/server/public/assets/data-Bt4Wsocg.js +1 -0
- package/payload/server/public/assets/defaultLocale-_WRwicXn.js +1 -0
- package/payload/server/public/assets/diagram-5BDNPKRD-CkUlWbsI.js +10 -0
- package/payload/server/public/assets/diagram-G4DWMVQ6-DVZBG1Ul.js +24 -0
- package/payload/server/public/assets/diagram-MMDJMWI5-BIb06ZkK.js +43 -0
- package/payload/server/public/assets/diagram-TYMM5635-BcT0_WR8.js +24 -0
- package/payload/server/public/assets/dist-Bd4S37oi.js +1 -0
- package/payload/server/public/assets/dm-sans-latin-400-normal-BwCSEQnW.woff +0 -0
- package/payload/server/public/assets/dm-sans-latin-400-normal-CW0RaeGs.woff2 +0 -0
- package/payload/server/public/assets/dm-sans-latin-500-normal-B9HHJjqV.woff2 +0 -0
- package/payload/server/public/assets/dm-sans-latin-500-normal-Dr3UlScf.woff +0 -0
- package/payload/server/public/assets/dm-sans-latin-ext-400-normal-BjWJ59Pq.woff +0 -0
- package/payload/server/public/assets/dm-sans-latin-ext-400-normal-BtiwyxMk.woff2 +0 -0
- package/payload/server/public/assets/dm-sans-latin-ext-500-normal-BJfUCQsA.woff2 +0 -0
- package/payload/server/public/assets/dm-sans-latin-ext-500-normal-DR84L5F-.woff +0 -0
- package/payload/server/public/assets/erDiagram-SMLLAGMA-BHk6lxIT.js +85 -0
- package/payload/server/public/assets/flatten-BsWEYbBB.js +1 -0
- package/payload/server/public/assets/flowDiagram-DWJPFMVM-CjeJn490.js +162 -0
- package/payload/server/public/assets/ganttDiagram-T4ZO3ILL-BGYvX3Lv.js +292 -0
- package/payload/server/public/assets/gitGraph-7Q5UKJZL-DeTNsAO0.js +1 -0
- package/payload/server/public/assets/gitGraphDiagram-UUTBAWPF-Br4WLGzW.js +106 -0
- package/payload/server/public/assets/graph-CRSLozxc.js +1 -0
- package/payload/server/public/assets/graph-labels-CQyZQ0u6.js +1 -0
- package/payload/server/public/assets/graphlib-BWd9sMeP.js +1 -0
- package/payload/server/public/assets/info-OMHHGYJF-DJJ9GlS6.js +1 -0
- package/payload/server/public/assets/infoDiagram-42DDH7IO-BjZeQoNZ.js +2 -0
- package/payload/server/public/assets/init-sTEcj9YX.js +1 -0
- package/payload/server/public/assets/isEmpty-BWl67LAZ.js +1 -0
- package/payload/server/public/assets/ishikawaDiagram-UXIWVN3A-POMae6Ni.js +70 -0
- package/payload/server/public/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
- package/payload/server/public/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
- package/payload/server/public/assets/jetbrains-mono-cyrillic-500-normal-DJqRU3vO.woff +0 -0
- package/payload/server/public/assets/jetbrains-mono-cyrillic-500-normal-DmUKJPL_.woff2 +0 -0
- package/payload/server/public/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
- package/payload/server/public/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
- package/payload/server/public/assets/jetbrains-mono-greek-500-normal-D7SFKleX.woff +0 -0
- package/payload/server/public/assets/jetbrains-mono-greek-500-normal-JpySY46c.woff2 +0 -0
- package/payload/server/public/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- package/payload/server/public/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/payload/server/public/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
- package/payload/server/public/assets/jetbrains-mono-latin-500-normal-CJOVTJB7.woff +0 -0
- package/payload/server/public/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
- package/payload/server/public/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
- package/payload/server/public/assets/jetbrains-mono-latin-ext-500-normal-Cut-4mMH.woff2 +0 -0
- package/payload/server/public/assets/jetbrains-mono-latin-ext-500-normal-ckzbgY84.woff +0 -0
- package/payload/server/public/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
- package/payload/server/public/assets/jetbrains-mono-vietnamese-500-normal-DNRqzVM1.woff +0 -0
- package/payload/server/public/assets/journeyDiagram-VCZTEJTY-ledtLV6x.js +139 -0
- package/payload/server/public/assets/jsx-runtime-DvanDPKm.css +1 -0
- package/payload/server/public/assets/jsx-runtime-vPsBTwUp.js +9 -0
- package/payload/server/public/assets/kanban-definition-6JOO6SKY-DI0T4W9z.js +89 -0
- package/payload/server/public/assets/katex-s61Rgv6l.js +257 -0
- package/payload/server/public/assets/lib-W5Jcz4p8.js +33 -0
- package/payload/server/public/assets/line-D1281H12.js +1 -0
- package/payload/server/public/assets/linear-0O14Y6uf.js +1 -0
- package/payload/server/public/assets/mermaid-parser.core-Dt95U7zk.js +4 -0
- package/payload/server/public/assets/mermaid.core-BuYSs1fU.js +11 -0
- package/payload/server/public/assets/mindmap-definition-QFDTVHPH-CFE1lmfX.js +96 -0
- package/payload/server/public/assets/ordinal-krseTxxN.js +1 -0
- package/payload/server/public/assets/packet-4T2RLAQJ-CGbvGkvF.js +1 -0
- package/payload/server/public/assets/page-CSUcuVW0.js +1 -0
- package/payload/server/public/assets/page-TARBO-Yr.js +50 -0
- package/payload/server/public/assets/path-B0Ik7Tu9.js +1 -0
- package/payload/server/public/assets/pie-ZZUOXDRM-BZy8rjFn.js +1 -0
- package/payload/server/public/assets/pieDiagram-DEJITSTG-BSd9xa7v.js +30 -0
- package/payload/server/public/assets/public-5r6aRXrb.js +8 -0
- package/payload/server/public/assets/quadrantDiagram-34T5L4WZ-xYehPVw5.js +7 -0
- package/payload/server/public/assets/radar-PYXPWWZC-DcfWIVXr.js +1 -0
- package/payload/server/public/assets/reduce-tk-xY6Fv.js +1 -0
- package/payload/server/public/assets/requirementDiagram-MS252O5E-C6n77V1S.js +84 -0
- package/payload/server/public/assets/rough.esm-DKRO8IF-.js +1 -0
- package/payload/server/public/assets/sankeyDiagram-XADWPNL6-CWIfeO1M.js +10 -0
- package/payload/server/public/assets/sequenceDiagram-FGHM5R23-DDv2DuMo.js +157 -0
- package/payload/server/public/assets/src-B6XdH6xq.js +1 -0
- package/payload/server/public/assets/stateDiagram-FHFEXIEX-BPZdmsww.js +1 -0
- package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-33eC4TwE.js +1 -0
- package/payload/server/public/assets/timeline-definition-GMOUNBTQ-BpRT_wSX.js +120 -0
- package/payload/server/public/assets/treeView-SZITEDCU-3WugwVdj.js +1 -0
- package/payload/server/public/assets/treemap-W4RFUUIX-Cf5mDLlu.js +1 -0
- package/payload/server/public/assets/vennDiagram-DHZGUBPP-BG8ubucH.js +34 -0
- package/payload/server/public/assets/wardley-RL74JXVD-Bv4md4b3.js +1 -0
- package/payload/server/public/assets/wardleyDiagram-NUSXRM2D-D4E7PU1B.js +20 -0
- package/payload/server/public/assets/xychartDiagram-5P7HB3ND-Ur2xVM-c.js +7 -0
- package/payload/server/public/brand/claude.png +0 -0
- package/payload/server/public/brand/favicon.ico +0 -0
- package/payload/server/public/brand/maxy-black.png +0 -0
- package/payload/server/public/brand/maxy-horizontal.png +0 -0
- package/payload/server/public/brand/maxy-monochrome.png +0 -0
- package/payload/server/public/brand/maxy-square.png +0 -0
- package/payload/server/public/brand/maxy.png +0 -0
- package/payload/server/public/brand/star.png +0 -0
- package/payload/server/public/brand-constants.json +8 -0
- package/payload/server/public/brand-defaults.css +12 -0
- package/payload/server/public/data.html +18 -0
- package/payload/server/public/favicon.ico +0 -0
- package/payload/server/public/graph.html +19 -0
- package/payload/server/public/index.html +23 -0
- package/payload/server/public/public.html +19 -0
- package/payload/server/public/robots.txt +5 -0
- package/payload/server/public/vnc-popout.html +63 -0
- package/payload/server/server-init.cjs +115 -0
- package/payload/server/server.js +14960 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,3325 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execFileSync, spawn, spawnSync } from "node:child_process";
|
|
3
|
+
import { existsSync, mkdirSync, writeFileSync, cpSync, readFileSync, rmSync, readdirSync, appendFileSync, openSync, closeSync, chmodSync, symlinkSync, unlinkSync, lstatSync, statSync, readlinkSync, realpathSync, accessSync, constants as fsConstants } from "node:fs";
|
|
4
|
+
import { resolve, join, dirname } from "node:path";
|
|
5
|
+
import { randomBytes } from "node:crypto";
|
|
6
|
+
import { resolveInstallPortFromFs, buildMaxyUnitFile, buildClaudeSessionManagerUnitFile } from "./port-resolution.js";
|
|
7
|
+
import { parseOsRelease, isUbuntuLike as isUbuntuLikePure, parseAptCacheCandidate, decideAptResolution, } from "./apt-resolve.js";
|
|
8
|
+
import { findPeerBrandOnDefaultNeo4jPort } from "./peer-brand-detect.js";
|
|
9
|
+
import { requireSupportedPlatform } from "./platform-detect.js";
|
|
10
|
+
import { renderPlist } from "./launchd-plist.js";
|
|
11
|
+
import { installAllBrewPackages } from "./brew-install.js";
|
|
12
|
+
import { parseSwVers, isSupportedMacosVersion } from "./macos-version.js";
|
|
13
|
+
import { decideChromiumAction, isSnapConfinedPath } from "./snap-chromium.js";
|
|
14
|
+
import { classifyPortHolder } from "./preflight-port-classifier.js";
|
|
15
|
+
const PAYLOAD_DIR = resolve(import.meta.dirname, "../payload");
|
|
16
|
+
// Brand manifest — read from payload to derive all brand-specific installation values.
|
|
17
|
+
// The bundler stamps brand.json into the payload at build time.
|
|
18
|
+
const BRAND_PATH = join(PAYLOAD_DIR, "platform", "config", "brand.json");
|
|
19
|
+
if (!existsSync(BRAND_PATH)) {
|
|
20
|
+
console.error("Setup failed: brand.json not found in payload (package may be corrupted)");
|
|
21
|
+
console.error("FATAL: brand.json not found in payload. Package may be corrupted.");
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
let BRAND;
|
|
25
|
+
try {
|
|
26
|
+
BRAND = JSON.parse(readFileSync(BRAND_PATH, "utf-8"));
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
console.error(`Setup failed: failed to parse brand.json: ${err.message}`);
|
|
30
|
+
console.error(`FATAL: Failed to parse brand.json: ${err.message}`);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
const INSTALL_DIR = resolve(process.env.HOME ?? "/root", BRAND.installDir);
|
|
34
|
+
const LOG_DIR = resolve(process.env.HOME ?? "/root", BRAND.configDir, "logs");
|
|
35
|
+
const LOG_FILE = join(LOG_DIR, `install-${new Date().toISOString().replace(/[:.]/g, "-")}.log`);
|
|
36
|
+
/** Known brand hostnames in the Maxy ecosystem. Each brand ships a main unit
|
|
37
|
+
* (`<hostname>.service`) and a per-brand edge unit (`<hostname>-edge.service`,
|
|
38
|
+
* Task 662). Peer-brand detection matches only these filenames — stale units,
|
|
39
|
+
* gnome-keyring disable markers, and unrelated user services are not peer
|
|
40
|
+
* evidence. When a third brand is added under `brands/`, append its hostname
|
|
41
|
+
* here AND in the matching constant in `uninstall.ts` (intentional duplication
|
|
42
|
+
* per `uninstall.ts:` "Shell helpers (duplicated from index.ts ...)" policy). */
|
|
43
|
+
const KNOWN_BRAND_HOSTNAMES = ["maxy", "realagent", "maxy-2", "maxy-3", "maxy-4"];
|
|
44
|
+
// The device's actual hostname — may differ from BRAND.hostname if the user customized it.
|
|
45
|
+
// Updated by installSystemDeps() after hostname setup; used for user-facing URLs.
|
|
46
|
+
let DEVICE_HOSTNAME = BRAND.hostname;
|
|
47
|
+
// Task 929 — absolute path to the non-snap Chromium binary chosen during
|
|
48
|
+
// installSystemDeps(). Defaults to /usr/bin/chromium so non-Linux installs
|
|
49
|
+
// (which never call ensureNonSnapChromium) and the systemd unit's
|
|
50
|
+
// PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH still see a sensible value. On Linux,
|
|
51
|
+
// installSystemDeps() always overwrites this — either /usr/bin/chromium (Pi
|
|
52
|
+
// Bookworm: real .deb) or /usr/bin/google-chrome-stable (Ubuntu Noble laptop:
|
|
53
|
+
// snap-confined chromium replaced). Read by writeChromiumBinaryPathFile()
|
|
54
|
+
// and threaded into buildMaxyUnitFile() so the chromium-binary.path config
|
|
55
|
+
// file, the systemd unit's PLAYWRIGHT env var, and vnc.sh's runtime resolver
|
|
56
|
+
// all agree on one absolute path.
|
|
57
|
+
let RESOLVED_CHROMIUM_BIN = "/usr/bin/chromium";
|
|
58
|
+
// npm flags tuned for Raspberry Pi — reduce parallelism, increase patience
|
|
59
|
+
const NPM_NET_FLAGS = [
|
|
60
|
+
"--fetch-retries=5",
|
|
61
|
+
"--fetch-retry-mintimeout=30000",
|
|
62
|
+
"--fetch-retry-maxtimeout=120000",
|
|
63
|
+
"--maxsockets=3",
|
|
64
|
+
];
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Logging — timestamped to console AND persistent log file
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
function initLogging() {
|
|
69
|
+
mkdirSync(LOG_DIR, { recursive: true });
|
|
70
|
+
appendFileSync(LOG_FILE, [
|
|
71
|
+
"",
|
|
72
|
+
"=".repeat(64),
|
|
73
|
+
` ${BRAND.productName} Install Log — ${new Date().toISOString()}`,
|
|
74
|
+
` Node ${process.version} | ${process.platform} ${process.arch}`,
|
|
75
|
+
"=".repeat(64),
|
|
76
|
+
"",
|
|
77
|
+
].join("\n") + "\n");
|
|
78
|
+
}
|
|
79
|
+
function logFile(msg) {
|
|
80
|
+
try {
|
|
81
|
+
appendFileSync(LOG_FILE, `[${new Date().toISOString()}] ${msg}\n`);
|
|
82
|
+
}
|
|
83
|
+
catch { /* fs full */ }
|
|
84
|
+
}
|
|
85
|
+
// Mirror all console output into the log file
|
|
86
|
+
const _log = console.log;
|
|
87
|
+
const _err = console.error;
|
|
88
|
+
console.log = (...args) => { _log(...args); logFile(args.map(String).join(" ")); };
|
|
89
|
+
console.error = (...args) => { _err(...args); logFile(`[ERROR] ${args.map(String).join(" ")}`); };
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
// Diagnostics — system state snapshot for post-mortem analysis
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
function logDiagnostics(label) {
|
|
94
|
+
logFile(`\n--- Diagnostics: ${label} ---`);
|
|
95
|
+
if (!isLinux()) {
|
|
96
|
+
logFile(" (not Linux — limited diagnostics)");
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const run = (cmd, args) => {
|
|
100
|
+
const r = spawnSync(cmd, args, { encoding: "utf-8", stdio: "pipe", timeout: 10_000 });
|
|
101
|
+
return (r.stdout || r.stderr || "").trim();
|
|
102
|
+
};
|
|
103
|
+
logFile(`Disk:\n${run("df", ["-h", "/", "/tmp"])}`);
|
|
104
|
+
logFile(`Memory:\n${run("free", ["-h"])}`);
|
|
105
|
+
logFile(`Uptime: ${run("uptime", [])}`);
|
|
106
|
+
for (const host of ["registry.npmjs.org", "github.com"]) {
|
|
107
|
+
const dns = spawnSync("host", ["-W", "5", host], { encoding: "utf-8", stdio: "pipe", timeout: 10_000 });
|
|
108
|
+
logFile(`DNS ${host}: ${dns.status === 0 ? "OK" : "FAIL"} — ${(dns.stdout || dns.stderr || "").trim().split("\n")[0]}`);
|
|
109
|
+
}
|
|
110
|
+
const curl = spawnSync("curl", [
|
|
111
|
+
"-sf", "-o", "/dev/null",
|
|
112
|
+
"-w", "HTTP %{http_code} in %{time_total}s (dns: %{time_namelookup}s, connect: %{time_connect}s)",
|
|
113
|
+
"--connect-timeout", "10",
|
|
114
|
+
"https://registry.npmjs.org/",
|
|
115
|
+
], { encoding: "utf-8", stdio: "pipe", timeout: 15_000 });
|
|
116
|
+
logFile(`Registry HTTP: ${curl.stdout?.trim() || `FAIL — ${curl.stderr?.trim()}`}`);
|
|
117
|
+
logFile(`--- End Diagnostics ---\n`);
|
|
118
|
+
}
|
|
119
|
+
/** Append the most recent npm debug log into our install log for post-mortem. */
|
|
120
|
+
function captureNpmDebugLog() {
|
|
121
|
+
const logsDir = resolve(process.env.HOME ?? "/root", ".npm/_logs");
|
|
122
|
+
if (!existsSync(logsDir))
|
|
123
|
+
return;
|
|
124
|
+
try {
|
|
125
|
+
const files = readdirSync(logsDir).filter(f => f.endsWith("-debug-0.log")).sort().reverse();
|
|
126
|
+
if (files[0]) {
|
|
127
|
+
const content = readFileSync(join(logsDir, files[0]), "utf-8");
|
|
128
|
+
logFile(`\n--- npm debug log: ${files[0]} (last 200 lines) ---`);
|
|
129
|
+
const lines = content.split("\n");
|
|
130
|
+
logFile(lines.slice(Math.max(0, lines.length - 200)).join("\n"));
|
|
131
|
+
logFile(`--- end npm debug log ---\n`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch { /* ignore */ }
|
|
135
|
+
}
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
// Helpers
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
function log(step, total, message) {
|
|
140
|
+
console.log(`[${step}/${total}] ${message}`);
|
|
141
|
+
}
|
|
142
|
+
function shell(command, args, options) {
|
|
143
|
+
const cmd = options?.sudo ? "sudo" : command;
|
|
144
|
+
const cmdArgs = options?.sudo ? [command, ...args] : args;
|
|
145
|
+
const start = Date.now();
|
|
146
|
+
// Redaction (Task 744): callers handling secrets pass redact: true so the
|
|
147
|
+
// wrapper records the command name only, not the secret-bearing args. The
|
|
148
|
+
// child process still receives the real args via spawnSync below; only the
|
|
149
|
+
// install log line is sanitised. The grep-able audit shape stays:
|
|
150
|
+
// > sudo neo4j-admin dbms set-initial-password [REDACTED]
|
|
151
|
+
const loggedArgs = options?.redact
|
|
152
|
+
? `${cmdArgs.slice(0, options?.sudo ? 4 : 3).join(" ")} [REDACTED]`
|
|
153
|
+
: cmdArgs.join(" ");
|
|
154
|
+
logFile(`> ${cmd} ${loggedArgs}${options?.cwd ? ` [cwd: ${options.cwd}]` : ""}`);
|
|
155
|
+
const result = spawnSync(cmd, cmdArgs, {
|
|
156
|
+
stdio: "inherit",
|
|
157
|
+
timeout: options?.timeout ?? 300_000,
|
|
158
|
+
cwd: options?.cwd,
|
|
159
|
+
env: options?.env,
|
|
160
|
+
});
|
|
161
|
+
const dur = ((Date.now() - start) / 1000).toFixed(1);
|
|
162
|
+
// bestEffort (Task 787): tear-down ops on units/state that may or may not
|
|
163
|
+
// exist (stop/disable a system service we may never have started, reset-failed
|
|
164
|
+
// a freshly-created unit) log the non-zero exit but do not throw. Reserved
|
|
165
|
+
// for the "may not exist" pattern only — never use for ops that must succeed.
|
|
166
|
+
if (result.signal) {
|
|
167
|
+
logFile(` KILLED (${result.signal}) after ${dur}s`);
|
|
168
|
+
if (options?.bestEffort)
|
|
169
|
+
return;
|
|
170
|
+
throw new Error(`Command killed (${result.signal}) after ${dur}s: ${cmd} ${cmdArgs.join(" ")}`);
|
|
171
|
+
}
|
|
172
|
+
if (result.status !== 0) {
|
|
173
|
+
logFile(` ${options?.bestEffort ? "best-effort non-zero" : "FAILED"} (exit ${result.status}) after ${dur}s`);
|
|
174
|
+
if (options?.bestEffort)
|
|
175
|
+
return;
|
|
176
|
+
throw new Error(`Command failed (exit ${result.status}) after ${dur}s: ${cmd} ${cmdArgs.join(" ")}`);
|
|
177
|
+
}
|
|
178
|
+
logFile(` OK in ${dur}s`);
|
|
179
|
+
}
|
|
180
|
+
/** Wait until DNS resolves for a host. Returns true if recovered. */
|
|
181
|
+
function waitForDns(host, maxSec = 60) {
|
|
182
|
+
for (let elapsed = 0; elapsed < maxSec; elapsed += 5) {
|
|
183
|
+
const r = spawnSync("host", ["-W", "3", host], { stdio: "pipe", timeout: 5_000 });
|
|
184
|
+
if (r.status === 0) {
|
|
185
|
+
logFile(` DNS ${host}: recovered after ${elapsed}s`);
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
console.log(` DNS ${host} not resolving — waiting (${elapsed}s/${maxSec}s)...`);
|
|
189
|
+
spawnSync("sleep", ["5"]);
|
|
190
|
+
}
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
/** Retry a shell command with exponential backoff. Runs network diagnostics between attempts. */
|
|
194
|
+
function shellRetry(command, args, options, maxAttempts = 3, backoffSec = 20) {
|
|
195
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
196
|
+
try {
|
|
197
|
+
shell(command, args, options);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
catch (err) {
|
|
201
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
202
|
+
logFile(` Attempt ${attempt}/${maxAttempts} error: ${msg}`);
|
|
203
|
+
if (attempt === maxAttempts) {
|
|
204
|
+
console.error(` All ${maxAttempts} attempts failed.`);
|
|
205
|
+
logDiagnostics(`final failure — ${command}`);
|
|
206
|
+
if (command === "npm")
|
|
207
|
+
captureNpmDebugLog();
|
|
208
|
+
throw err;
|
|
209
|
+
}
|
|
210
|
+
const wait = backoffSec * attempt;
|
|
211
|
+
console.log(` Attempt ${attempt}/${maxAttempts} failed. Retrying in ${wait}s...`);
|
|
212
|
+
// On network errors, diagnose and wait for DNS recovery before retrying
|
|
213
|
+
if (/ETIMEDOUT|EAI_AGAIN|ECONNRESET|ENOTFOUND|ENETUNREACH/.test(msg)) {
|
|
214
|
+
console.log(" Network error — running diagnostics...");
|
|
215
|
+
logDiagnostics(`network failure — attempt ${attempt}`);
|
|
216
|
+
waitForDns("registry.npmjs.org");
|
|
217
|
+
}
|
|
218
|
+
if (command === "npm")
|
|
219
|
+
captureNpmDebugLog();
|
|
220
|
+
spawnSync("sleep", [String(wait)]);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
function commandExists(cmd) {
|
|
225
|
+
try {
|
|
226
|
+
execFileSync("which", [cmd], { stdio: "pipe" });
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
function nodeVersion() {
|
|
234
|
+
try {
|
|
235
|
+
const v = execFileSync("node", ["-v"], { encoding: "utf-8" }).trim();
|
|
236
|
+
return parseInt(v.replace("v", "").split(".")[0], 10);
|
|
237
|
+
}
|
|
238
|
+
catch {
|
|
239
|
+
return 0;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
function isLinux() {
|
|
243
|
+
return process.platform === "linux";
|
|
244
|
+
}
|
|
245
|
+
function isArm64() {
|
|
246
|
+
return process.arch === "arm64";
|
|
247
|
+
}
|
|
248
|
+
/** Check whether non-interactive sudo is available (passwordless or cached credentials). */
|
|
249
|
+
function canSudo() {
|
|
250
|
+
const result = spawnSync("sudo", ["-n", "true"], { stdio: "pipe", timeout: 5_000 });
|
|
251
|
+
return result.status === 0;
|
|
252
|
+
}
|
|
253
|
+
// Task 634 — verified-not-asserted apt-dep reconciliation.
|
|
254
|
+
// Task 637 — resolve package-name aliases before probing dpkg, so the
|
|
255
|
+
// post-install check no longer false-negatives when apt resolves a virtual
|
|
256
|
+
// name (e.g. Noble's `chromium` → `chromium-browser`).
|
|
257
|
+
// UBUNTU_ALIASES, parseOsRelease, isUbuntuLike, and decideAptResolution moved
|
|
258
|
+
// to ./apt-resolve.ts (Task 638). The pure logic lives there so a unit test
|
|
259
|
+
// can hit every branch without spawning real apt/dpkg or reading
|
|
260
|
+
// /etc/os-release. The thin wrappers below feed real spawn + fs results into
|
|
261
|
+
// that pure decision.
|
|
262
|
+
function readOsRelease() {
|
|
263
|
+
try {
|
|
264
|
+
return parseOsRelease(readFileSync("/etc/os-release", "utf-8"));
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
return {};
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/** Summarise `apt-cache policy` output for diagnostics — one token per package. */
|
|
271
|
+
function aptCachePolicySummary(pkg) {
|
|
272
|
+
const r = spawnSync("apt-cache", ["policy", pkg], { stdio: "pipe", encoding: "utf-8", timeout: 5_000 });
|
|
273
|
+
if (r.status !== 0)
|
|
274
|
+
return "policy-spawn-failed";
|
|
275
|
+
const cand = parseAptCacheCandidate(r.stdout ?? "");
|
|
276
|
+
if (cand === null)
|
|
277
|
+
return "no-candidate-line";
|
|
278
|
+
return cand === "(none)" ? "candidate-none" : `candidate=${cand}`;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Task 637 — map an apt-level package name to the concrete name dpkg will
|
|
282
|
+
* record after install. Resolution order:
|
|
283
|
+
* 1. `dpkg -s pkg` exits 0 → name is concrete and installed; return pkg.
|
|
284
|
+
* 2. `apt-cache policy pkg` reports a real Candidate → concrete-but-missing;
|
|
285
|
+
* return pkg (apt-get install + post-check will use the same name).
|
|
286
|
+
* 3. `apt-cache policy pkg` reports `Candidate: (none)` and `pkg` is in
|
|
287
|
+
* UBUNTU_ALIASES on an Ubuntu-like host → return the alias. Log the
|
|
288
|
+
* resolution so the install log answers "what did apt resolve this to?".
|
|
289
|
+
* 4. Otherwise return pkg unchanged — the post-check will throw loudly,
|
|
290
|
+
* preserving Task 634's fail-loud contract for genuinely missing packages.
|
|
291
|
+
*/
|
|
292
|
+
function resolveAptName(pkg) {
|
|
293
|
+
const dpkg = spawnSync("dpkg", ["-s", pkg], { stdio: "pipe", timeout: 5_000 });
|
|
294
|
+
const dpkgInstalled = dpkg.status === 0;
|
|
295
|
+
// Short-circuit: when dpkg already records the name as installed, skip the
|
|
296
|
+
// apt-cache + os-release work — `decideAptResolution` returns pkg unchanged
|
|
297
|
+
// anyway and the extra spawn would burn ~10 ms per already-installed
|
|
298
|
+
// package across every `pkgsMissing` pass.
|
|
299
|
+
if (dpkgInstalled)
|
|
300
|
+
return pkg;
|
|
301
|
+
const policy = spawnSync("apt-cache", ["policy", pkg], {
|
|
302
|
+
stdio: "pipe", encoding: "utf-8", timeout: 5_000,
|
|
303
|
+
});
|
|
304
|
+
const aptCandidate = policy.status === 0
|
|
305
|
+
? parseAptCacheCandidate(policy.stdout ?? "")
|
|
306
|
+
: null;
|
|
307
|
+
const os = readOsRelease();
|
|
308
|
+
const decision = decideAptResolution({
|
|
309
|
+
pkg,
|
|
310
|
+
dpkgInstalled,
|
|
311
|
+
aptCandidate,
|
|
312
|
+
ubuntuLike: isUbuntuLikePure(os),
|
|
313
|
+
distro: `${os.ID ?? "unknown"}-${os.VERSION_CODENAME ?? "unknown"}`,
|
|
314
|
+
});
|
|
315
|
+
if (decision.log)
|
|
316
|
+
logFile(decision.log);
|
|
317
|
+
return decision.resolved;
|
|
318
|
+
}
|
|
319
|
+
/** Probe runtime binary presence on PATH (independent of dpkg-recorded state). */
|
|
320
|
+
function commandVPath(pkg) {
|
|
321
|
+
const r = spawnSync("which", [pkg], { stdio: "pipe", encoding: "utf-8", timeout: 5_000 });
|
|
322
|
+
return r.status === 0 ? (r.stdout ?? "").trim() || "missing" : "missing";
|
|
323
|
+
}
|
|
324
|
+
/** Probe snap-recorded state for a name (snap lives outside dpkg). */
|
|
325
|
+
function snapStatus(pkg) {
|
|
326
|
+
const r = spawnSync("snap", ["list", pkg], { stdio: "pipe", encoding: "utf-8", timeout: 5_000 });
|
|
327
|
+
if (r.status !== 0)
|
|
328
|
+
return "none";
|
|
329
|
+
const line = (r.stdout ?? "").split("\n").find((l) => l.startsWith(pkg));
|
|
330
|
+
return line ? line.split(/\s+/).slice(0, 2).join(" ") : "none";
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Returns the subset of `pkgs` that are not currently installed, after
|
|
334
|
+
* alias resolution. Uses `dpkg -s <resolved>` per package (exit 0 = installed,
|
|
335
|
+
* any non-zero = missing) so we never parse dpkg's prose output for control
|
|
336
|
+
* flow (feedback_no_stdout_parsing_for_control_flow.md). The operator-facing
|
|
337
|
+
* name stays the original `pkg` — the resolution is logged but not renamed
|
|
338
|
+
* in the returned list, so diagnostics match what the installer declared.
|
|
339
|
+
*/
|
|
340
|
+
function pkgsMissing(pkgs) {
|
|
341
|
+
return pkgs.filter((p) => {
|
|
342
|
+
const resolved = resolveAptName(p);
|
|
343
|
+
const r = spawnSync("dpkg", ["-s", resolved], { stdio: "pipe", timeout: 5_000 });
|
|
344
|
+
return r.status !== 0;
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Install a logical group of apt packages, then verify each one landed in
|
|
349
|
+
* dpkg's installed state. The post-install `dpkg -s` probe catches the
|
|
350
|
+
* partial-failure class where `apt-get install` returns 0 but a package did
|
|
351
|
+
* not actually install — silent regressions that would otherwise only
|
|
352
|
+
* surface at runtime when the binary is missing.
|
|
353
|
+
*
|
|
354
|
+
* Resolves each package through `resolveAptName` before both the install
|
|
355
|
+
* command and the post-check, so alias packages (Task 637) pass through
|
|
356
|
+
* cleanly on distros where the apt-level name differs from the dpkg-recorded
|
|
357
|
+
* name (e.g. Noble's chromium → chromium-browser). Resolution is computed
|
|
358
|
+
* once per package and reused across install, post-check, and error
|
|
359
|
+
* diagnostics — three spawnSync chains per package, not nine on failure.
|
|
360
|
+
*/
|
|
361
|
+
function installAptGroup(label, pkgs) {
|
|
362
|
+
const pairs = pkgs.map((original) => ({ original, resolved: resolveAptName(original) }));
|
|
363
|
+
logFile(` apt install (${label}): ${pairs.map((x) => x.resolved).join(" ")}`);
|
|
364
|
+
console.log(" [privileged] apt-get install");
|
|
365
|
+
shell("apt-get", ["install", "-y", ...pairs.map((x) => x.resolved)], { sudo: true });
|
|
366
|
+
const stillMissing = pairs.filter(({ resolved }) => {
|
|
367
|
+
const r = spawnSync("dpkg", ["-s", resolved], { stdio: "pipe", timeout: 5_000 });
|
|
368
|
+
return r.status !== 0;
|
|
369
|
+
});
|
|
370
|
+
if (stillMissing.length > 0) {
|
|
371
|
+
const diag = stillMissing.map(({ original, resolved }) => `${original} (resolved-name=${resolved}, apt-cache-policy=${aptCachePolicySummary(original)}, command-v=${commandVPath(original)}, snap-status=${snapStatus(resolved)})`).join("; ");
|
|
372
|
+
throw new Error(`apt-get install (${label}) returned 0 but packages are still not installed per dpkg -s: ${diag}`);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
// ---------------------------------------------------------------------------
|
|
376
|
+
// Installation steps
|
|
377
|
+
// ---------------------------------------------------------------------------
|
|
378
|
+
const TOTAL = "11";
|
|
379
|
+
/**
|
|
380
|
+
* Task 840 — set macOS hostname via scutil. Three sequential `sudo scutil
|
|
381
|
+
* --set` calls (HostName, LocalHostName, ComputerName) — `hostnamectl` is
|
|
382
|
+
* Linux-only and `--hostname <h>` silently no-ops on darwin. All-or-nothing
|
|
383
|
+
* rollback within the 3-call batch: if any call fails, we restore the
|
|
384
|
+
* pre-batch values for the keys we already changed and re-throw. Full
|
|
385
|
+
* system-state rollback (avahi, /etc/hosts) is out of scope per the brief.
|
|
386
|
+
*/
|
|
387
|
+
function setMacosHostnameViaScutil(hostname) {
|
|
388
|
+
const keys = ["HostName", "LocalHostName", "ComputerName"];
|
|
389
|
+
// Capture pre-batch values for rollback. Empty string is a legitimate
|
|
390
|
+
// scutil --get value (the key was never set) — preserve that as "" so
|
|
391
|
+
// rollback writes empty back rather than failing.
|
|
392
|
+
const previous = {};
|
|
393
|
+
for (const k of keys) {
|
|
394
|
+
const r = spawnSync("scutil", ["--get", k], { encoding: "utf-8", stdio: "pipe", timeout: 5_000 });
|
|
395
|
+
previous[k] = (r.stdout ?? "").trim();
|
|
396
|
+
}
|
|
397
|
+
const applied = [];
|
|
398
|
+
for (const k of keys) {
|
|
399
|
+
const r = spawnSync("sudo", ["scutil", "--set", k, hostname], { encoding: "utf-8", stdio: "inherit", timeout: 30_000 });
|
|
400
|
+
if (r.status !== 0) {
|
|
401
|
+
// Roll back any keys we already changed within this batch.
|
|
402
|
+
for (const done of applied) {
|
|
403
|
+
spawnSync("sudo", ["scutil", "--set", done, previous[done] ?? ""], { stdio: "inherit", timeout: 30_000 });
|
|
404
|
+
}
|
|
405
|
+
const stderr = r.stderr ? `: ${r.stderr.toString().trim()}` : "";
|
|
406
|
+
console.error(` [scutil] ${k} failed${stderr}`);
|
|
407
|
+
throw new Error(`scutil --set ${k} ${hostname} exited ${r.status}`);
|
|
408
|
+
}
|
|
409
|
+
applied.push(k);
|
|
410
|
+
}
|
|
411
|
+
console.log(` [scutil] HostName=${hostname} LocalHostName=${hostname} ComputerName=${hostname} set ok`);
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Task 929 — detect snap-confined Chromium on Linux and install Google Chrome
|
|
415
|
+
* stable as the non-snap replacement. Runs after `installAptGroup("VNC stack")`
|
|
416
|
+
* inside `installSystemDeps`. Resolution rules live in `snap-chromium.ts`
|
|
417
|
+
* (pure decision); this wrapper does the spawnSync + apt-repo writes + post-
|
|
418
|
+
* install gate. Skipped on darwin (Maxy uses Playwright-managed Chromium per
|
|
419
|
+
* brew-install.ts) — RESOLVED_CHROMIUM_BIN keeps its `/usr/bin/chromium`
|
|
420
|
+
* default which is unused on darwin (no systemd unit).
|
|
421
|
+
*
|
|
422
|
+
* Detection: `command -v chromium` + `realpath`; an extra probe for
|
|
423
|
+
* `google-chrome-stable` covers re-run installs where a previous run already
|
|
424
|
+
* landed Chrome. Snap detection is the literal `snap` segment in the realpath
|
|
425
|
+
* (see isSnapConfinedPath in snap-chromium.ts) — covers the three real-world
|
|
426
|
+
* shapes (`/snap/bin/chromium`, `/snap/<rev>/usr/...`, `/usr/bin/snap` which
|
|
427
|
+
* is the snap launcher binary that `readlink -f` terminates at on Noble).
|
|
428
|
+
*
|
|
429
|
+
* Replacement: Google's signed apt repo (cryptographic verification via
|
|
430
|
+
* `signed-by=` GPG key) — the canonical pinned-deterministic source for
|
|
431
|
+
* Chrome stable. Pinning a specific Chrome version would require an out-of-
|
|
432
|
+
* band SHA-bump cadence and contradicts the apt-repo trust model.
|
|
433
|
+
*
|
|
434
|
+
* Post-install gate: spawn the resolved binary headless against a throwaway
|
|
435
|
+
* profile dir under persistDir, assert exit 0. The AppArmor denial that
|
|
436
|
+
* triggered Task 929 was on SingletonLock writes which `--headless=new` still
|
|
437
|
+
* attempts, so the headless probe fires the same EACCES path that production
|
|
438
|
+
* VNC headed launches do — closing the post-fix-sibling-audit-skipped gap.
|
|
439
|
+
*/
|
|
440
|
+
function ensureNonSnapChromium() {
|
|
441
|
+
if (process.platform !== "linux") {
|
|
442
|
+
logFile(` ensureNonSnapChromium skipped: platform=${process.platform}`);
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
const which = (cmd) => {
|
|
446
|
+
const r = spawnSync("command", ["-v", cmd], { stdio: "pipe", encoding: "utf-8", shell: "/bin/bash", timeout: 5_000 });
|
|
447
|
+
if (r.status !== 0)
|
|
448
|
+
return null;
|
|
449
|
+
const out = (r.stdout ?? "").trim();
|
|
450
|
+
return out || null;
|
|
451
|
+
};
|
|
452
|
+
const realpath = (path) => {
|
|
453
|
+
if (!path)
|
|
454
|
+
return null;
|
|
455
|
+
try {
|
|
456
|
+
return realpathSync(path);
|
|
457
|
+
}
|
|
458
|
+
catch {
|
|
459
|
+
return null;
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
const whichChromium = which("chromium");
|
|
463
|
+
const whichGoogleChrome = which("google-chrome-stable");
|
|
464
|
+
const decision = decideChromiumAction({
|
|
465
|
+
platform: "linux",
|
|
466
|
+
whichChromium,
|
|
467
|
+
realpathChromium: realpath(whichChromium),
|
|
468
|
+
whichGoogleChrome,
|
|
469
|
+
realpathGoogleChrome: realpath(whichGoogleChrome),
|
|
470
|
+
});
|
|
471
|
+
logFile(` [snap-chromium] decision: ${decision.action} reason="${decision.reason}"`);
|
|
472
|
+
if (decision.action === "fail") {
|
|
473
|
+
throw new Error(`ensureNonSnapChromium: ${decision.reason}. apt install of \`chromium\` ran in installAptGroup(VNC stack) above; if its post-check passed but no chromium binary is on PATH, the system PATH is misconfigured.`);
|
|
474
|
+
}
|
|
475
|
+
if (decision.action === "install-google-chrome") {
|
|
476
|
+
console.log(" Detected snap-confined Chromium — installing Google Chrome stable...");
|
|
477
|
+
logFile(` [snap-chromium] installing google-chrome-stable from Google's signed apt repo`);
|
|
478
|
+
// Fetch + dearmor the signing key, write to /etc/apt/trusted.gpg.d/. Pipe
|
|
479
|
+
// composition runs through bash -c so the curl|gpg pipeline is one
|
|
480
|
+
// privileged command rather than two separate sudo escalations.
|
|
481
|
+
console.log(" [privileged] curl + gpg --dearmor (Google Chrome signing key)");
|
|
482
|
+
shell("bash", ["-c",
|
|
483
|
+
"set -euo pipefail; " +
|
|
484
|
+
"curl -fsSL https://dl.google.com/linux/linux_signing_key.pub | " +
|
|
485
|
+
"gpg --dearmor --yes -o /etc/apt/trusted.gpg.d/google-chrome.gpg",
|
|
486
|
+
], { sudo: true });
|
|
487
|
+
// Add the apt source list with `signed-by=` scoping so the key only
|
|
488
|
+
// verifies google-chrome-* packages, not arbitrary repo overrides. arch
|
|
489
|
+
// pinned to amd64 — Google does not ship arm64 Chrome for Linux.
|
|
490
|
+
console.log(" [privileged] tee /etc/apt/sources.list.d/google-chrome.list");
|
|
491
|
+
shell("bash", ["-c",
|
|
492
|
+
"echo 'deb [arch=amd64 signed-by=/etc/apt/trusted.gpg.d/google-chrome.gpg] " +
|
|
493
|
+
"http://dl.google.com/linux/chrome/deb/ stable main' " +
|
|
494
|
+
"> /etc/apt/sources.list.d/google-chrome.list",
|
|
495
|
+
], { sudo: true });
|
|
496
|
+
console.log(" [privileged] apt-get update");
|
|
497
|
+
shell("apt-get", ["update"], { sudo: true });
|
|
498
|
+
installAptGroup("Google Chrome stable", ["google-chrome-stable"]);
|
|
499
|
+
// Re-resolve after install to capture the now-installed absolute path.
|
|
500
|
+
const postInstallWhich = which("google-chrome-stable");
|
|
501
|
+
if (!postInstallWhich) {
|
|
502
|
+
throw new Error("ensureNonSnapChromium: apt install of google-chrome-stable returned 0 and dpkg -s passed, but `command -v google-chrome-stable` is empty — PATH is broken.");
|
|
503
|
+
}
|
|
504
|
+
RESOLVED_CHROMIUM_BIN = postInstallWhich;
|
|
505
|
+
}
|
|
506
|
+
else {
|
|
507
|
+
// action === "use" — decision.resolvedPath is the existing non-snap binary
|
|
508
|
+
// (chromium or google-chrome-stable already installed).
|
|
509
|
+
if (!decision.resolvedPath) {
|
|
510
|
+
throw new Error(`ensureNonSnapChromium: action=use returned without resolvedPath — bug in snap-chromium.ts (input: chromium=${whichChromium} google-chrome=${whichGoogleChrome})`);
|
|
511
|
+
}
|
|
512
|
+
RESOLVED_CHROMIUM_BIN = decision.resolvedPath;
|
|
513
|
+
}
|
|
514
|
+
// Defensive: never persist a snap-confined path. If realpath of the resolved
|
|
515
|
+
// binary still lands under /snap/ (e.g. apt landed a snap package by mistake
|
|
516
|
+
// on a misconfigured device), throw before writeChromiumBinaryPathFile sees
|
|
517
|
+
// it — the runtime gate in vnc.sh would refuse anyway, but failing here
|
|
518
|
+
// surfaces the contract breach with the install context still in scope.
|
|
519
|
+
const finalRealpath = realpath(RESOLVED_CHROMIUM_BIN);
|
|
520
|
+
if (isSnapConfinedPath(finalRealpath)) {
|
|
521
|
+
throw new Error(`ensureNonSnapChromium: resolved Chromium binary ${RESOLVED_CHROMIUM_BIN} realpaths to ${finalRealpath} which is under /snap/ — refusing to persist.`);
|
|
522
|
+
}
|
|
523
|
+
console.log(` Chromium binary: ${RESOLVED_CHROMIUM_BIN} (realpath=${finalRealpath ?? "?"})`);
|
|
524
|
+
logFile(` [snap-chromium] resolved bin=${RESOLVED_CHROMIUM_BIN} realpath=${finalRealpath ?? "null"}`);
|
|
525
|
+
runChromiumPostInstallGate(RESOLVED_CHROMIUM_BIN);
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Task 929 post-install gate. Spawns the resolved Chromium binary headless
|
|
529
|
+
* against a throwaway profile dir under persistDir (`~/.{brand}/chromium-
|
|
530
|
+
* gate-profile/`). The AppArmor denial that triggered the task was on
|
|
531
|
+
* SingletonLock writes which `--headless=new` still attempts, so this probe
|
|
532
|
+
* fires the same EACCES path the headed VNC stack would. Cleans up the gate
|
|
533
|
+
* profile afterward — the live profile (`chromium-profile/`) is owned by
|
|
534
|
+
* vnc.sh's start_chrome and not touched here.
|
|
535
|
+
*/
|
|
536
|
+
function runChromiumPostInstallGate(chromiumBin) {
|
|
537
|
+
const persistDir = resolve(process.env.HOME ?? "/root", BRAND.configDir);
|
|
538
|
+
const gateProfileDir = join(persistDir, "chromium-gate-profile");
|
|
539
|
+
mkdirSync(gateProfileDir, { recursive: true });
|
|
540
|
+
console.log(` Verifying ${chromiumBin} can write to ${gateProfileDir} (Task 929 post-install gate)...`);
|
|
541
|
+
const r = spawnSync(chromiumBin, [
|
|
542
|
+
`--user-data-dir=${gateProfileDir}`,
|
|
543
|
+
"--headless=new",
|
|
544
|
+
"--disable-gpu",
|
|
545
|
+
"--no-sandbox",
|
|
546
|
+
"--disable-dev-shm-usage",
|
|
547
|
+
"--dump-dom",
|
|
548
|
+
"about:blank",
|
|
549
|
+
], { stdio: "pipe", encoding: "utf-8", timeout: 30_000 });
|
|
550
|
+
// Cleanup before throwing on failure so successive runs start clean.
|
|
551
|
+
try {
|
|
552
|
+
rmSync(gateProfileDir, { recursive: true, force: true });
|
|
553
|
+
}
|
|
554
|
+
catch { /* best-effort */ }
|
|
555
|
+
if (r.status !== 0) {
|
|
556
|
+
const stderr = (r.stderr ?? "").slice(-2000);
|
|
557
|
+
const eaccesHit = /Permission denied/i.test(stderr) || /EACCES/i.test(stderr);
|
|
558
|
+
const taskRef = eaccesHit
|
|
559
|
+
? "Task 929: chromium-profile not writable (likely AppArmor denial on snap-confined binary). "
|
|
560
|
+
: "";
|
|
561
|
+
throw new Error(`${taskRef}Chromium post-install gate failed: ${chromiumBin} exited ${r.status} signal=${r.signal ?? "none"}. stderr:\n${stderr}`);
|
|
562
|
+
}
|
|
563
|
+
console.log(" Chromium post-install gate passed.");
|
|
564
|
+
logFile(` [snap-chromium] post-install gate ok: ${chromiumBin} exit=0`);
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Task 929 — write the resolved Chromium absolute path to
|
|
568
|
+
* `<INSTALL_DIR>/platform/config/chromium-binary.path` so vnc.sh,
|
|
569
|
+
* writeChromiumWrapper, and setup-tunnel.sh all read the same value. Called
|
|
570
|
+
* after deployPayload so the platform/config/ directory exists. Idempotent:
|
|
571
|
+
* re-running the installer with the same RESOLVED_CHROMIUM_BIN is a no-op
|
|
572
|
+
* write (writeFileSync overwrites in place).
|
|
573
|
+
*/
|
|
574
|
+
function writeChromiumBinaryPathFile() {
|
|
575
|
+
if (process.platform !== "linux") {
|
|
576
|
+
logFile(` writeChromiumBinaryPathFile skipped: platform=${process.platform}`);
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
const configDir = resolve(INSTALL_DIR, "platform/config");
|
|
580
|
+
mkdirSync(configDir, { recursive: true });
|
|
581
|
+
const target = join(configDir, "chromium-binary.path");
|
|
582
|
+
writeFileSync(target, RESOLVED_CHROMIUM_BIN + "\n", { mode: 0o644 });
|
|
583
|
+
console.log(` Wrote ${target} → ${RESOLVED_CHROMIUM_BIN}`);
|
|
584
|
+
logFile(` [snap-chromium] wrote ${target} contents=${RESOLVED_CHROMIUM_BIN}`);
|
|
585
|
+
}
|
|
586
|
+
function installSystemDeps() {
|
|
587
|
+
log("1", TOTAL, "System dependencies and network...");
|
|
588
|
+
const platform = requireSupportedPlatform(process.platform);
|
|
589
|
+
if (platform === "darwin") {
|
|
590
|
+
// Task 840 — darwin hostname via scutil when --hostname is supplied,
|
|
591
|
+
// otherwise preserve the existing system hostname.
|
|
592
|
+
if (HOSTNAME_FLAG) {
|
|
593
|
+
console.log(` Hostname: ${HOSTNAME_FLAG} (from --hostname flag)`);
|
|
594
|
+
try {
|
|
595
|
+
setMacosHostnameViaScutil(HOSTNAME_FLAG);
|
|
596
|
+
DEVICE_HOSTNAME = HOSTNAME_FLAG;
|
|
597
|
+
}
|
|
598
|
+
catch (err) {
|
|
599
|
+
console.error(` WARNING: Failed to set hostname to '${HOSTNAME_FLAG}': ${err instanceof Error ? err.message : String(err)}`);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
else {
|
|
603
|
+
try {
|
|
604
|
+
DEVICE_HOSTNAME = execFileSync("hostname", [], { encoding: "utf-8" }).trim();
|
|
605
|
+
console.log(` Hostname: ${DEVICE_HOSTNAME} (preserved — no --hostname flag on darwin)`);
|
|
606
|
+
}
|
|
607
|
+
catch { /* fallback to brand — set at declaration */ }
|
|
608
|
+
}
|
|
609
|
+
// Task 839 — macOS has no apt analogue for the VNC/WiFi-AP stacks
|
|
610
|
+
// (kiosk display + hostapd/dnsmasq are Pi-specific per Task 836's
|
|
611
|
+
// out-of-scope note). mDNS is provided by the OS, so avahi-* drop out.
|
|
612
|
+
// Translate the remaining apt names through decideBrewResolution and
|
|
613
|
+
// let installAllBrewPackages handle the install + verify pattern.
|
|
614
|
+
const DARWIN_BASE_DEPS = ["curl", "git", "unzip", "jq", "poppler", "ffmpeg"];
|
|
615
|
+
installAllBrewPackages(DARWIN_BASE_DEPS, logFile);
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
const BASE_DEPS = ["curl", "git", "unzip", "jq", "avahi-daemon", "avahi-utils", "poppler-utils", "ffmpeg", "bind9-dnsutils"];
|
|
619
|
+
// xterm is the *preferred* terminal-emulator binary for the VNC-rendered
|
|
620
|
+
// Terminal surface (Task 632 — gnome-terminal is a D-Bus launcher that
|
|
621
|
+
// delegates window creation to the session's gnome-terminal-server,
|
|
622
|
+
// opening windows on the wrong display; xterm has no IPC layer and
|
|
623
|
+
// honours DISPLAY directly). Kept in the apt list unconditionally so
|
|
624
|
+
// vnc.sh's resolve_terminal_bin has a display-safe binary on every
|
|
625
|
+
// supported distro (Ubuntu 24.04 noble/universe, Debian 12 bookworm/main
|
|
626
|
+
// verified). xdotool (Task 632) backs the post-spawn display-membership
|
|
627
|
+
// assertion in vnc.sh check_window_on_display, closing the silent-fail
|
|
628
|
+
// class where PID is alive but no window is mapped on the target display.
|
|
629
|
+
const VNC_DEPS = ["tigervnc-standalone-server", "python3-websockify", "novnc", "xdg-utils", "chromium", "xterm", "xdotool"];
|
|
630
|
+
// Task 664 retired the ttyd/tmux admin terminal stack — upgrades run via
|
|
631
|
+
// the action runner (systemd-run --user transient units) and no longer
|
|
632
|
+
// need a shared tmux session. `tmux` was only required by the retired
|
|
633
|
+
// byte-stream terminal; removing it shrinks the apt footprint.
|
|
634
|
+
const WIFI_DEPS = ["hostapd", "dnsmasq"];
|
|
635
|
+
const ALL_APT_DEPS = [...BASE_DEPS, ...VNC_DEPS, ...WIFI_DEPS];
|
|
636
|
+
// Task 634 — verify the "deps are present" assumption with `dpkg -s` instead
|
|
637
|
+
// of asserting it (feedback_loud_failures.md). The previous silent-skip
|
|
638
|
+
// branch was benign until Task 632 added xdotool (the first new apt dep
|
|
639
|
+
// since the skip path became load-bearing on user-password-sudo devices).
|
|
640
|
+
const missing = pkgsMissing(ALL_APT_DEPS);
|
|
641
|
+
if (missing.length === 0) {
|
|
642
|
+
logFile(` all system deps present per dpkg -s (${ALL_APT_DEPS.length} packages) — skipping apt install`);
|
|
643
|
+
console.log(` All ${ALL_APT_DEPS.length} system deps already installed — skipping apt install.`);
|
|
644
|
+
}
|
|
645
|
+
else {
|
|
646
|
+
const canEscalate = canSudo() || process.stdout.isTTY === true;
|
|
647
|
+
if (!canEscalate) {
|
|
648
|
+
const repair = `sudo apt-get install -y ${missing.join(" ")}`;
|
|
649
|
+
console.error(` MISSING ${missing.length} system deps per dpkg -s: ${missing.join(" ")}`);
|
|
650
|
+
console.error(` Non-interactive sudo is unavailable; cannot prompt for password from a non-TTY shell.`);
|
|
651
|
+
console.error(` Re-run this installer from an interactive shell, or repair manually:`);
|
|
652
|
+
console.error(` ${repair}`);
|
|
653
|
+
logFile(` FAIL: missing apt deps without interactive sudo: ${missing.join(",")}`);
|
|
654
|
+
throw new Error(`installSystemDeps: missing packages (${missing.join(", ")}) and sudo is unavailable non-interactively — repair with: ${repair}`);
|
|
655
|
+
}
|
|
656
|
+
console.log(` Missing apt deps (${missing.length}): ${missing.join(", ")}`);
|
|
657
|
+
console.log(` Installing via sudo apt-get — sudo may prompt for your password...`);
|
|
658
|
+
console.log(" [privileged] apt-get update");
|
|
659
|
+
shell("apt-get", ["update"], { sudo: true });
|
|
660
|
+
installAptGroup("base utilities", BASE_DEPS);
|
|
661
|
+
installAptGroup("VNC stack", VNC_DEPS);
|
|
662
|
+
installAptGroup("WiFi AP", WIFI_DEPS);
|
|
663
|
+
}
|
|
664
|
+
// Task 929 — replace snap-confined Chromium with Google Chrome stable on
|
|
665
|
+
// Linux laptops (Ubuntu Noble) where `/usr/bin/chromium` realpaths to the
|
|
666
|
+
// snap launcher. The snap AppArmor profile denies writes to hidden top-level
|
|
667
|
+
// paths under $HOME, so any write to `~/.{brand}/chromium-profile/SingletonLock`
|
|
668
|
+
// hits EACCES and Chromium never starts the CDP listener. Always sets
|
|
669
|
+
// RESOLVED_CHROMIUM_BIN even on Pi Bookworm (where the path is unchanged),
|
|
670
|
+
// so deployPayload's writeChromiumBinaryPathFile and installService's
|
|
671
|
+
// buildMaxyUnitFile both have a real absolute path to thread through.
|
|
672
|
+
ensureNonSnapChromium();
|
|
673
|
+
// Hostname resolution — four sources, in priority order:
|
|
674
|
+
// 1. --hostname flag (unconditional — the caller is the authority)
|
|
675
|
+
// 2. OS detection on same-brand upgrade (service exists → keep whatever is currently set)
|
|
676
|
+
// 3. OS preservation on multi-brand device (another brand's service detected → keep hostname)
|
|
677
|
+
// 4. BRAND.hostname on fresh install (brand default)
|
|
678
|
+
if (HOSTNAME_FLAG) {
|
|
679
|
+
// --hostname flag: set unconditionally, no detection, no preservation logic.
|
|
680
|
+
console.log(` Hostname: ${HOSTNAME_FLAG} (from --hostname flag)`);
|
|
681
|
+
try {
|
|
682
|
+
console.log(" [privileged] hostnamectl set-hostname");
|
|
683
|
+
shell("hostnamectl", ["set-hostname", HOSTNAME_FLAG], { sudo: true });
|
|
684
|
+
console.log(" [privileged] sed -i");
|
|
685
|
+
shell("sed", ["-i", `s/127\\.0\\.1\\.1.*$/127.0.1.1\\t${HOSTNAME_FLAG}/`, "/etc/hosts"], { sudo: true });
|
|
686
|
+
try {
|
|
687
|
+
console.log(" [privileged] sed -i");
|
|
688
|
+
shell("sed", ["-i", `s/^[#]*host-name=.*/host-name=${HOSTNAME_FLAG}/`, "/etc/avahi/avahi-daemon.conf"], { sudo: true });
|
|
689
|
+
console.log(` Avahi host-name: ${HOSTNAME_FLAG} (updated avahi-daemon.conf)`);
|
|
690
|
+
}
|
|
691
|
+
catch { /* avahi-daemon.conf may not exist — non-critical */ }
|
|
692
|
+
// Restart avahi-daemon so the new hostname takes effect immediately
|
|
693
|
+
// and any stale "maxytest-2" auto-renamed records from a previous
|
|
694
|
+
// boot's hostname-conflict cycle are withdrawn. Without this,
|
|
695
|
+
// avahi-resolve -n <hostname>.local times out from the device itself
|
|
696
|
+
// because the daemon is still advertising the previous identity.
|
|
697
|
+
try {
|
|
698
|
+
console.log(" [privileged] systemctl restart avahi-daemon");
|
|
699
|
+
shell("systemctl", ["restart", "avahi-daemon"], { sudo: true });
|
|
700
|
+
}
|
|
701
|
+
catch (err) {
|
|
702
|
+
console.error(` WARNING: avahi-daemon restart failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
catch (err) {
|
|
706
|
+
console.error(` WARNING: Failed to set hostname to '${HOSTNAME_FLAG}': ${err instanceof Error ? err.message : String(err)}`);
|
|
707
|
+
}
|
|
708
|
+
DEVICE_HOSTNAME = HOSTNAME_FLAG;
|
|
709
|
+
}
|
|
710
|
+
else {
|
|
711
|
+
// No flag — fall back to detection (upgrade) or brand default (fresh install).
|
|
712
|
+
// Check for this brand's service (same-brand upgrade) and any other brand's service
|
|
713
|
+
// (multi-brand device). On a multi-brand device, hostname is a device-level concern
|
|
714
|
+
// set by the user or the first installer — subsequent brands must not overwrite it.
|
|
715
|
+
const systemdUserDir = resolve(process.env.HOME ?? "/root", ".config/systemd/user");
|
|
716
|
+
const serviceExists = existsSync(join(systemdUserDir, BRAND.serviceName));
|
|
717
|
+
// Task 690: narrow peer detection to KNOWN_BRAND_HOSTNAMES. The previous
|
|
718
|
+
// predicate ("any *.service that isn't ours") matched stray user units
|
|
719
|
+
// such as a `gnome-keyring-daemon.service -> /dev/null` disable marker,
|
|
720
|
+
// silently flipping single-brand fresh installs into the "preserve hostname"
|
|
721
|
+
// branch. Mirrors Task 683's `peerBrandPresent` allowlist in `uninstall.ts`.
|
|
722
|
+
let otherBrandService = false;
|
|
723
|
+
if (!serviceExists) {
|
|
724
|
+
const peerUnits = KNOWN_BRAND_HOSTNAMES
|
|
725
|
+
.filter((h) => h !== BRAND.hostname)
|
|
726
|
+
.flatMap((h) => [`${h}.service`, `${h}-edge.service`]);
|
|
727
|
+
try {
|
|
728
|
+
const files = new Set(readdirSync(systemdUserDir));
|
|
729
|
+
otherBrandService = peerUnits.some((unit) => files.has(unit));
|
|
730
|
+
}
|
|
731
|
+
catch { /* directory may not exist on a fresh device — not an error */ }
|
|
732
|
+
}
|
|
733
|
+
// "raspberrypi" is the Pi factory default — it means "never configured,"
|
|
734
|
+
// not "the admin chose this hostname." Treat it the same as a fresh install.
|
|
735
|
+
const FACTORY_HOSTNAMES = ["raspberrypi", "localhost"];
|
|
736
|
+
let hostnameSetAttempted = false;
|
|
737
|
+
try {
|
|
738
|
+
const currentHostname = execFileSync("hostname", [], { encoding: "utf-8" }).trim();
|
|
739
|
+
const isFactory = FACTORY_HOSTNAMES.includes(currentHostname);
|
|
740
|
+
if (currentHostname === BRAND.hostname) {
|
|
741
|
+
console.log(` Hostname: ${currentHostname} (detected from OS)`);
|
|
742
|
+
}
|
|
743
|
+
else if (serviceExists && !isFactory) {
|
|
744
|
+
// Upgrade: admin-chosen hostname — preserve it.
|
|
745
|
+
console.log(` Hostname: ${currentHostname} (detected from OS — differs from brand '${BRAND.hostname}')`);
|
|
746
|
+
}
|
|
747
|
+
else if (otherBrandService && !isFactory) {
|
|
748
|
+
// Multi-brand: another brand set the hostname — preserve it.
|
|
749
|
+
console.log(` Hostname preserved: ${currentHostname} (another brand service detected)`);
|
|
750
|
+
}
|
|
751
|
+
else {
|
|
752
|
+
// Fresh install, OR a factory default that was never changed.
|
|
753
|
+
const reason = isFactory && serviceExists
|
|
754
|
+
? `factory default '${currentHostname}' was never changed`
|
|
755
|
+
: `brand default — fresh install, was '${currentHostname}'`;
|
|
756
|
+
console.log(` Hostname: ${BRAND.hostname} (${reason})`);
|
|
757
|
+
hostnameSetAttempted = true;
|
|
758
|
+
try {
|
|
759
|
+
console.log(" [privileged] hostnamectl set-hostname");
|
|
760
|
+
shell("hostnamectl", ["set-hostname", BRAND.hostname], { sudo: true });
|
|
761
|
+
console.log(" [privileged] sed -i");
|
|
762
|
+
shell("sed", ["-i", `s/127\\.0\\.1\\.1.*$/127.0.1.1\\t${BRAND.hostname}/`, "/etc/hosts"], { sudo: true });
|
|
763
|
+
try {
|
|
764
|
+
console.log(" [privileged] sed -i");
|
|
765
|
+
shell("sed", ["-i", `s/^[#]*host-name=.*/host-name=${BRAND.hostname}/`, "/etc/avahi/avahi-daemon.conf"], { sudo: true });
|
|
766
|
+
console.log(` Avahi host-name: ${BRAND.hostname} (updated avahi-daemon.conf)`);
|
|
767
|
+
}
|
|
768
|
+
catch { /* avahi-daemon.conf may not exist — non-critical */ }
|
|
769
|
+
}
|
|
770
|
+
catch (err) {
|
|
771
|
+
console.error(` WARNING: Could not set hostname to '${BRAND.hostname}': ${err instanceof Error ? err.message : String(err)}`);
|
|
772
|
+
console.error(` After install, run: sudo hostnamectl set-hostname ${BRAND.hostname}`);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
catch (err) {
|
|
777
|
+
console.error(` WARNING: Could not detect hostname: ${err instanceof Error ? err.message : String(err)}`);
|
|
778
|
+
}
|
|
779
|
+
// Read the actual hostname after any changes — used for user-facing URLs.
|
|
780
|
+
try {
|
|
781
|
+
DEVICE_HOSTNAME = execFileSync("hostname", [], { encoding: "utf-8" }).trim();
|
|
782
|
+
}
|
|
783
|
+
catch { /* fallback to brand — set at declaration */ }
|
|
784
|
+
// If we attempted to set the hostname but it didn't take, use the brand
|
|
785
|
+
// hostname for the completion URL. The user needs the URL that will work
|
|
786
|
+
// after a reboot or manual hostname fix — not the stale factory default.
|
|
787
|
+
if (hostnameSetAttempted && DEVICE_HOSTNAME !== BRAND.hostname) {
|
|
788
|
+
console.error(` WARNING: Hostname is still '${DEVICE_HOSTNAME}' — using '${BRAND.hostname}' for the completion URL.`);
|
|
789
|
+
DEVICE_HOSTNAME = BRAND.hostname;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
// Avahi service file for LAN discovery
|
|
793
|
+
const avahiService = `<?xml version="1.0" standalone='no'?>
|
|
794
|
+
<!DOCTYPE service-group SYSTEM "avahi-service.dtd">
|
|
795
|
+
<service-group>
|
|
796
|
+
<name replace-wildcards="yes">${BRAND.productName} on %h</name>
|
|
797
|
+
<service>
|
|
798
|
+
<type>_http._tcp</type>
|
|
799
|
+
<port>${PORT}</port>
|
|
800
|
+
<txt-record>role=${BRAND.hostname}</txt-record>
|
|
801
|
+
</service>
|
|
802
|
+
</service-group>`;
|
|
803
|
+
const avahiTmpPath = `/tmp/${BRAND.hostname}-avahi.service`;
|
|
804
|
+
const avahiDestPath = `/etc/avahi/services/${BRAND.hostname}.service`;
|
|
805
|
+
try {
|
|
806
|
+
writeFileSync(avahiTmpPath, avahiService);
|
|
807
|
+
console.log(" [privileged] cp");
|
|
808
|
+
shell("cp", [avahiTmpPath, avahiDestPath], { sudo: true });
|
|
809
|
+
console.log(" [privileged] systemctl enable");
|
|
810
|
+
shell("systemctl", ["enable", "avahi-daemon"], { sudo: true });
|
|
811
|
+
console.log(" [privileged] systemctl restart");
|
|
812
|
+
shell("systemctl", ["restart", "avahi-daemon"], { sudo: true });
|
|
813
|
+
}
|
|
814
|
+
catch { /* not critical */ }
|
|
815
|
+
// Hostname collision detection: check if another device already claims this hostname.
|
|
816
|
+
// Avahi appends -2 on collision, which causes the agent to construct URLs to the wrong device.
|
|
817
|
+
try {
|
|
818
|
+
const resolveResult = spawnSync("avahi-resolve", ["-n", `${BRAND.hostname}.local`], {
|
|
819
|
+
encoding: "utf-8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"],
|
|
820
|
+
});
|
|
821
|
+
if (resolveResult.status === 0 && resolveResult.stdout.trim()) {
|
|
822
|
+
const resolvedIp = resolveResult.stdout.trim().split(/\s+/)[1];
|
|
823
|
+
// Get this device's IP addresses to compare
|
|
824
|
+
const hostnameResult = spawnSync("hostname", ["-I"], { encoding: "utf-8", timeout: 3000 });
|
|
825
|
+
const localIps = hostnameResult.stdout?.trim().split(/\s+/) ?? [];
|
|
826
|
+
if (resolvedIp && !localIps.includes(resolvedIp)) {
|
|
827
|
+
console.log(` WARNING: ${BRAND.hostname}.local already resolves to ${resolvedIp} — this device may get ${BRAND.hostname}-2.local`);
|
|
828
|
+
logFile(` WARNING: hostname collision detected — ${BRAND.hostname}.local resolves to ${resolvedIp}, local IPs: ${localIps.join(", ")}`);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
catch { /* non-critical — avahi-resolve may not be ready yet */ }
|
|
833
|
+
// Disable WiFi power management — the Pi's WiFi driver aggressively sleeps
|
|
834
|
+
// the radio between transmissions, causing dropped DNS/mDNS packets and
|
|
835
|
+
// intermittent connection resets on LAN.
|
|
836
|
+
try {
|
|
837
|
+
const nmConfDir = "/etc/NetworkManager/conf.d";
|
|
838
|
+
const nmConfFile = join(nmConfDir, `${BRAND.hostname}-no-powersave.conf`);
|
|
839
|
+
if (existsSync("/usr/bin/nmcli") && !existsSync(nmConfFile)) {
|
|
840
|
+
console.log(" Disabling WiFi power save...");
|
|
841
|
+
writeFileSync(`/tmp/${BRAND.hostname}-no-powersave.conf`, "[connection]\nwifi.powersave = 2\n");
|
|
842
|
+
console.log(" [privileged] cp");
|
|
843
|
+
shell("cp", [`/tmp/${BRAND.hostname}-no-powersave.conf`, nmConfFile], { sudo: true });
|
|
844
|
+
spawnSync("sudo", ["systemctl", "restart", "NetworkManager"], { stdio: "pipe" });
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
catch { /* not critical — wired connections unaffected */ }
|
|
848
|
+
console.log(` Device reachable at http://${DEVICE_HOSTNAME}.local:${PORT}`);
|
|
849
|
+
}
|
|
850
|
+
function installNodejs() {
|
|
851
|
+
if (commandExists("node") && nodeVersion() >= 20) {
|
|
852
|
+
log("2", TOTAL, `Node.js v${nodeVersion()} already installed.`);
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
log("2", TOTAL, "Installing Node.js...");
|
|
856
|
+
const platform = requireSupportedPlatform(process.platform);
|
|
857
|
+
if (platform === "darwin") {
|
|
858
|
+
// Pass the apt-side `nodejs` name; decideBrewResolution maps it to
|
|
859
|
+
// `node@22` per DARWIN_ALIASES so the cellar formula matches Maxy's
|
|
860
|
+
// pinned-binaries floor (Task 836).
|
|
861
|
+
installAllBrewPackages(["nodejs"], logFile);
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
spawnSync("bash", ["-c", "curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -"], { stdio: "inherit" });
|
|
865
|
+
console.log(" [privileged] apt-get install");
|
|
866
|
+
shell("apt-get", ["install", "-y", "nodejs"], { sudo: true });
|
|
867
|
+
}
|
|
868
|
+
function installClaudeCode() {
|
|
869
|
+
let needsInstall = true;
|
|
870
|
+
if (commandExists("claude")) {
|
|
871
|
+
try {
|
|
872
|
+
// `claude --version` prints "2.1.114 (Claude Code)" — extract the semver so
|
|
873
|
+
// the equality check against `npm view` (which returns bare "2.1.114") works.
|
|
874
|
+
const rawVersion = execFileSync("claude", ["--version"], { encoding: "utf-8", timeout: 10_000 }).trim();
|
|
875
|
+
const installed = rawVersion.match(/^(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)/)?.[1] ?? rawVersion;
|
|
876
|
+
let latest = null;
|
|
877
|
+
try {
|
|
878
|
+
latest = execFileSync("npm", ["view", "@anthropic-ai/claude-code", "version"], { encoding: "utf-8", timeout: 30_000 }).trim();
|
|
879
|
+
}
|
|
880
|
+
catch {
|
|
881
|
+
logFile(" Could not check latest version — will attempt install anyway");
|
|
882
|
+
}
|
|
883
|
+
if (latest && installed === latest) {
|
|
884
|
+
log("3", TOTAL, `Claude Code v${installed} already up to date.`);
|
|
885
|
+
needsInstall = false;
|
|
886
|
+
}
|
|
887
|
+
else {
|
|
888
|
+
log("3", TOTAL, `Upgrading Claude Code (${installed}${latest ? ` → ${latest}` : ""})...`);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
catch {
|
|
892
|
+
log("3", TOTAL, "Claude Code found but version check failed. Reinstalling...");
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
else {
|
|
896
|
+
log("3", TOTAL, "Installing Claude Code...");
|
|
897
|
+
}
|
|
898
|
+
if (needsInstall) {
|
|
899
|
+
// `npm install -g` needs write access to the global prefix, which on Linux is
|
|
900
|
+
// root-owned by default — so we run it under sudo. When sudo requires a password
|
|
901
|
+
// and the installer is running non-interactively (e.g. systemd-run --scope on
|
|
902
|
+
// upgrade), sudo fails instantly. Skip the upgrade in that case; the running
|
|
903
|
+
// installation is assumed adequate. Matches the apt-get skip in step 1.
|
|
904
|
+
if (isLinux() && !canSudo()) {
|
|
905
|
+
console.log(" Skipping Claude Code upgrade (sudo unavailable non-interactively — keeping installed version)");
|
|
906
|
+
}
|
|
907
|
+
else {
|
|
908
|
+
console.log(" This may take 15–30 minutes on Raspberry Pi...");
|
|
909
|
+
console.log(" [privileged] npm install -g @anthropic-ai/claude-code@latest");
|
|
910
|
+
shellRetry("npm", ["install", "-g", ...NPM_NET_FLAGS, "--loglevel", "verbose", "@anthropic-ai/claude-code@latest"], { sudo: true, timeout: 2_400_000 }, // 40 min — Pi downloads can take 25+ min
|
|
911
|
+
3, 30);
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
console.log(" Registering Claude plugin marketplaces...");
|
|
915
|
+
const marketplaceList = spawnSync("claude", ["plugin", "marketplace", "list"], { stdio: "pipe", encoding: "utf-8" });
|
|
916
|
+
if (marketplaceList.stderr)
|
|
917
|
+
process.stderr.write(marketplaceList.stderr);
|
|
918
|
+
const listed = marketplaceList.stdout ?? "";
|
|
919
|
+
// Task 976: register three Anthropic marketplaces uniformly with
|
|
920
|
+
// [plugin-marketplace] observability. Each slug is grep-checked against
|
|
921
|
+
// `marketplace list` for idempotence; failures log exit + first stderr
|
|
922
|
+
// line but do not abort the installer (one marketplace's outage must not
|
|
923
|
+
// block the others).
|
|
924
|
+
// `slug` is the GitHub repo path passed to `marketplace add`. `marketplaceName`
|
|
925
|
+
// is the marketplace's declared `name` from its .claude-plugin/marketplace.json —
|
|
926
|
+
// what `marketplace list` prints and what `<plugin>@<marketplace>` resolves on
|
|
927
|
+
// install. For FSI the two diverge: repo `anthropics/financial-services`,
|
|
928
|
+
// marketplace name `claude-for-financial-services`. The idempotence check
|
|
929
|
+
// greps the declared name to avoid substring false-positives.
|
|
930
|
+
const MARKETPLACES = [
|
|
931
|
+
{ slug: "anthropics/claude-plugins-official", marketplaceName: "claude-plugins-official" },
|
|
932
|
+
{ slug: "anthropics/knowledge-work-plugins", marketplaceName: "knowledge-work-plugins" },
|
|
933
|
+
{ slug: "anthropics/financial-services", marketplaceName: "claude-for-financial-services" },
|
|
934
|
+
];
|
|
935
|
+
for (const { slug, marketplaceName } of MARKETPLACES) {
|
|
936
|
+
if (listed.includes(marketplaceName)) {
|
|
937
|
+
logFile(`[plugin-marketplace] added ${slug} idempotent=true`);
|
|
938
|
+
continue;
|
|
939
|
+
}
|
|
940
|
+
const add = spawnSync("claude", ["plugin", "marketplace", "add", slug], { stdio: "pipe", encoding: "utf-8", timeout: 60_000 });
|
|
941
|
+
if (add.status === 0) {
|
|
942
|
+
logFile(`[plugin-marketplace] added ${slug} idempotent=false`);
|
|
943
|
+
}
|
|
944
|
+
else {
|
|
945
|
+
const stderrShort = (add.stderr ?? "").split("\n")[0]?.slice(0, 200) ?? "";
|
|
946
|
+
logFile(`[plugin-marketplace] ERROR add ${slug} exit=${add.status} stderr=${JSON.stringify(stderrShort)}`);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
console.log(" Configuring git to use HTTPS for GitHub (plugin install support)...");
|
|
950
|
+
shell("git", ["config", "--global", "url.https://github.com/.insteadOf", "git@github.com:"]);
|
|
951
|
+
// Remove the Playwright plugin if previously installed. The programmatic MCP
|
|
952
|
+
// server in claude-agent.ts (with --cdp-endpoint) is the single correct path.
|
|
953
|
+
// The plugin creates a shadow server that intermittently handles tool calls
|
|
954
|
+
// without --cdp-endpoint, causing Chrome launch failures on ARM64.
|
|
955
|
+
const pluginList = spawnSync("claude", ["plugin", "list"], { stdio: "pipe", encoding: "utf-8" });
|
|
956
|
+
if (pluginList.stdout?.includes("playwright")) {
|
|
957
|
+
console.log(" Removing Playwright plugin (replaced by programmatic CDP server)...");
|
|
958
|
+
spawnSync("claude", ["plugin", "uninstall", "playwright"], { stdio: "inherit" });
|
|
959
|
+
}
|
|
960
|
+
// Ensure @playwright/mcp and all its dependencies (including playwright-core)
|
|
961
|
+
// are cached. Wipe any stale npx cache for it first — killed installs on Pi
|
|
962
|
+
// leave corrupt temp dirs that block subsequent npm operations.
|
|
963
|
+
console.log(" Pre-caching Playwright MCP server...");
|
|
964
|
+
const npxDir = resolve(process.env.HOME ?? "/root", ".npm/_npx");
|
|
965
|
+
const findResult = spawnSync("find", [npxDir, "-path", "*/@playwright/mcp/package.json", "-print", "-quit"], {
|
|
966
|
+
encoding: "utf-8", stdio: "pipe",
|
|
967
|
+
});
|
|
968
|
+
const existingCache = findResult.stdout?.trim();
|
|
969
|
+
if (existingCache) {
|
|
970
|
+
// Nuke the entire npx cache entry to avoid ENOTEMPTY errors
|
|
971
|
+
const cacheEntry = resolve(existingCache, "../../..");
|
|
972
|
+
spawnSync("rm", ["-rf", cacheEntry], { stdio: "pipe" });
|
|
973
|
+
}
|
|
974
|
+
// Fresh install — npx creates a clean cache with all deps
|
|
975
|
+
const npxResult = spawnSync("npx", ["-y", "@playwright/mcp@latest", "--version"], {
|
|
976
|
+
stdio: "pipe",
|
|
977
|
+
timeout: 180000,
|
|
978
|
+
encoding: "utf-8",
|
|
979
|
+
});
|
|
980
|
+
// Verify playwright-core landed
|
|
981
|
+
const verifyResult = spawnSync("find", [npxDir, "-path", "*/playwright-core/package.json", "-print", "-quit"], {
|
|
982
|
+
encoding: "utf-8", stdio: "pipe",
|
|
983
|
+
});
|
|
984
|
+
if (verifyResult.stdout?.trim()) {
|
|
985
|
+
console.log(" Playwright MCP server cached with all dependencies.");
|
|
986
|
+
}
|
|
987
|
+
else {
|
|
988
|
+
console.log(` Warning: Playwright MCP cache may be incomplete (browser automation may not work).${npxResult.stderr ? ` npx stderr: ${npxResult.stderr.slice(0, 200)}` : ""}`);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
function resetNeo4jAuth(port = DEFAULT_NEO4J_PORT, dataDir = "/var/lib/neo4j") {
|
|
992
|
+
const password = randomBytes(24).toString("base64url");
|
|
993
|
+
const dedicated = port !== DEFAULT_NEO4J_PORT;
|
|
994
|
+
const serviceName = dedicated ? `neo4j-${BRAND.hostname}` : "neo4j";
|
|
995
|
+
console.log(` Resetting Neo4j auth with fresh password (${serviceName})...`);
|
|
996
|
+
spawnSync("sudo", ["systemctl", "stop", serviceName], { stdio: "inherit" });
|
|
997
|
+
// Clear the system database (stores auth/roles) and dbms auth config.
|
|
998
|
+
// The neo4j user database (graph data) is preserved.
|
|
999
|
+
// set-initial-password only works before the system DB's first start,
|
|
1000
|
+
// so we must delete it to make Neo4j treat the next start as initial.
|
|
1001
|
+
spawnSync("sudo", ["rm", "-rf",
|
|
1002
|
+
`${dataDir}/data/dbms`,
|
|
1003
|
+
`${dataDir}/data/databases/system`,
|
|
1004
|
+
`${dataDir}/data/transactions/system`,
|
|
1005
|
+
], { stdio: "inherit" });
|
|
1006
|
+
if (dedicated) {
|
|
1007
|
+
const confDir = `/etc/neo4j-${BRAND.hostname}`;
|
|
1008
|
+
// sudo env VAR=val passes the variable through sudo's env_reset
|
|
1009
|
+
spawnSync("sudo", ["env", `NEO4J_CONF=${confDir}`, "neo4j-admin", "dbms", "set-initial-password", "--", password], {
|
|
1010
|
+
stdio: "inherit",
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
1013
|
+
else {
|
|
1014
|
+
console.log(" [privileged] neo4j-admin dbms");
|
|
1015
|
+
shell("neo4j-admin", ["dbms", "set-initial-password", "--", password], { sudo: true, redact: true });
|
|
1016
|
+
}
|
|
1017
|
+
console.log(" [privileged] systemctl start");
|
|
1018
|
+
shell("systemctl", ["start", serviceName], { sudo: true });
|
|
1019
|
+
console.log(" Waiting for Neo4j to start...");
|
|
1020
|
+
for (let i = 0; i < 15; i++) {
|
|
1021
|
+
const check = spawnSync("cypher-shell", [
|
|
1022
|
+
"-u", "neo4j", "-p", password,
|
|
1023
|
+
"-a", `bolt://localhost:${port}`,
|
|
1024
|
+
"RETURN 1",
|
|
1025
|
+
], { stdio: "pipe", timeout: 5000 });
|
|
1026
|
+
if (check.status === 0)
|
|
1027
|
+
break;
|
|
1028
|
+
spawnSync("sleep", ["2"]);
|
|
1029
|
+
}
|
|
1030
|
+
return password;
|
|
1031
|
+
}
|
|
1032
|
+
/**
|
|
1033
|
+
* Task 744 — scrub plaintext neo4j passwords from pre-fix install-*.log files.
|
|
1034
|
+
* Calls platform/scripts/redact-install-logs.sh against the installer's LOG_DIR.
|
|
1035
|
+
* The script is idempotent; re-running on clean logs is a no-op. Failures here
|
|
1036
|
+
* are non-fatal — credential redaction is best-effort cleanup, not a blocker
|
|
1037
|
+
* for installation.
|
|
1038
|
+
*/
|
|
1039
|
+
function redactInstallLogs() {
|
|
1040
|
+
const script = resolve(INSTALL_DIR, "platform/scripts/redact-install-logs.sh");
|
|
1041
|
+
if (!existsSync(script)) {
|
|
1042
|
+
logFile("[redact-install-logs] script not found at " + script + " — skipping");
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
const r = spawnSync("bash", [script, "--dir", LOG_DIR], {
|
|
1046
|
+
stdio: "pipe",
|
|
1047
|
+
encoding: "utf-8",
|
|
1048
|
+
timeout: 30_000,
|
|
1049
|
+
});
|
|
1050
|
+
if (r.stdout)
|
|
1051
|
+
logFile(r.stdout.trim());
|
|
1052
|
+
if (r.status !== 0 && r.stderr)
|
|
1053
|
+
logFile("[redact-install-logs] WARN " + r.stderr.trim());
|
|
1054
|
+
}
|
|
1055
|
+
/** Check Neo4j has a working password. Called AFTER deploy so config is in place. */
|
|
1056
|
+
function ensureNeo4jPassword() {
|
|
1057
|
+
const passwordFile = join(INSTALL_DIR, "platform/config/.neo4j-password");
|
|
1058
|
+
const configDir = resolve(INSTALL_DIR, "platform/config");
|
|
1059
|
+
mkdirSync(configDir, { recursive: true });
|
|
1060
|
+
const persistDir = resolve(process.env.HOME ?? "/root", BRAND.configDir);
|
|
1061
|
+
const persistentPasswordFile = join(persistDir, ".neo4j-password");
|
|
1062
|
+
// Dedicated instances have their own auth database — password checks and resets
|
|
1063
|
+
// must target the dedicated port and data directory, not the shared instance.
|
|
1064
|
+
const dataDir = NEO4J_DEDICATED ? `/var/lib/neo4j-${BRAND.hostname}` : "/var/lib/neo4j";
|
|
1065
|
+
// 1. Same-brand check: if our own password file exists and works, done (upgrade path).
|
|
1066
|
+
if (existsSync(passwordFile)) {
|
|
1067
|
+
const existingPassword = readFileSync(passwordFile, "utf-8").trim();
|
|
1068
|
+
if (neo4jPasswordWorks(existingPassword, NEO4J_PORT)) {
|
|
1069
|
+
logFile(` Neo4j password: already configured (port ${NEO4J_PORT})`);
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
1072
|
+
if (NEO4J_DEDICATED) {
|
|
1073
|
+
console.log(" Stored password doesn't match dedicated Neo4j instance.");
|
|
1074
|
+
}
|
|
1075
|
+
else {
|
|
1076
|
+
console.log(" Stored password doesn't match Neo4j. Resetting auth.");
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
// 2. Fresh install or recovery: no working same-brand password. Generate and set a new one.
|
|
1080
|
+
// Brand isolation (Task 659): never read another brand's .neo4j-password.
|
|
1081
|
+
if (!existsSync(passwordFile)) {
|
|
1082
|
+
console.log(" No Neo4j password file found. Setting initial password...");
|
|
1083
|
+
}
|
|
1084
|
+
// Reset auth and set a new password. Graph data is preserved —
|
|
1085
|
+
// only the auth config ({dataDir}/data/dbms/) is cleared.
|
|
1086
|
+
const hasData = existsSync(`${dataDir}/data/databases/neo4j`);
|
|
1087
|
+
if (hasData) {
|
|
1088
|
+
console.log(" Neo4j has existing data. Resetting auth only (data preserved)...");
|
|
1089
|
+
logFile(` Neo4j auth reset — clearing dbms auth in ${dataDir}, preserving databases + transactions`);
|
|
1090
|
+
}
|
|
1091
|
+
const password = resetNeo4jAuth(NEO4J_PORT, dataDir);
|
|
1092
|
+
writeFileSync(passwordFile, password, { mode: 0o600 });
|
|
1093
|
+
mkdirSync(persistDir, { recursive: true });
|
|
1094
|
+
writeFileSync(persistentPasswordFile, password, { mode: 0o600 });
|
|
1095
|
+
logFile(" Neo4j password: generated new");
|
|
1096
|
+
}
|
|
1097
|
+
/** Test a Neo4j password against the running instance on the given port. */
|
|
1098
|
+
function neo4jPasswordWorks(password, port = DEFAULT_NEO4J_PORT) {
|
|
1099
|
+
const check = spawnSync("cypher-shell", [
|
|
1100
|
+
"-u", "neo4j", "-p", password,
|
|
1101
|
+
"-a", `bolt://localhost:${port}`,
|
|
1102
|
+
"RETURN 1",
|
|
1103
|
+
], { stdio: "pipe", timeout: 10000 });
|
|
1104
|
+
return check.status === 0;
|
|
1105
|
+
}
|
|
1106
|
+
function installNeo4j() {
|
|
1107
|
+
if (commandExists("neo4j")) {
|
|
1108
|
+
log("4", TOTAL, "Neo4j already installed.");
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1111
|
+
log("4", TOTAL, "Installing Neo4j Community Edition 5...");
|
|
1112
|
+
const platform = requireSupportedPlatform(process.platform);
|
|
1113
|
+
if (platform === "darwin") {
|
|
1114
|
+
// Homebrew's neo4j formula declares openjdk as a runtime dependency, so
|
|
1115
|
+
// Java is pulled in transitively — no separate openjdk install step.
|
|
1116
|
+
// Process supervision (launchd LaunchAgent) is owned by Task 838; this
|
|
1117
|
+
// step ends after `brew install neo4j` + initial-password seeding so the
|
|
1118
|
+
// payload deploy step finds the shared password file.
|
|
1119
|
+
installAllBrewPackages(["neo4j"], logFile);
|
|
1120
|
+
// Generate strong random password — stored in persistent location (~/{configDir}/)
|
|
1121
|
+
const password = randomBytes(24).toString("base64url");
|
|
1122
|
+
const persistDir = resolve(process.env.HOME ?? "/root", BRAND.configDir);
|
|
1123
|
+
mkdirSync(persistDir, { recursive: true });
|
|
1124
|
+
writeFileSync(join(persistDir, ".neo4j-password"), password, { mode: 0o600 });
|
|
1125
|
+
const configDir = resolve(INSTALL_DIR, "platform/config");
|
|
1126
|
+
mkdirSync(configDir, { recursive: true });
|
|
1127
|
+
writeFileSync(join(configDir, ".neo4j-password"), password, { mode: 0o600 });
|
|
1128
|
+
// Persist Neo4j data under ~/.maxy/neo4j-data/ per the task brief, so a
|
|
1129
|
+
// brew uninstall (which removes the cellar) does not destroy the graph.
|
|
1130
|
+
const neo4jDataDir = resolve(process.env.HOME ?? "/root", BRAND.configDir, "neo4j-data");
|
|
1131
|
+
mkdirSync(neo4jDataDir, { recursive: true });
|
|
1132
|
+
shell("neo4j-admin", ["dbms", "set-initial-password", "--", password], { redact: true });
|
|
1133
|
+
console.log(" Neo4j installed via Homebrew. Password stored securely.");
|
|
1134
|
+
return;
|
|
1135
|
+
}
|
|
1136
|
+
// Neo4j 5.x supports Java 17 and 21. Debian Bookworm ships 17, Trixie ships 21.
|
|
1137
|
+
// apt-cache policy shows "Candidate: (none)" when no installable version exists.
|
|
1138
|
+
const policyResult = spawnSync("apt-cache", ["policy", "openjdk-17-jre-headless"], { stdio: "pipe" });
|
|
1139
|
+
const policyOutput = policyResult.stdout?.toString() ?? "";
|
|
1140
|
+
const has17 = policyResult.status === 0 && !policyOutput.includes("Candidate: (none)");
|
|
1141
|
+
const javaPackage = has17 ? "openjdk-17-jre-headless" : "openjdk-21-jre-headless";
|
|
1142
|
+
console.log(` Installing Java (${javaPackage})...`);
|
|
1143
|
+
console.log(" [privileged] apt-get install");
|
|
1144
|
+
shell("apt-get", ["install", "-y", javaPackage], { sudo: true });
|
|
1145
|
+
spawnSync("bash", ["-c", "curl -fsSL https://debian.neo4j.com/neotechnology.gpg.key | sudo gpg --yes --dearmor -o /usr/share/keyrings/neo4j.gpg 2>/dev/null"], { stdio: "inherit" });
|
|
1146
|
+
spawnSync("bash", ["-c", 'echo "deb [signed-by=/usr/share/keyrings/neo4j.gpg] https://debian.neo4j.com stable 5" | sudo tee /etc/apt/sources.list.d/neo4j.list'], { stdio: "inherit" });
|
|
1147
|
+
console.log(" [privileged] apt-get update");
|
|
1148
|
+
shell("apt-get", ["update"], { sudo: true });
|
|
1149
|
+
console.log(" [privileged] apt-get install");
|
|
1150
|
+
shell("apt-get", ["install", "-y", "neo4j"], { sudo: true });
|
|
1151
|
+
console.log(" [privileged] sed -i");
|
|
1152
|
+
shell("sed", ["-i", "s/#server.default_listen_address=0.0.0.0/server.default_listen_address=127.0.0.1/", "/etc/neo4j/neo4j.conf"], { sudo: true });
|
|
1153
|
+
// Generate strong random password — stored in persistent location (~/{configDir}/)
|
|
1154
|
+
const password = randomBytes(24).toString("base64url");
|
|
1155
|
+
const persistDir = resolve(process.env.HOME ?? "/root", BRAND.configDir);
|
|
1156
|
+
mkdirSync(persistDir, { recursive: true });
|
|
1157
|
+
writeFileSync(join(persistDir, ".neo4j-password"), password, { mode: 0o600 });
|
|
1158
|
+
// Also write to install dir (will be there when deploy step runs)
|
|
1159
|
+
const configDir = resolve(INSTALL_DIR, "platform/config");
|
|
1160
|
+
mkdirSync(configDir, { recursive: true });
|
|
1161
|
+
writeFileSync(join(configDir, ".neo4j-password"), password, { mode: 0o600 });
|
|
1162
|
+
console.log(" [privileged] neo4j-admin dbms");
|
|
1163
|
+
shell("neo4j-admin", ["dbms", "set-initial-password", "--", password], { sudo: true, redact: true });
|
|
1164
|
+
console.log(" [privileged] systemctl enable");
|
|
1165
|
+
shell("systemctl", ["enable", "neo4j"], { sudo: true });
|
|
1166
|
+
console.log(" [privileged] systemctl start");
|
|
1167
|
+
shell("systemctl", ["start", "neo4j"], { sudo: true });
|
|
1168
|
+
console.log(" Neo4j started. Password stored securely.");
|
|
1169
|
+
}
|
|
1170
|
+
/**
|
|
1171
|
+
* Task 800 — does any peer brand on this host pin `NEO4J_URI=bolt://localhost:<default>`
|
|
1172
|
+
* in its `.env`? If yes, the apt-installed `neo4j.service` is its database and the
|
|
1173
|
+
* dedicated-unit installer must NOT stop+disable it. Returns the first matching
|
|
1174
|
+
* peer's hostname, or `null` when no peer pins the default port.
|
|
1175
|
+
*
|
|
1176
|
+
* Wraps the pure decision in `peer-brand-detect.ts` with the fs reads of
|
|
1177
|
+
* `~/.<peer>/.env`. Bias on read errors: if `.env` exists but is unreadable
|
|
1178
|
+
* (permissions, transient I/O), the wrapper treats that peer as a *potential*
|
|
1179
|
+
* dependency and short-circuits to the kept-active path. Disabling the system
|
|
1180
|
+
* unit on faulty evidence would silently kill the peer's database (the exact
|
|
1181
|
+
* failure Task 800 prevents); a conservatively-skipped disable is recoverable
|
|
1182
|
+
* because the dedicated-unit bind check at the end of `setupDedicatedNeo4j`
|
|
1183
|
+
* fails loud if the system unit is actually free.
|
|
1184
|
+
*/
|
|
1185
|
+
function peerBrandUsingSystemUnit() {
|
|
1186
|
+
const home = process.env.HOME ?? "/root";
|
|
1187
|
+
const peerEnvContents = [];
|
|
1188
|
+
for (const hostname of KNOWN_BRAND_HOSTNAMES) {
|
|
1189
|
+
if (hostname === BRAND.hostname) {
|
|
1190
|
+
peerEnvContents.push([hostname, null]);
|
|
1191
|
+
continue;
|
|
1192
|
+
}
|
|
1193
|
+
const envPath = resolve(home, `.${hostname}`, ".env");
|
|
1194
|
+
if (!existsSync(envPath)) {
|
|
1195
|
+
peerEnvContents.push([hostname, null]);
|
|
1196
|
+
continue;
|
|
1197
|
+
}
|
|
1198
|
+
try {
|
|
1199
|
+
peerEnvContents.push([hostname, readFileSync(envPath, "utf-8")]);
|
|
1200
|
+
}
|
|
1201
|
+
catch (err) {
|
|
1202
|
+
console.error(` WARNING: unable to read peer brand .env at ${envPath} — treating as potential dependency to avoid data loss: ${err instanceof Error ? err.message : String(err)}`);
|
|
1203
|
+
return hostname;
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
return findPeerBrandOnDefaultNeo4jPort({
|
|
1207
|
+
currentBrandHostname: BRAND.hostname,
|
|
1208
|
+
defaultNeo4jPort: DEFAULT_NEO4J_PORT,
|
|
1209
|
+
peerEnvContents,
|
|
1210
|
+
});
|
|
1211
|
+
}
|
|
1212
|
+
/**
|
|
1213
|
+
* Create a dedicated Neo4j instance for this brand when NEO4J_DEDICATED is true.
|
|
1214
|
+
* Produces: separate config dir, data dir, log dir, systemd service, and password.
|
|
1215
|
+
* On upgrade (config already exists), skips conf creation — but always runs the
|
|
1216
|
+
* Task 787 state-remediation block (stop/disable system unit, reset-failed
|
|
1217
|
+
* dedicated, start, verify) so a half-installed Pi recovers in-place without
|
|
1218
|
+
* manual systemctl. ensureNeo4jPassword() handles password verification on the
|
|
1219
|
+
* recovery path.
|
|
1220
|
+
*
|
|
1221
|
+
* Task 800: on multi-brand hosts where a peer brand still depends on the apt
|
|
1222
|
+
* `neo4j.service` (port 7687), the stop+disable step is skipped — disabling
|
|
1223
|
+
* the system unit would kill the peer's database.
|
|
1224
|
+
*/
|
|
1225
|
+
function setupDedicatedNeo4j() {
|
|
1226
|
+
if (!NEO4J_DEDICATED)
|
|
1227
|
+
return;
|
|
1228
|
+
const brandSuffix = BRAND.hostname; // e.g., "realagent"
|
|
1229
|
+
const confDir = `/etc/neo4j-${brandSuffix}`;
|
|
1230
|
+
const dataDir = `/var/lib/neo4j-${brandSuffix}`;
|
|
1231
|
+
const logDir = `/var/log/neo4j-${brandSuffix}`;
|
|
1232
|
+
const serviceName = `neo4j-${brandSuffix}`;
|
|
1233
|
+
const httpPort = NEO4J_PORT - 213; // Preserve standard 7687/7474 offset
|
|
1234
|
+
// Per-brand state (sed, mkdir, chown, unit-write) is idempotent and runs on
|
|
1235
|
+
// every install. Only the base-config copy and initial-password rotation are
|
|
1236
|
+
// gated to first install — Task 787 established that state remediation must
|
|
1237
|
+
// run every install; Task 979 extended the same principle to the conf and
|
|
1238
|
+
// unit emission so a half-installed host (broken unit missing NEO4J_HOME)
|
|
1239
|
+
// recovers on retry without an out-of-band manual reset.
|
|
1240
|
+
const confExists = spawnSync("test", ["-f", `${confDir}/neo4j.conf`], { stdio: "pipe" }).status === 0;
|
|
1241
|
+
if (confExists) {
|
|
1242
|
+
console.log(` Dedicated Neo4j instance for ${BRAND.productName} already configured at ${confDir} — re-applying per-brand state`);
|
|
1243
|
+
logFile(` Neo4j dedicated: existing config at ${confDir}, re-applying sed/mkdir/chown/unit on every install`);
|
|
1244
|
+
}
|
|
1245
|
+
else {
|
|
1246
|
+
console.log(` Setting up dedicated Neo4j instance for ${BRAND.productName} on bolt://localhost:${NEO4J_PORT}...`);
|
|
1247
|
+
// Pre-check: neo4j user must exist (created by the apt package)
|
|
1248
|
+
const neo4jUserCheck = spawnSync("id", ["neo4j"], { stdio: "pipe" });
|
|
1249
|
+
if (neo4jUserCheck.status !== 0) {
|
|
1250
|
+
throw new Error("Neo4j system user 'neo4j' not found. Is Neo4j installed via apt?");
|
|
1251
|
+
}
|
|
1252
|
+
// Pre-check: source config must exist
|
|
1253
|
+
if (!existsSync("/etc/neo4j/neo4j.conf")) {
|
|
1254
|
+
throw new Error("/etc/neo4j/neo4j.conf not found. Cannot create dedicated instance without base config.");
|
|
1255
|
+
}
|
|
1256
|
+
// Copy base config (first install only — sed runs unconditionally below)
|
|
1257
|
+
console.log(" [privileged] cp -r");
|
|
1258
|
+
shell("cp", ["-r", "/etc/neo4j", confDir], { sudo: true });
|
|
1259
|
+
}
|
|
1260
|
+
// Idempotent per-brand state — runs on every install (Task 979).
|
|
1261
|
+
// sed `s|^#?key=.*|key=value|` no-ops when the file already has the target
|
|
1262
|
+
// value; mkdir -p, chown -R, and unit-write are idempotent by definition.
|
|
1263
|
+
// NEO4J_HOME=${dataDir} makes server.directories.run resolve per-brand to
|
|
1264
|
+
// ${dataDir}/run, so the launcher's pre-flight does not collide with the
|
|
1265
|
+
// system unit's /var/lib/neo4j/run/neo4j.pid (Task 979 root cause). Plugins
|
|
1266
|
+
// and import are sed-overridden because the apt base conf pins them to
|
|
1267
|
+
// absolute /var/lib/neo4j/plugins and /var/lib/neo4j/import (shared).
|
|
1268
|
+
console.log(" [privileged] sed -i");
|
|
1269
|
+
shell("sed", ["-i", `s/^#\\?server\\.bolt\\.listen_address=.*/server.bolt.listen_address=:${NEO4J_PORT}/`, `${confDir}/neo4j.conf`], { sudo: true });
|
|
1270
|
+
console.log(" [privileged] sed -i");
|
|
1271
|
+
shell("sed", ["-i", `s/^#\\?server\\.http\\.listen_address=.*/server.http.listen_address=:${httpPort}/`, `${confDir}/neo4j.conf`], { sudo: true });
|
|
1272
|
+
console.log(" [privileged] sed -i");
|
|
1273
|
+
shell("sed", ["-i", `s|^#\\?server\\.directories\\.data=.*|server.directories.data=${dataDir}/data|`, `${confDir}/neo4j.conf`], { sudo: true });
|
|
1274
|
+
console.log(" [privileged] sed -i");
|
|
1275
|
+
shell("sed", ["-i", `s|^#\\?server\\.directories\\.logs=.*|server.directories.logs=${logDir}|`, `${confDir}/neo4j.conf`], { sudo: true });
|
|
1276
|
+
console.log(" [privileged] sed -i");
|
|
1277
|
+
shell("sed", ["-i", `s|^#\\?server\\.directories\\.plugins=.*|server.directories.plugins=${dataDir}/plugins|`, `${confDir}/neo4j.conf`], { sudo: true });
|
|
1278
|
+
console.log(" [privileged] sed -i");
|
|
1279
|
+
shell("sed", ["-i", `s|^#\\?server\\.directories\\.import=.*|server.directories.import=${dataDir}/import|`, `${confDir}/neo4j.conf`], { sudo: true });
|
|
1280
|
+
// Verify config was updated — sed silently no-ops if the key format changed
|
|
1281
|
+
const confContent = spawnSync("grep", [`server.bolt.listen_address=:${NEO4J_PORT}`, `${confDir}/neo4j.conf`], { stdio: "pipe" });
|
|
1282
|
+
if (confContent.status !== 0) {
|
|
1283
|
+
console.error(` WARNING: neo4j.conf may not have been updated correctly — bolt port ${NEO4J_PORT} not found in config`);
|
|
1284
|
+
logFile(` WARNING: sed verification failed — bolt port ${NEO4J_PORT} not found in ${confDir}/neo4j.conf`);
|
|
1285
|
+
}
|
|
1286
|
+
console.log(" [privileged] mkdir -p");
|
|
1287
|
+
shell("mkdir", ["-p", `${dataDir}/data`, `${dataDir}/plugins`, `${dataDir}/import`, logDir], { sudo: true });
|
|
1288
|
+
console.log(" [privileged] chown -R");
|
|
1289
|
+
shell("chown", ["-R", "neo4j:neo4j", dataDir, logDir, confDir], { sudo: true });
|
|
1290
|
+
const serviceContent = `[Unit]
|
|
1291
|
+
Description=Neo4j Graph Database (${BRAND.productName})
|
|
1292
|
+
After=network-online.target
|
|
1293
|
+
Wants=network-online.target
|
|
1294
|
+
|
|
1295
|
+
[Service]
|
|
1296
|
+
ExecStart=/usr/bin/neo4j console
|
|
1297
|
+
Restart=on-failure
|
|
1298
|
+
User=neo4j
|
|
1299
|
+
Group=neo4j
|
|
1300
|
+
Environment="NEO4J_CONF=${confDir}" "NEO4J_HOME=${dataDir}"
|
|
1301
|
+
LimitNOFILE=60000
|
|
1302
|
+
|
|
1303
|
+
[Install]
|
|
1304
|
+
WantedBy=multi-user.target
|
|
1305
|
+
`;
|
|
1306
|
+
const tmpServicePath = `/tmp/${serviceName}.service`;
|
|
1307
|
+
writeFileSync(tmpServicePath, serviceContent);
|
|
1308
|
+
console.log(" [privileged] cp");
|
|
1309
|
+
shell("cp", [tmpServicePath, `/etc/systemd/system/${serviceName}.service`], { sudo: true });
|
|
1310
|
+
spawnSync("rm", ["-f", tmpServicePath]);
|
|
1311
|
+
logFile(` [neo4j] dedicated unit env: NEO4J_CONF=${confDir} NEO4J_HOME=${dataDir}`);
|
|
1312
|
+
if (!confExists) {
|
|
1313
|
+
// Set initial password before first start (first install only — rotation
|
|
1314
|
+
// on retry would brick an existing DB whose password is already stored).
|
|
1315
|
+
const password = randomBytes(24).toString("base64url");
|
|
1316
|
+
const persistDir = resolve(process.env.HOME ?? "/root", BRAND.configDir);
|
|
1317
|
+
mkdirSync(persistDir, { recursive: true });
|
|
1318
|
+
writeFileSync(join(persistDir, ".neo4j-password"), password, { mode: 0o600 });
|
|
1319
|
+
const configDir = resolve(INSTALL_DIR, "platform/config");
|
|
1320
|
+
mkdirSync(configDir, { recursive: true });
|
|
1321
|
+
writeFileSync(join(configDir, ".neo4j-password"), password, { mode: 0o600 });
|
|
1322
|
+
// sudo env VAR=val passes NEO4J_CONF through sudo's env_reset policy
|
|
1323
|
+
spawnSync("sudo", ["env", `NEO4J_CONF=${confDir}`, "neo4j-admin", "dbms", "set-initial-password", "--", password], {
|
|
1324
|
+
stdio: "inherit",
|
|
1325
|
+
});
|
|
1326
|
+
}
|
|
1327
|
+
// ============================================================================
|
|
1328
|
+
// Task 787 — unified state remediation + start + verify.
|
|
1329
|
+
//
|
|
1330
|
+
// Runs on both fresh and recovery paths. The dedicated unit and the apt
|
|
1331
|
+
// package's system unit both exec /usr/bin/neo4j console without overriding
|
|
1332
|
+
// NEO4J_HOME, so server.directories.run resolves to /var/lib/neo4j/run for
|
|
1333
|
+
// both — the launcher refuses with "Neo4j is already running (pid:N)" if
|
|
1334
|
+
// the system unit holds the PID file. Stopping the system unit first frees
|
|
1335
|
+
// the run-state; disabling prevents it returning at next boot. reset-failed
|
|
1336
|
+
// clears any prior start-limit-hit from a half-installed Pi.
|
|
1337
|
+
// ============================================================================
|
|
1338
|
+
spawnSync("sudo", ["systemctl", "daemon-reload"], { stdio: "inherit" });
|
|
1339
|
+
console.log(" [privileged] systemctl enable");
|
|
1340
|
+
shell("systemctl", ["enable", serviceName], { sudo: true });
|
|
1341
|
+
// Task 800: skip stop+disable when a peer brand on this host still depends
|
|
1342
|
+
// on the apt `neo4j.service` (port 7687). Disabling it would kill the peer's
|
|
1343
|
+
// database — Task 797 reproducer on Neo's laptop. The kept-active path is
|
|
1344
|
+
// mutually exclusive with the disable path: exactly one log line per install.
|
|
1345
|
+
const peerOnSystemUnit = peerBrandUsingSystemUnit();
|
|
1346
|
+
if (peerOnSystemUnit !== null) {
|
|
1347
|
+
const keptActiveMsg = ` [neo4j] system unit kept active — peer brand ${peerOnSystemUnit} depends on port ${DEFAULT_NEO4J_PORT}`;
|
|
1348
|
+
console.log(keptActiveMsg);
|
|
1349
|
+
logFile(keptActiveMsg);
|
|
1350
|
+
}
|
|
1351
|
+
else {
|
|
1352
|
+
console.log(` [neo4j] disabling system unit (brand-dedicated active on port ${NEO4J_PORT})`);
|
|
1353
|
+
logFile(` [neo4j] disabling system unit (brand-dedicated active on port ${NEO4J_PORT})`);
|
|
1354
|
+
shell("systemctl", ["stop", "neo4j"], { sudo: true });
|
|
1355
|
+
shell("systemctl", ["disable", "neo4j"], { sudo: true });
|
|
1356
|
+
}
|
|
1357
|
+
console.log(` [neo4j] reset-failed ${serviceName} before start`);
|
|
1358
|
+
logFile(` [neo4j] reset-failed ${serviceName} before start`);
|
|
1359
|
+
shell("systemctl", ["reset-failed", serviceName], { sudo: true, bestEffort: true });
|
|
1360
|
+
console.log(" [privileged] systemctl start");
|
|
1361
|
+
shell("systemctl", ["start", serviceName], { sudo: true });
|
|
1362
|
+
// Verify the dedicated unit bound its port. Password verification is
|
|
1363
|
+
// ensureNeo4jPassword()'s job (called next in the install pipeline) — that
|
|
1364
|
+
// function tests the stored password against this port and resets auth if
|
|
1365
|
+
// the password no longer matches the running instance.
|
|
1366
|
+
console.log(` Waiting for dedicated Neo4j instance on port ${NEO4J_PORT}...`);
|
|
1367
|
+
let listening = false;
|
|
1368
|
+
const portMatch = new RegExp(`:${NEO4J_PORT}\\b`);
|
|
1369
|
+
for (let i = 0; i < 15; i++) {
|
|
1370
|
+
const portCheck = spawnSync("ss", ["-tln"], { stdio: "pipe", timeout: 5000 });
|
|
1371
|
+
if (portMatch.test(portCheck.stdout?.toString() ?? "")) {
|
|
1372
|
+
listening = true;
|
|
1373
|
+
break;
|
|
1374
|
+
}
|
|
1375
|
+
spawnSync("sleep", ["2"]);
|
|
1376
|
+
}
|
|
1377
|
+
if (!listening) {
|
|
1378
|
+
// Loud failure — no silent fallback to the system instance (Task 787).
|
|
1379
|
+
const portCheck = spawnSync("ss", ["-tlnp"], { stdio: "pipe", timeout: 5000 });
|
|
1380
|
+
const portLines = (portCheck.stdout?.toString() ?? "").split("\n").filter((l) => l.includes(String(NEO4J_PORT)));
|
|
1381
|
+
const diagnostic = portLines.length > 0 ? portLines.join("; ") : "nothing listening on port";
|
|
1382
|
+
const journal = spawnSync("journalctl", ["-u", serviceName, "--since", "5 min ago"], { stdio: "pipe", timeout: 5000 });
|
|
1383
|
+
const journalTail = (journal.stdout?.toString() ?? "").split("\n").slice(-20).join("\n");
|
|
1384
|
+
logFile(` Neo4j dedicated: failed to bind port ${NEO4J_PORT} — ${diagnostic}`);
|
|
1385
|
+
throw new Error(`Dedicated Neo4j instance ${serviceName} did not bind bolt://localhost:${NEO4J_PORT} within 30s.\n` +
|
|
1386
|
+
`Port ${NEO4J_PORT}: ${diagnostic}\n` +
|
|
1387
|
+
`journalctl -u ${serviceName} --since "5 min ago" | tail -20:\n${journalTail}`);
|
|
1388
|
+
}
|
|
1389
|
+
logFile(` Neo4j dedicated: config=${confDir} data=${dataDir} service=${serviceName} bolt=:${NEO4J_PORT} http=:${httpPort}`);
|
|
1390
|
+
console.log(` Dedicated Neo4j instance ready on bolt://localhost:${NEO4J_PORT}`);
|
|
1391
|
+
}
|
|
1392
|
+
function ensureOllamaServing() {
|
|
1393
|
+
const check = () => spawnSync("curl", ["-sf", "http://localhost:11434/api/tags"], {
|
|
1394
|
+
stdio: "pipe", timeout: 5_000,
|
|
1395
|
+
});
|
|
1396
|
+
if (check().status === 0)
|
|
1397
|
+
return;
|
|
1398
|
+
// Log which ollama binary we're using and its version
|
|
1399
|
+
const which = spawnSync("which", ["ollama"], { stdio: "pipe" });
|
|
1400
|
+
const ollamaPath = which.stdout?.toString().trim() || "not found";
|
|
1401
|
+
logFile(` Ollama binary: ${ollamaPath}`);
|
|
1402
|
+
const version = spawnSync("ollama", ["--version"], { stdio: "pipe", timeout: 5_000 });
|
|
1403
|
+
logFile(` Ollama version: ${version.stdout?.toString().trim() || version.stderr?.toString().trim() || `exit ${version.status}`}`);
|
|
1404
|
+
// Check if systemd service exists
|
|
1405
|
+
const svcCheck = spawnSync("systemctl", ["cat", "ollama"], { stdio: "pipe", timeout: 5_000 });
|
|
1406
|
+
logFile(` Ollama systemd service: ${svcCheck.status === 0 ? "exists" : "not found"}`);
|
|
1407
|
+
// Check if port 11434 is already in use by something else
|
|
1408
|
+
const portCheck = spawnSync("ss", ["-tlnp"], { stdio: "pipe", timeout: 5_000 });
|
|
1409
|
+
const portLines = portCheck.stdout?.toString().split("\n").filter((l) => l.includes("11434")) || [];
|
|
1410
|
+
if (portLines.length > 0)
|
|
1411
|
+
logFile(` Port 11434 in use: ${portLines.join("; ")}`);
|
|
1412
|
+
console.log(" Starting Ollama server...");
|
|
1413
|
+
logFile(" Ollama server not responding — starting ollama serve");
|
|
1414
|
+
// Capture ollama serve output to a log file for diagnostics
|
|
1415
|
+
const ollamaLog = join(LOG_DIR, "ollama-serve.log");
|
|
1416
|
+
const logFd = openSync(ollamaLog, "a");
|
|
1417
|
+
const child = spawn("ollama", ["serve"], {
|
|
1418
|
+
stdio: ["ignore", logFd, logFd], detached: true,
|
|
1419
|
+
});
|
|
1420
|
+
child.unref();
|
|
1421
|
+
closeSync(logFd);
|
|
1422
|
+
logFile(` Spawned ollama serve (PID ${child.pid}), log: ${ollamaLog}`);
|
|
1423
|
+
for (let elapsed = 0; elapsed < 30; elapsed += 3) {
|
|
1424
|
+
spawnSync("sleep", ["3"]);
|
|
1425
|
+
// Check if process is still alive
|
|
1426
|
+
const alive = child.pid && spawnSync("kill", ["-0", String(child.pid)], { stdio: "pipe" }).status === 0;
|
|
1427
|
+
logFile(` Poll ${elapsed + 3}s: pid ${child.pid} alive=${alive}`);
|
|
1428
|
+
if (check().status === 0) {
|
|
1429
|
+
logFile(` Ollama server ready after ${elapsed + 3}s`);
|
|
1430
|
+
return;
|
|
1431
|
+
}
|
|
1432
|
+
if (!alive) {
|
|
1433
|
+
// Process died — read the log to find out why
|
|
1434
|
+
const serveLog = readFileSync(ollamaLog, "utf-8").slice(-2000);
|
|
1435
|
+
logFile(` Ollama serve exited. Log tail:\n${serveLog}`);
|
|
1436
|
+
console.error(` Ollama serve exited unexpectedly. Log: ${ollamaLog}`);
|
|
1437
|
+
throw new Error(`Ollama serve failed to start. Log: ${ollamaLog}\n${serveLog}`);
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
// Timed out — include diagnostics
|
|
1441
|
+
const serveLog = readFileSync(ollamaLog, "utf-8").slice(-2000);
|
|
1442
|
+
logFile(` Ollama serve timed out after 30s. Log tail:\n${serveLog}`);
|
|
1443
|
+
throw new Error(`Ollama server did not respond within 30s. Log: ${ollamaLog}\n${serveLog}`);
|
|
1444
|
+
}
|
|
1445
|
+
function installOllama(embedModel) {
|
|
1446
|
+
if (!commandExists("ollama")) {
|
|
1447
|
+
log("5", TOTAL, "Installing Ollama...");
|
|
1448
|
+
spawnSync("bash", ["-c", "curl -fsSL https://ollama.ai/install.sh | sh"], { stdio: "inherit" });
|
|
1449
|
+
}
|
|
1450
|
+
else {
|
|
1451
|
+
log("5", TOTAL, "Ollama already installed.");
|
|
1452
|
+
}
|
|
1453
|
+
ensureOllamaServing();
|
|
1454
|
+
console.log(` Pulling ${embedModel} embedding model...`);
|
|
1455
|
+
logFile(` Pulling embedding model: ${embedModel}`);
|
|
1456
|
+
shellRetry("ollama", ["pull", embedModel], { timeout: 600_000 }, 3, 15);
|
|
1457
|
+
}
|
|
1458
|
+
// Task 557 — uv/uvx bootstrap. Required by the `graph` MCP server which
|
|
1459
|
+
// spawns `uvx mcp-neo4j-cypher@0.6.0 --transport stdio`. Idempotent: when
|
|
1460
|
+
// uvx is already on PATH (or under $HOME/.local/bin) we skip. Non-fatal on
|
|
1461
|
+
// failure — the installer continues; the graph server will loudly fail
|
|
1462
|
+
// at session start with a clear "uvx not found" error, which is retriable
|
|
1463
|
+
// via a second installer run with network access.
|
|
1464
|
+
function installUv() {
|
|
1465
|
+
if (commandExists("uvx")) {
|
|
1466
|
+
logFile(" uv: already installed");
|
|
1467
|
+
console.log(" uv/uvx already installed.");
|
|
1468
|
+
return;
|
|
1469
|
+
}
|
|
1470
|
+
console.log(" Installing uv (Python tool runner — required by Neo4j MCP server)...");
|
|
1471
|
+
logFile(" uv: installing via astral.sh installer");
|
|
1472
|
+
// astral.sh installer auto-confirms when stdin is not a TTY (our case under
|
|
1473
|
+
// systemd-run). Historically we passed `-y`, which the script rejects with
|
|
1474
|
+
// "unknown option -y" and causes uv to never install on upgrade.
|
|
1475
|
+
const result = spawnSync("bash", ["-c", "curl -LsSf https://astral.sh/uv/install.sh | sh"], { stdio: "inherit" });
|
|
1476
|
+
if (result.status !== 0) {
|
|
1477
|
+
console.error(` WARNING: uv install exited ${result.status} — graph MCP server will fail at session start until this is retried`);
|
|
1478
|
+
logFile(` WARNING: uv install failed with status ${result.status}`);
|
|
1479
|
+
return;
|
|
1480
|
+
}
|
|
1481
|
+
// The installer writes uvx to $HOME/.local/bin — add it to PATH for the
|
|
1482
|
+
// remainder of this install so commandExists("uvx") works downstream.
|
|
1483
|
+
const localBin = `${process.env.HOME}/.local/bin`;
|
|
1484
|
+
if (process.env.PATH && !process.env.PATH.includes(localBin)) {
|
|
1485
|
+
process.env.PATH = `${localBin}:${process.env.PATH}`;
|
|
1486
|
+
}
|
|
1487
|
+
if (commandExists("uvx")) {
|
|
1488
|
+
logFile(" uv: install succeeded; uvx on PATH");
|
|
1489
|
+
}
|
|
1490
|
+
else {
|
|
1491
|
+
console.error(" WARNING: uv installed but uvx not on PATH — check $HOME/.local/bin");
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
function installCloudflared() {
|
|
1495
|
+
if (commandExists("cloudflared")) {
|
|
1496
|
+
log("6", TOTAL, "Cloudflared already installed.");
|
|
1497
|
+
return;
|
|
1498
|
+
}
|
|
1499
|
+
log("6", TOTAL, "Installing cloudflared...");
|
|
1500
|
+
const platform = requireSupportedPlatform(process.platform);
|
|
1501
|
+
if (platform === "darwin") {
|
|
1502
|
+
// Homebrew's `cloudflared` formula tracks the Cloudflare release stream;
|
|
1503
|
+
// tunnel-login flow stays unchanged (`feedback_no_api_token_route.md`).
|
|
1504
|
+
// `cloudflared service install` is invoked by the launchd supervisor in
|
|
1505
|
+
// Task 838 — out of scope here.
|
|
1506
|
+
installAllBrewPackages(["cloudflared"], logFile);
|
|
1507
|
+
return;
|
|
1508
|
+
}
|
|
1509
|
+
const arch = isArm64() ? "arm64" : "amd64";
|
|
1510
|
+
const debPath = "/tmp/cloudflared.deb";
|
|
1511
|
+
shellRetry("curl", ["-fSL", "--progress-bar", `https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-${arch}.deb`, "-o", debPath], { timeout: 120_000 }, 3, 10);
|
|
1512
|
+
console.log(" [privileged] dpkg -i");
|
|
1513
|
+
shell("dpkg", ["-i", debPath], { sudo: true });
|
|
1514
|
+
spawnSync("rm", ["-f", debPath]);
|
|
1515
|
+
}
|
|
1516
|
+
function installWhisperCpp() {
|
|
1517
|
+
const WHISPER_DIR = "/opt/whisper.cpp";
|
|
1518
|
+
const WHISPER_BINARY = join(WHISPER_DIR, "build/bin/whisper-cli");
|
|
1519
|
+
const WHISPER_MODEL = join(WHISPER_DIR, "models", "ggml-base.bin");
|
|
1520
|
+
if (existsSync(WHISPER_BINARY) && existsSync(WHISPER_MODEL)) {
|
|
1521
|
+
log("7", TOTAL, "whisper.cpp already installed.");
|
|
1522
|
+
return;
|
|
1523
|
+
}
|
|
1524
|
+
log("7", TOTAL, "Installing whisper.cpp (speech-to-text)...");
|
|
1525
|
+
if (!isLinux()) {
|
|
1526
|
+
console.log(" Skipping — install manually for your platform.");
|
|
1527
|
+
return;
|
|
1528
|
+
}
|
|
1529
|
+
// Build dependencies — cmake is required since whisper.cpp migrated from plain make
|
|
1530
|
+
console.log(" [privileged] apt-get install");
|
|
1531
|
+
shell("apt-get", ["install", "-y", "build-essential", "cmake"], { sudo: true });
|
|
1532
|
+
// Clone or update the repository
|
|
1533
|
+
if (!existsSync(WHISPER_DIR)) {
|
|
1534
|
+
console.log(" Cloning whisper.cpp...");
|
|
1535
|
+
console.log(" [privileged] git clone");
|
|
1536
|
+
shell("git", ["clone", "--depth", "1", "https://github.com/ggerganov/whisper.cpp.git", WHISPER_DIR], { sudo: true });
|
|
1537
|
+
}
|
|
1538
|
+
// Compile via cmake (whisper.cpp's Makefile is a thin cmake wrapper)
|
|
1539
|
+
console.log(" Compiling whisper.cpp (this takes a few minutes on Pi)...");
|
|
1540
|
+
console.log(" [privileged] cmake -B");
|
|
1541
|
+
shell("cmake", ["-B", "build"], { cwd: WHISPER_DIR, sudo: true, timeout: 120_000 });
|
|
1542
|
+
console.log(" [privileged] cmake --build");
|
|
1543
|
+
shell("cmake", ["--build", "build", "--config", "Release", "-j2"], { cwd: WHISPER_DIR, sudo: true, timeout: 600_000 });
|
|
1544
|
+
// Download the base model (~150MB)
|
|
1545
|
+
if (!existsSync(WHISPER_MODEL)) {
|
|
1546
|
+
console.log(" Downloading ggml-base model (~150MB)...");
|
|
1547
|
+
console.log(" [privileged] bash -c");
|
|
1548
|
+
shellRetry("bash", ["-c", `cd ${WHISPER_DIR} && bash models/download-ggml-model.sh base`], { sudo: true, timeout: 300_000 }, 3, 15);
|
|
1549
|
+
}
|
|
1550
|
+
console.log(" whisper.cpp installed successfully.");
|
|
1551
|
+
}
|
|
1552
|
+
/**
|
|
1553
|
+
* Provision the shared HMAC secret used to sign remote-session cookies
|
|
1554
|
+
* (Task 653). Both `maxy-edge` and `maxy-ui` read this file; without it
|
|
1555
|
+
* they independently mint ephemeral secrets on first use and the
|
|
1556
|
+
* cross-process session namespace silently diverges again.
|
|
1557
|
+
*
|
|
1558
|
+
* First install: create the file (0600, 32-byte hex).
|
|
1559
|
+
* Upgrade: leave the existing file untouched — invalidating it here
|
|
1560
|
+
* would log every operator out on every upgrade.
|
|
1561
|
+
*/
|
|
1562
|
+
function provisionRemoteSessionSecret() {
|
|
1563
|
+
const persistDir = resolve(process.env.HOME ?? "/root", BRAND.configDir);
|
|
1564
|
+
const credentialsDir = join(persistDir, "credentials");
|
|
1565
|
+
const secretFile = join(credentialsDir, "remote-session-secret");
|
|
1566
|
+
if (existsSync(secretFile)) {
|
|
1567
|
+
console.log(` [install] remote-session-secret exists — preserved`);
|
|
1568
|
+
return;
|
|
1569
|
+
}
|
|
1570
|
+
mkdirSync(credentialsDir, { recursive: true, mode: 0o700 });
|
|
1571
|
+
writeFileSync(secretFile, randomBytes(32).toString("hex"), { mode: 0o600 });
|
|
1572
|
+
console.log(` [install] remote-session-secret provisioned path=${secretFile}`);
|
|
1573
|
+
}
|
|
1574
|
+
// Task 904 — install-time admin-auth invariant. Walks every account.json
|
|
1575
|
+
// under accountsDir and compares its admins[] to the persistent users.json,
|
|
1576
|
+
// emitting one [install-invariant] line per divergence and one summary line.
|
|
1577
|
+
// Log-only (no install abort) so pre-Task-904 devices with already-divergent
|
|
1578
|
+
// state still upgrade; future sprint can promote to refuse-or-degrade once
|
|
1579
|
+
// the deployed fleet is audited clean. Mirror of
|
|
1580
|
+
// checkAdminAuthInvariant() in platform/lib/admins-write/src/index.ts.
|
|
1581
|
+
function runInstallInvariantCheck(usersFile, accountsDir) {
|
|
1582
|
+
const TAG = "[install-invariant]";
|
|
1583
|
+
const usersUserIds = new Set();
|
|
1584
|
+
if (existsSync(usersFile)) {
|
|
1585
|
+
try {
|
|
1586
|
+
const raw = readFileSync(usersFile, "utf-8").trim();
|
|
1587
|
+
if (raw) {
|
|
1588
|
+
const users = JSON.parse(raw);
|
|
1589
|
+
for (const u of users) {
|
|
1590
|
+
if (typeof u.userId === "string")
|
|
1591
|
+
usersUserIds.add(u.userId);
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
catch (err) {
|
|
1596
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1597
|
+
console.log(` ${TAG} users.json unreadable usersFile=${usersFile} error=${msg}`);
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
const accountUserIds = new Set();
|
|
1601
|
+
let divergences = 0;
|
|
1602
|
+
if (existsSync(accountsDir)) {
|
|
1603
|
+
let entries = [];
|
|
1604
|
+
try {
|
|
1605
|
+
entries = readdirSync(accountsDir);
|
|
1606
|
+
}
|
|
1607
|
+
catch (err) {
|
|
1608
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1609
|
+
console.log(` ${TAG} accounts-dir unreadable accountsDir=${accountsDir} error=${msg}`);
|
|
1610
|
+
console.log(` ${TAG} check complete divergences=0 (accounts-dir unreadable)`);
|
|
1611
|
+
return;
|
|
1612
|
+
}
|
|
1613
|
+
for (const entry of entries) {
|
|
1614
|
+
if (entry.startsWith("."))
|
|
1615
|
+
continue;
|
|
1616
|
+
const accountDir = join(accountsDir, entry);
|
|
1617
|
+
try {
|
|
1618
|
+
if (!statSync(accountDir).isDirectory())
|
|
1619
|
+
continue;
|
|
1620
|
+
}
|
|
1621
|
+
catch {
|
|
1622
|
+
continue;
|
|
1623
|
+
}
|
|
1624
|
+
const accountJsonPath = join(accountDir, "account.json");
|
|
1625
|
+
if (!existsSync(accountJsonPath))
|
|
1626
|
+
continue;
|
|
1627
|
+
let admins = [];
|
|
1628
|
+
try {
|
|
1629
|
+
const config = JSON.parse(readFileSync(accountJsonPath, "utf-8"));
|
|
1630
|
+
admins = (config.admins ?? []);
|
|
1631
|
+
}
|
|
1632
|
+
catch (err) {
|
|
1633
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1634
|
+
console.log(` ${TAG} account.json unreadable source=${accountJsonPath} error=${msg}`);
|
|
1635
|
+
continue;
|
|
1636
|
+
}
|
|
1637
|
+
for (const a of admins) {
|
|
1638
|
+
if (typeof a.userId !== "string")
|
|
1639
|
+
continue;
|
|
1640
|
+
accountUserIds.add(a.userId);
|
|
1641
|
+
if (!usersUserIds.has(a.userId)) {
|
|
1642
|
+
const userIdShort = a.userId.slice(0, 8);
|
|
1643
|
+
console.log(` ${TAG} direction=account-without-users userId=${userIdShort} source=${accountJsonPath}`);
|
|
1644
|
+
divergences += 1;
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
for (const uid of usersUserIds) {
|
|
1650
|
+
if (!accountUserIds.has(uid)) {
|
|
1651
|
+
const userIdShort = uid.slice(0, 8);
|
|
1652
|
+
console.log(` ${TAG} direction=users-without-account userId=${userIdShort} source=${usersFile}`);
|
|
1653
|
+
divergences += 1;
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
console.log(` ${TAG} check complete divergences=${divergences}`);
|
|
1657
|
+
}
|
|
1658
|
+
function deployPayload() {
|
|
1659
|
+
log("8", TOTAL, `Deploying ${BRAND.productName}...`);
|
|
1660
|
+
if (!existsSync(PAYLOAD_DIR)) {
|
|
1661
|
+
throw new Error(`Payload not found at ${PAYLOAD_DIR}. Package may be corrupted.`);
|
|
1662
|
+
}
|
|
1663
|
+
// Persistent config lives at ~/{configDir}/ — survives rm -rf ~/{installDir}
|
|
1664
|
+
const persistentDir = resolve(process.env.HOME ?? "/root", BRAND.configDir);
|
|
1665
|
+
const persistentPasswordFile = join(persistentDir, ".neo4j-password");
|
|
1666
|
+
const persistentAccountsDir = join(persistentDir, "accounts");
|
|
1667
|
+
// Migrate: if password is in old location, move to persistent
|
|
1668
|
+
const oldPasswordFile = join(INSTALL_DIR, "platform/config/.neo4j-password");
|
|
1669
|
+
if (existsSync(oldPasswordFile) && !existsSync(persistentPasswordFile)) {
|
|
1670
|
+
mkdirSync(persistentDir, { recursive: true });
|
|
1671
|
+
cpSync(oldPasswordFile, persistentPasswordFile);
|
|
1672
|
+
}
|
|
1673
|
+
const oldAccountsDir = join(INSTALL_DIR, "platform/config/accounts");
|
|
1674
|
+
if (existsSync(oldAccountsDir) && !existsSync(persistentAccountsDir)) {
|
|
1675
|
+
mkdirSync(persistentDir, { recursive: true });
|
|
1676
|
+
cpSync(oldAccountsDir, persistentAccountsDir, { recursive: true });
|
|
1677
|
+
}
|
|
1678
|
+
// Task 904 — users.json lives at <persistentDir>/users.json. Pre-Task-904
|
|
1679
|
+
// installs wrote to <INSTALL_DIR>/platform/config/users.json (the wipe zone)
|
|
1680
|
+
// and relied on a one-shot copy-up at first install plus a copy-back after
|
|
1681
|
+
// every wipe. The copy-back overwrote LIVE with the FROZEN-AT-FIRST-INSTALL
|
|
1682
|
+
// backup, dropping every admin row added since first install. Writers (paths.ts,
|
|
1683
|
+
// admin-add, set-pin, seed-neo4j.sh, migrate-import.sh) now target the
|
|
1684
|
+
// persistent file directly — see comment in platform/ui/app/lib/paths.ts.
|
|
1685
|
+
//
|
|
1686
|
+
// Legacy pickup: if a pre-Task-904 install left users.json inside the wipe
|
|
1687
|
+
// zone, copy it up once. Idempotent — guarded by !existsSync(persistentUsersFile).
|
|
1688
|
+
// Safe to delete this block one release after Task 904 has been deployed
|
|
1689
|
+
// everywhere; until then it absorbs upgrades from any pre-Task-904 version.
|
|
1690
|
+
const persistentUsersFile = join(persistentDir, "users.json");
|
|
1691
|
+
const oldUsersFile = join(INSTALL_DIR, "platform/config/users.json");
|
|
1692
|
+
if (existsSync(oldUsersFile) && !existsSync(persistentUsersFile)) {
|
|
1693
|
+
mkdirSync(persistentDir, { recursive: true });
|
|
1694
|
+
cpSync(oldUsersFile, persistentUsersFile);
|
|
1695
|
+
console.log(" Migrated pre-Task-904 users.json to persistent storage.");
|
|
1696
|
+
}
|
|
1697
|
+
// Task 923 — Claude Code OAuth credentials legacy pickup. Pre-Task-923
|
|
1698
|
+
// installs shared `~/.claude/.credentials.json` across brands; the brand
|
|
1699
|
+
// main service's `claude` SDK subprocess and admin server now read from
|
|
1700
|
+
// `${persistDir}/.claude/.credentials.json` per the new
|
|
1701
|
+
// Environment=CLAUDE_CONFIG_DIR= block in port-resolution.ts.
|
|
1702
|
+
//
|
|
1703
|
+
// Move-semantics: the FIRST brand to install grabs the legacy file. The
|
|
1704
|
+
// operation is cpSync + unlinkSync (not renameSync) so it survives cross-
|
|
1705
|
+
// volume `~/` mounts that would EXDEV-fail rename. Subsequent brand
|
|
1706
|
+
// installs find no legacy file and require a fresh `claude /login`.
|
|
1707
|
+
//
|
|
1708
|
+
// Idempotency stamp at `${persistentDir}/.claude/.credentials-migrated`
|
|
1709
|
+
// prevents re-pickup on re-installs of the same brand even if an operator
|
|
1710
|
+
// later runs `claude /login` with no CLAUDE_CONFIG_DIR set, which would
|
|
1711
|
+
// re-create the legacy path.
|
|
1712
|
+
const persistentClaudeDir = join(persistentDir, ".claude");
|
|
1713
|
+
const persistentCredsFile = join(persistentClaudeDir, ".credentials.json");
|
|
1714
|
+
const legacyCredsFile = join(process.env.HOME ?? "/root", ".claude", ".credentials.json");
|
|
1715
|
+
const credsMigratedStamp = join(persistentClaudeDir, ".credentials-migrated");
|
|
1716
|
+
if (!existsSync(credsMigratedStamp)) {
|
|
1717
|
+
mkdirSync(persistentClaudeDir, { recursive: true });
|
|
1718
|
+
if (existsSync(legacyCredsFile) && !existsSync(persistentCredsFile)) {
|
|
1719
|
+
cpSync(legacyCredsFile, persistentCredsFile);
|
|
1720
|
+
try {
|
|
1721
|
+
unlinkSync(legacyCredsFile);
|
|
1722
|
+
}
|
|
1723
|
+
catch (err) {
|
|
1724
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1725
|
+
console.log(` [install] claude-creds pickup: legacy unlink warn=${msg}`);
|
|
1726
|
+
}
|
|
1727
|
+
console.log(` [install] claude-creds pickup: brand=${BRAND.hostname} source=${legacyCredsFile} target=${persistentCredsFile}`);
|
|
1728
|
+
}
|
|
1729
|
+
writeFileSync(credsMigratedStamp, new Date().toISOString() + "\n");
|
|
1730
|
+
}
|
|
1731
|
+
// Brand isolation: installer does not read ~/.maxy/, ~/.cloudflared/, or
|
|
1732
|
+
// ~/.cloudflare/ on non-default brands. These are peer-brand or shared-singleton
|
|
1733
|
+
// paths (Task 659). Pre-Task-659 installs that need to recover legacy state
|
|
1734
|
+
// follow the manual recovery paragraph in .docs/deployment.md.
|
|
1735
|
+
// Stop the running service before wiping directories (upgrade path).
|
|
1736
|
+
// The server holds open files in platform/ — rmSync fails with ENOTEMPTY if it's running.
|
|
1737
|
+
// Task 838 — darwin uses `launchctl bootout` instead of systemctl; bootout
|
|
1738
|
+
// returns synchronously when the agent has exited.
|
|
1739
|
+
if (requireSupportedPlatform(process.platform) === "darwin") {
|
|
1740
|
+
spawnSync("launchctl", ["bootout", `${gui()}/${launchdLabel()}`], { stdio: "pipe", timeout: 15_000 });
|
|
1741
|
+
}
|
|
1742
|
+
else {
|
|
1743
|
+
// systemctl stop returns when the main process exits, but ExecStopPost (e.g. VNC cleanup)
|
|
1744
|
+
// may still hold file handles. Poll is-active to wait for full deactivation.
|
|
1745
|
+
const svcName = BRAND.serviceName.replace(".service", "");
|
|
1746
|
+
spawnSync("systemctl", ["--user", "stop", svcName], { stdio: "pipe" });
|
|
1747
|
+
const MAX_STOP_WAIT = 5;
|
|
1748
|
+
for (let i = 0; i < MAX_STOP_WAIT; i++) {
|
|
1749
|
+
const result = spawnSync("systemctl", ["--user", "is-active", svcName], { stdio: "pipe" });
|
|
1750
|
+
const status = result.stdout?.toString().trim();
|
|
1751
|
+
if (status !== "active" && status !== "deactivating") {
|
|
1752
|
+
console.log(` Service stopped (${status || "not found"}).`);
|
|
1753
|
+
break;
|
|
1754
|
+
}
|
|
1755
|
+
if (i === MAX_STOP_WAIT - 1) {
|
|
1756
|
+
console.log(` [WARN] Service still ${status} after ${MAX_STOP_WAIT}s — proceeding with directory wipe.`);
|
|
1757
|
+
break;
|
|
1758
|
+
}
|
|
1759
|
+
spawnSync("sleep", ["1"]);
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
// Migrate user data to {installDir}/data/ — outside the platform/ wipe zone.
|
|
1763
|
+
// Runs after service stop to avoid copying files while the agent is writing.
|
|
1764
|
+
// Once data/ is populated, subsequent reinstalls skip this entirely.
|
|
1765
|
+
const dataDir = join(INSTALL_DIR, "data");
|
|
1766
|
+
const dataAccountsDir = join(dataDir, "accounts");
|
|
1767
|
+
const dataUploadsDir = join(dataDir, "uploads");
|
|
1768
|
+
const oldLiveAccounts = join(INSTALL_DIR, "platform/config/accounts");
|
|
1769
|
+
if (!existsSync(dataAccountsDir)) {
|
|
1770
|
+
if (existsSync(oldLiveAccounts)) {
|
|
1771
|
+
mkdirSync(dataDir, { recursive: true });
|
|
1772
|
+
cpSync(oldLiveAccounts, dataAccountsDir, { recursive: true });
|
|
1773
|
+
console.log(" Migrated accounts to data/.");
|
|
1774
|
+
}
|
|
1775
|
+
else if (existsSync(persistentAccountsDir)) {
|
|
1776
|
+
mkdirSync(dataDir, { recursive: true });
|
|
1777
|
+
cpSync(persistentAccountsDir, dataAccountsDir, { recursive: true });
|
|
1778
|
+
console.log(" Restored accounts from persistent backup to data/.");
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
const oldLiveUploads = join(INSTALL_DIR, "platform/data/attachments");
|
|
1782
|
+
if (!existsSync(dataUploadsDir) && existsSync(oldLiveUploads)) {
|
|
1783
|
+
mkdirSync(dataDir, { recursive: true });
|
|
1784
|
+
cpSync(oldLiveUploads, dataUploadsDir, { recursive: true });
|
|
1785
|
+
console.log(" Migrated uploads to data/.");
|
|
1786
|
+
}
|
|
1787
|
+
// Wipe deployment directories to prevent stale files.
|
|
1788
|
+
// data/ is NOT wiped — it contains user data (accounts, uploads) that must survive.
|
|
1789
|
+
for (const dir of ["platform", "server", "maxy", "premium-plugins", "docs", ".claude"]) {
|
|
1790
|
+
const target = join(INSTALL_DIR, dir);
|
|
1791
|
+
if (existsSync(target)) {
|
|
1792
|
+
rmSync(target, { recursive: true, force: true });
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
mkdirSync(INSTALL_DIR, { recursive: true });
|
|
1796
|
+
// Deploy payload
|
|
1797
|
+
cpSync(PAYLOAD_DIR, INSTALL_DIR, {
|
|
1798
|
+
recursive: true,
|
|
1799
|
+
force: true,
|
|
1800
|
+
});
|
|
1801
|
+
// Link persistent config into install directory
|
|
1802
|
+
const configDir = join(INSTALL_DIR, "platform/config");
|
|
1803
|
+
mkdirSync(configDir, { recursive: true });
|
|
1804
|
+
if (existsSync(persistentPasswordFile)) {
|
|
1805
|
+
cpSync(persistentPasswordFile, join(configDir, ".neo4j-password"));
|
|
1806
|
+
console.log(" Restored Neo4j password.");
|
|
1807
|
+
}
|
|
1808
|
+
// Task 904 — users.json is read directly from persistentDir by both
|
|
1809
|
+
// platform/ui/app/lib/paths.ts (USERS_FILE = MAXY_DIR/users.json) and the
|
|
1810
|
+
// admin MCP plugin (USERS_FILE = CONFIG_DIR/users.json). No copy into the
|
|
1811
|
+
// wipe zone — the pre-Task-904 cpSync to platform/config/users.json was the
|
|
1812
|
+
// copy that overwrote new admin rows on every install. The line below
|
|
1813
|
+
// observes the row count + userId prefixes so a future regression is grep-
|
|
1814
|
+
// detectable in the install log without any runtime change (singular
|
|
1815
|
+
// "userId preserved" hid the multi-row failure mode).
|
|
1816
|
+
if (existsSync(persistentUsersFile)) {
|
|
1817
|
+
try {
|
|
1818
|
+
const raw = readFileSync(persistentUsersFile, "utf-8").trim();
|
|
1819
|
+
const rows = raw ? JSON.parse(raw) : [];
|
|
1820
|
+
const ids = rows
|
|
1821
|
+
.map(r => (typeof r.userId === "string" ? r.userId.slice(0, 8) : "?"))
|
|
1822
|
+
.join(",");
|
|
1823
|
+
console.log(` [install] users.json preserved: rows=${rows.length} userIds=${ids}`);
|
|
1824
|
+
}
|
|
1825
|
+
catch (err) {
|
|
1826
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1827
|
+
console.log(` [install] users.json preserved: rows=? parse-failed error=${msg}`);
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
else {
|
|
1831
|
+
console.log(" [install] users.json: no persistent file (fresh install — set-pin will create it)");
|
|
1832
|
+
}
|
|
1833
|
+
// Task 904 item 3 — install-time admin-auth invariant check. Walks every
|
|
1834
|
+
// data/accounts/*/account.json admins[] and compares to the persistent
|
|
1835
|
+
// users.json. Log-only (does NOT refuse install) so pre-Task-904 devices
|
|
1836
|
+
// with already-divergent state still upgrade; the [install-invariant]
|
|
1837
|
+
// line surfaces the bug to the operator without bricking the device.
|
|
1838
|
+
// Inline rather than imported from platform/lib/admins-write because the
|
|
1839
|
+
// installer is its own npm package and doesn't bundle the platform lib.
|
|
1840
|
+
// Mirrors checkAdminAuthInvariant() in platform/lib/admins-write/src/index.ts;
|
|
1841
|
+
// future divergence between the two should be caught by the test suite.
|
|
1842
|
+
runInstallInvariantCheck(persistentUsersFile, join(INSTALL_DIR, "data", "accounts"));
|
|
1843
|
+
// Write version marker so the running platform knows which create-maxy produced this deployment
|
|
1844
|
+
writeFileSync(join(configDir, `.${BRAND.hostname}-version`), PKG_VERSION, "utf-8");
|
|
1845
|
+
console.log(` Deployed to ${INSTALL_DIR}`);
|
|
1846
|
+
}
|
|
1847
|
+
function buildPlatform() {
|
|
1848
|
+
log("9", TOTAL, "Installing dependencies and building...");
|
|
1849
|
+
console.log(` Installing platform dependencies (${join(INSTALL_DIR, "platform")})...`);
|
|
1850
|
+
shellRetry("npm", ["install", ...NPM_NET_FLAGS], { cwd: join(INSTALL_DIR, "platform") }, 3, 15);
|
|
1851
|
+
// MCP server dist/ files are pre-compiled in the payload — no build step needed.
|
|
1852
|
+
// Server external dependencies (neo4j-driver, @anthropic-ai/sdk) are listed in
|
|
1853
|
+
// server/package.json but NOT shipped as pre-built node_modules — npm pack silently
|
|
1854
|
+
// strips files from nested node_modules (e.g. rxjs/package.json), breaking require().
|
|
1855
|
+
// Install fresh on device to guarantee a complete dependency tree.
|
|
1856
|
+
//
|
|
1857
|
+
// On upgrade, wipe `node_modules` first so npm extracts a clean tree. Without
|
|
1858
|
+
// this, an interrupted previous install (network blip, operator cancellation,
|
|
1859
|
+
// power loss) can leave nested package.json files half-truncated — the most
|
|
1860
|
+
// common manifestation is `Error: Invalid package config .../rxjs/package.json`
|
|
1861
|
+
// at server startup, which loops the brand service indefinitely. The wipe
|
|
1862
|
+
// adds ~30 s to upgrades but eliminates a class of unrecoverable customer
|
|
1863
|
+
// states; reliability wins over speed for a one-shot install path.
|
|
1864
|
+
const serverNodeModules = join(INSTALL_DIR, "server", "node_modules");
|
|
1865
|
+
if (existsSync(serverNodeModules)) {
|
|
1866
|
+
console.log(" Wiping previous server/node_modules for a clean reinstall...");
|
|
1867
|
+
rmSync(serverNodeModules, { recursive: true, force: true });
|
|
1868
|
+
}
|
|
1869
|
+
console.log(` Installing server dependencies (${join(INSTALL_DIR, "server")})...`);
|
|
1870
|
+
shellRetry("npm", ["install", "--omit=dev", ...NPM_NET_FLAGS], { cwd: join(INSTALL_DIR, "server") }, 3, 15);
|
|
1871
|
+
// Task 001 (maxy-code) — claude-session-manager has its own package.json
|
|
1872
|
+
// declaring hono + @hono/node-server + node-pty. node-pty is a native
|
|
1873
|
+
// binding; it MUST be installed on the Pi, not shipped pre-built (different
|
|
1874
|
+
// architecture between the build host and the Pi). Wipe + reinstall on
|
|
1875
|
+
// upgrade so a half-extracted previous install does not loop the unit.
|
|
1876
|
+
const csmDir = join(INSTALL_DIR, "platform", "services", "claude-session-manager");
|
|
1877
|
+
if (existsSync(csmDir)) {
|
|
1878
|
+
const csmNodeModules = join(csmDir, "node_modules");
|
|
1879
|
+
if (existsSync(csmNodeModules)) {
|
|
1880
|
+
console.log(" Wiping previous claude-session-manager/node_modules for a clean reinstall...");
|
|
1881
|
+
rmSync(csmNodeModules, { recursive: true, force: true });
|
|
1882
|
+
}
|
|
1883
|
+
console.log(` Installing claude-session-manager dependencies (${csmDir})...`);
|
|
1884
|
+
shellRetry("npm", ["install", "--omit=dev", ...NPM_NET_FLAGS], { cwd: csmDir }, 3, 15);
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
function setupVncViewer() {
|
|
1888
|
+
if (!isLinux())
|
|
1889
|
+
return;
|
|
1890
|
+
const novncSrc = "/usr/share/novnc";
|
|
1891
|
+
const novncDest = join(INSTALL_DIR, "server/public/novnc");
|
|
1892
|
+
if (!existsSync(join(novncSrc, "core"))) {
|
|
1893
|
+
console.log(" noVNC not found — skipping VNC viewer setup.");
|
|
1894
|
+
return;
|
|
1895
|
+
}
|
|
1896
|
+
console.log(" Installing VNC viewer...");
|
|
1897
|
+
// Copy core/ and vendor/ (pako compression library) — both required by rfb.js
|
|
1898
|
+
cpSync(join(novncSrc, "core"), join(novncDest, "core"), { recursive: true, force: true });
|
|
1899
|
+
const vendorSrc = join(novncSrc, "vendor");
|
|
1900
|
+
if (existsSync(vendorSrc)) {
|
|
1901
|
+
cpSync(vendorSrc, join(novncDest, "vendor"), { recursive: true, force: true });
|
|
1902
|
+
}
|
|
1903
|
+
// Custom viewer: no toolbar, scales to fit, auto-connects with retry.
|
|
1904
|
+
//
|
|
1905
|
+
// Transport: same-origin WebSocket proxied through the Maxy server's
|
|
1906
|
+
// /websockify upgrade handler. The URL is built from location.protocol
|
|
1907
|
+
// and location.host so that:
|
|
1908
|
+
// - HTTP origin → ws://<host>:<port>/websockify (LAN)
|
|
1909
|
+
// - HTTPS origin → wss://<host>:<port>/websockify (Cloudflare tunnel)
|
|
1910
|
+
// This eliminates the mixed-content block that previously killed the
|
|
1911
|
+
// viewer when accessed via https://admin.maxy.bot.
|
|
1912
|
+
//
|
|
1913
|
+
// The viewer does NOT read a host/port from query string — those
|
|
1914
|
+
// parameters are ignored if present (kept for backward compatibility
|
|
1915
|
+
// with any cached callers) and the connection is always same-origin.
|
|
1916
|
+
//
|
|
1917
|
+
// A per-session correlation ID is generated client-side and included
|
|
1918
|
+
// in the WebSocket URL (?corrId=X) and in the POST to
|
|
1919
|
+
// /api/vnc/client-event so the server-side log can correlate the
|
|
1920
|
+
// noVNC disconnect reason to the specific WS upgrade entry.
|
|
1921
|
+
const html = `<!DOCTYPE html>
|
|
1922
|
+
<html>
|
|
1923
|
+
<head>
|
|
1924
|
+
<meta charset="utf-8">
|
|
1925
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
|
|
1926
|
+
<title>Connect Claude</title>
|
|
1927
|
+
<style>
|
|
1928
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
1929
|
+
html, body { width: 100%; height: 100%; background: #111; overflow: hidden; }
|
|
1930
|
+
#screen { position: relative; width: 100%; height: 100%; }
|
|
1931
|
+
#status {
|
|
1932
|
+
position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
|
|
1933
|
+
color: #888; font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
|
1934
|
+
font-size: 14px; text-align: center; z-index: 10;
|
|
1935
|
+
transition: opacity 0.3s;
|
|
1936
|
+
}
|
|
1937
|
+
#status.hidden { opacity: 0; pointer-events: none; }
|
|
1938
|
+
.status-spinner {
|
|
1939
|
+
display: inline-block; width: 20px; height: 20px;
|
|
1940
|
+
border: 2px solid #444; border-top-color: #888; border-radius: 50%;
|
|
1941
|
+
animation: spin 0.8s linear infinite; margin-bottom: 8px;
|
|
1942
|
+
}
|
|
1943
|
+
@keyframes spin { to { transform: rotate(360deg); } }
|
|
1944
|
+
.status-reason { font-size: 12px; color: #666; margin-top: 6px; }
|
|
1945
|
+
</style>
|
|
1946
|
+
</head>
|
|
1947
|
+
<body>
|
|
1948
|
+
<div id="screen"></div>
|
|
1949
|
+
<div id="status">
|
|
1950
|
+
<div class="status-spinner"></div>
|
|
1951
|
+
<div>Connecting to browser…</div>
|
|
1952
|
+
<div class="status-reason"></div>
|
|
1953
|
+
</div>
|
|
1954
|
+
<script type="module">
|
|
1955
|
+
import RFB from '/novnc/core/rfb.js';
|
|
1956
|
+
|
|
1957
|
+
// Build a same-origin WebSocket URL. Protocol auto-matches the
|
|
1958
|
+
// parent page so https pages use wss and http pages use ws.
|
|
1959
|
+
const wsScheme = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
1960
|
+
// Generate a client-side correlation ID. This ID is used only for
|
|
1961
|
+
// client-side log correlation (the server assigns its own corrId
|
|
1962
|
+
// on the upgrade handler so an attacker cannot forge server
|
|
1963
|
+
// internal ids). Base-36 random concatenation yields up to 16
|
|
1964
|
+
// chars — collision-free in practice for the 1-session-at-a-time
|
|
1965
|
+
// VNC use case.
|
|
1966
|
+
const corrId = (Math.random().toString(36).slice(2, 10) + Math.random().toString(36).slice(2, 10))
|
|
1967
|
+
.replace(/[^a-z0-9]/gi, '') || 'c' + Date.now().toString(36);
|
|
1968
|
+
const wsUrl = wsScheme + '//' + location.host + '/websockify?corrId=' + encodeURIComponent(corrId);
|
|
1969
|
+
|
|
1970
|
+
const screen = document.getElementById('screen');
|
|
1971
|
+
const status = document.getElementById('status');
|
|
1972
|
+
let retryCount = 0;
|
|
1973
|
+
const MAX_RETRIES = 30;
|
|
1974
|
+
|
|
1975
|
+
// Best-effort POST of a disconnect/error reason to the server so
|
|
1976
|
+
// the noVNC-observed failure mode ends up in vnc-boot.log alongside
|
|
1977
|
+
// the server-side proxy events. Network failures are swallowed —
|
|
1978
|
+
// this is a telemetry side channel, not a critical path.
|
|
1979
|
+
function reportClientEvent(phase, reason) {
|
|
1980
|
+
try {
|
|
1981
|
+
fetch('/api/vnc/client-event', {
|
|
1982
|
+
method: 'POST',
|
|
1983
|
+
credentials: 'same-origin',
|
|
1984
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1985
|
+
body: JSON.stringify({ corrId: corrId, phase: phase, reason: reason || '' }),
|
|
1986
|
+
keepalive: true,
|
|
1987
|
+
}).catch(function() { /* swallow */ });
|
|
1988
|
+
} catch (e) { /* swallow */ }
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1991
|
+
// Layer-6 beacon (Task 958): emit event=rfb-connected and
|
|
1992
|
+
// event=rfb-error to the operator-grep lifecycle endpoint so
|
|
1993
|
+
// server.log shows the noVNC outcome alongside [setup-tunnel] /
|
|
1994
|
+
// [cloudflare-setup] / [device-url:click] / [http] / [websockify].
|
|
1995
|
+
// Same-origin POST works whether the iframe is parented by the
|
|
1996
|
+
// React BrowserViewer or by vnc-popout.html.
|
|
1997
|
+
function reportBrowserViewerEvent(event, fields) {
|
|
1998
|
+
try {
|
|
1999
|
+
var body = JSON.stringify(Object.assign(
|
|
2000
|
+
{ event: event, surface: 'iframe' },
|
|
2001
|
+
fields || {},
|
|
2002
|
+
));
|
|
2003
|
+
fetch('/api/admin/browser-iframe/event', {
|
|
2004
|
+
method: 'POST',
|
|
2005
|
+
credentials: 'same-origin',
|
|
2006
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2007
|
+
body: body,
|
|
2008
|
+
keepalive: true,
|
|
2009
|
+
}).catch(function() { /* swallow */ });
|
|
2010
|
+
} catch (e) { /* swallow */ }
|
|
2011
|
+
}
|
|
2012
|
+
var connectStartedAt = 0;
|
|
2013
|
+
|
|
2014
|
+
function connect() {
|
|
2015
|
+
status.classList.remove('hidden');
|
|
2016
|
+
status.querySelector('.status-spinner').style.display = '';
|
|
2017
|
+
status.querySelector('div:nth-child(2)').textContent =
|
|
2018
|
+
retryCount > 0 ? 'Reconnecting… (' + retryCount + ')' : 'Connecting to browser…';
|
|
2019
|
+
status.querySelector('.status-reason').textContent = '';
|
|
2020
|
+
connectStartedAt = Date.now();
|
|
2021
|
+
|
|
2022
|
+
const rfb = new RFB(screen, wsUrl);
|
|
2023
|
+
rfb.scaleViewport = true;
|
|
2024
|
+
rfb.clipViewport = true;
|
|
2025
|
+
rfb.resizeSession = false;
|
|
2026
|
+
window.rfb = rfb;
|
|
2027
|
+
|
|
2028
|
+
rfb.addEventListener('connect', () => {
|
|
2029
|
+
status.classList.add('hidden');
|
|
2030
|
+
retryCount = 0;
|
|
2031
|
+
reportBrowserViewerEvent('rfb-connected', {
|
|
2032
|
+
durationMs: Date.now() - connectStartedAt,
|
|
2033
|
+
});
|
|
2034
|
+
});
|
|
2035
|
+
|
|
2036
|
+
rfb.addEventListener('disconnect', (e) => {
|
|
2037
|
+
status.classList.remove('hidden');
|
|
2038
|
+
const detail = e.detail || {};
|
|
2039
|
+
const reason = detail.reason || (detail.clean === false ? 'Connection refused' : '');
|
|
2040
|
+
reportClientEvent('disconnect', reason || (detail.clean === false ? 'unclean-close' : 'normal'));
|
|
2041
|
+
reportBrowserViewerEvent('rfb-error', {
|
|
2042
|
+
durationMs: Date.now() - connectStartedAt,
|
|
2043
|
+
errorCode: detail.clean === false ? 'unclean-close' : 'normal',
|
|
2044
|
+
message: reason || '',
|
|
2045
|
+
});
|
|
2046
|
+
const reasonEl = status.querySelector('.status-reason');
|
|
2047
|
+
if (retryCount < MAX_RETRIES) {
|
|
2048
|
+
retryCount++;
|
|
2049
|
+
const delay = Math.min(1000 * retryCount, 5000);
|
|
2050
|
+
status.querySelector('div:nth-child(2)').textContent = 'Reconnecting in ' + Math.ceil(delay/1000) + 's…';
|
|
2051
|
+
reasonEl.textContent = reason;
|
|
2052
|
+
setTimeout(connect, delay);
|
|
2053
|
+
} else {
|
|
2054
|
+
status.querySelector('.status-spinner').style.display = 'none';
|
|
2055
|
+
status.querySelector('div:nth-child(2)').textContent = 'Connection lost. Reload the page to retry.';
|
|
2056
|
+
reasonEl.textContent = reason;
|
|
2057
|
+
}
|
|
2058
|
+
});
|
|
2059
|
+
|
|
2060
|
+
// --- Clipboard bridge (local ↔ remote) ---
|
|
2061
|
+
|
|
2062
|
+
// Paste bridge: intercept Cmd/Ctrl+V in the capturing phase before
|
|
2063
|
+
// noVNC's keydown handler can call preventDefault(). noVNC v1.3.0
|
|
2064
|
+
// binds its handler with .bind(this) in the Keyboard constructor and
|
|
2065
|
+
// registers the bound reference via addEventListener — so monkey-
|
|
2066
|
+
// patching the unbound method on the instance is a no-op. A capturing-
|
|
2067
|
+
// phase listener on document fires before noVNC's target-phase handler
|
|
2068
|
+
// regardless of registration timing or internal structure.
|
|
2069
|
+
document.addEventListener('keydown', (e) => {
|
|
2070
|
+
if ((e.ctrlKey || e.metaKey) && (e.key === 'v' || e.key === 'V')) {
|
|
2071
|
+
e.stopImmediatePropagation();
|
|
2072
|
+
reportClientEvent('paste-bridge', 'keydown-intercepted');
|
|
2073
|
+
}
|
|
2074
|
+
}, true);
|
|
2075
|
+
|
|
2076
|
+
// Catch the paste event, sync text to VNC server clipboard, then
|
|
2077
|
+
// send Ctrl+V keystrokes so the remote app pastes the synced content.
|
|
2078
|
+
document.addEventListener('paste', (e) => {
|
|
2079
|
+
e.preventDefault();
|
|
2080
|
+
const text = (e.clipboardData || window.clipboardData)?.getData('text');
|
|
2081
|
+
if (!text || !window.rfb) {
|
|
2082
|
+
reportClientEvent('paste-bridge', 'empty-clipboard');
|
|
2083
|
+
return;
|
|
2084
|
+
}
|
|
2085
|
+
reportClientEvent('paste-bridge', 'paste-event text-length=' + text.length);
|
|
2086
|
+
window.rfb.clipboardPasteFrom(text);
|
|
2087
|
+
reportClientEvent('paste-bridge', 'clipboard-synced');
|
|
2088
|
+
setTimeout(() => {
|
|
2089
|
+
window.rfb.sendKey(0xFFE3, 'ControlLeft', true);
|
|
2090
|
+
window.rfb.sendKey(0x0076, 'v', true);
|
|
2091
|
+
window.rfb.sendKey(0x0076, 'v', false);
|
|
2092
|
+
window.rfb.sendKey(0xFFE3, 'ControlLeft', false);
|
|
2093
|
+
}, 50);
|
|
2094
|
+
});
|
|
2095
|
+
|
|
2096
|
+
// Copy bridge: when the remote clipboard changes, notify the parent frame.
|
|
2097
|
+
rfb.addEventListener('clipboard', (e) => {
|
|
2098
|
+
window.parent.postMessage({ type: 'vnc-clipboard', text: e.detail.text }, '*');
|
|
2099
|
+
});
|
|
2100
|
+
}
|
|
2101
|
+
|
|
2102
|
+
connect();
|
|
2103
|
+
</script>
|
|
2104
|
+
</body>
|
|
2105
|
+
</html>`;
|
|
2106
|
+
writeFileSync(join(INSTALL_DIR, "server/public/vnc-viewer.html"), html);
|
|
2107
|
+
console.log(" VNC viewer ready at /vnc-viewer.html");
|
|
2108
|
+
}
|
|
2109
|
+
function setupAccount() {
|
|
2110
|
+
log("10", TOTAL, "Setting up...");
|
|
2111
|
+
// Task 787 — seed-neo4j.sh hard-exits without NEO4J_URI. The installer
|
|
2112
|
+
// owns the brand-correct URI and password, so we derive them once.
|
|
2113
|
+
// Missing password file is a hard error: ensureNeo4jPassword() ran
|
|
2114
|
+
// upstream and would have thrown already if it couldn't reach the
|
|
2115
|
+
// brand's Neo4j.
|
|
2116
|
+
const passwordFile = join(INSTALL_DIR, "platform/config/.neo4j-password");
|
|
2117
|
+
if (!existsSync(passwordFile)) {
|
|
2118
|
+
throw new Error(`Neo4j password file missing at ${passwordFile} — required by setup step.`);
|
|
2119
|
+
}
|
|
2120
|
+
const password = readFileSync(passwordFile, "utf-8").trim();
|
|
2121
|
+
const neo4jUri = `bolt://localhost:${NEO4J_PORT}`;
|
|
2122
|
+
const neo4jEnv = { ...process.env, NEO4J_URI: neo4jUri, NEO4J_PASSWORD: password };
|
|
2123
|
+
const seedScript = join(INSTALL_DIR, "platform/scripts/seed-neo4j.sh");
|
|
2124
|
+
if (existsSync(seedScript)) {
|
|
2125
|
+
console.log(` [neo4j] passing NEO4J_URI=${neo4jUri} to seed`);
|
|
2126
|
+
logFile(` [neo4j] passing NEO4J_URI=${neo4jUri} to seed`);
|
|
2127
|
+
shell("bash", [seedScript], { cwd: INSTALL_DIR, env: neo4jEnv });
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
// ---------------------------------------------------------------------------
|
|
2131
|
+
// Tunnel script shortcuts
|
|
2132
|
+
//
|
|
2133
|
+
// The cloudflare plugin's SKILL.md, PLUGIN.md, and reference docs encode the
|
|
2134
|
+
// invocation `~/setup-tunnel.sh` (and `~/reset-tunnel.sh`). The filesystem
|
|
2135
|
+
// reality is <INSTALL_DIR>/platform/plugins/cloudflare/scripts/*.sh. Without
|
|
2136
|
+
// the symlink the agent's first SKILL-compliant invocation fails with exit
|
|
2137
|
+
// 127 — the discipline-violation loop Task 555 exists to close.
|
|
2138
|
+
//
|
|
2139
|
+
// Collision discipline (Task 659 — last-writer-wins across brands):
|
|
2140
|
+
// - absent path → create
|
|
2141
|
+
// - regular file → exit 1 (operator-owned file, do not clobber)
|
|
2142
|
+
// - symlink → same target → no-op
|
|
2143
|
+
// - symlink → any other target → overwrite (unlink + symlink)
|
|
2144
|
+
// covers stale-same-brand, dangling,
|
|
2145
|
+
// unreadable, and peer-brand cases.
|
|
2146
|
+
// setup-tunnel.sh takes <brand> as argv
|
|
2147
|
+
// so the script still operates on the
|
|
2148
|
+
// correct brand regardless of who owns
|
|
2149
|
+
// the shortcut symlink.
|
|
2150
|
+
// ---------------------------------------------------------------------------
|
|
2151
|
+
function createTunnelSymlink(linkPath, target) {
|
|
2152
|
+
const targetAbs = resolve(target);
|
|
2153
|
+
let lstat = null;
|
|
2154
|
+
try {
|
|
2155
|
+
lstat = lstatSync(linkPath);
|
|
2156
|
+
}
|
|
2157
|
+
catch (err) {
|
|
2158
|
+
const code = err.code;
|
|
2159
|
+
if (code !== "ENOENT") {
|
|
2160
|
+
console.error(`Setup failed: ${linkPath} lstat failed: ${err.message}`);
|
|
2161
|
+
console.error(`[create-maxy:error] ${linkPath} lstat failed: ${err.message}`);
|
|
2162
|
+
process.exit(1);
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
if (lstat === null) {
|
|
2166
|
+
symlinkSync(targetAbs, linkPath);
|
|
2167
|
+
console.log(` [create-maxy] symlink ${linkPath} → ${targetAbs}`);
|
|
2168
|
+
logFile(` symlink created: ${linkPath} → ${targetAbs}`);
|
|
2169
|
+
return;
|
|
2170
|
+
}
|
|
2171
|
+
if (!lstat.isSymbolicLink()) {
|
|
2172
|
+
console.error(`Setup failed: ${linkPath} collision (regular file)`);
|
|
2173
|
+
console.error(`[create-maxy:collision] ${linkPath} already exists target=<regular-file>`);
|
|
2174
|
+
console.error(` Remove the file and re-run: npx -y @rubytech/create-maxy`);
|
|
2175
|
+
process.exit(1);
|
|
2176
|
+
}
|
|
2177
|
+
// Brand isolation (Task 659): a symlink at this path is either stale-same-brand,
|
|
2178
|
+
// dangling, unreadable, or pointing at another brand. All four cases are resolved
|
|
2179
|
+
// by last-writer-wins overwrite — setup-tunnel.sh takes <brand> as argv, so
|
|
2180
|
+
// whichever brand's installer ran last owns the shortcut.
|
|
2181
|
+
const resolvedTarget = (() => {
|
|
2182
|
+
try {
|
|
2183
|
+
return resolve(dirname(linkPath), readlinkSync(linkPath));
|
|
2184
|
+
}
|
|
2185
|
+
catch {
|
|
2186
|
+
return "<unreadable>";
|
|
2187
|
+
}
|
|
2188
|
+
})();
|
|
2189
|
+
if (resolvedTarget === targetAbs) {
|
|
2190
|
+
console.log(` [create-maxy] symlink ${linkPath} already points to ${targetAbs}`);
|
|
2191
|
+
logFile(` symlink unchanged: ${linkPath} → ${targetAbs}`);
|
|
2192
|
+
return;
|
|
2193
|
+
}
|
|
2194
|
+
unlinkSync(linkPath);
|
|
2195
|
+
symlinkSync(targetAbs, linkPath);
|
|
2196
|
+
console.log(` [create-maxy] symlink replaced: ${linkPath} → ${targetAbs}`);
|
|
2197
|
+
logFile(` symlink replaced: ${linkPath} was ${resolvedTarget}, now ${targetAbs}`);
|
|
2198
|
+
}
|
|
2199
|
+
function installTunnelScripts() {
|
|
2200
|
+
const setupSrc = join(INSTALL_DIR, "platform/plugins/cloudflare/scripts/setup-tunnel.sh");
|
|
2201
|
+
const resetSrc = join(INSTALL_DIR, "platform/plugins/cloudflare/scripts/reset-tunnel.sh");
|
|
2202
|
+
const listSrc = join(INSTALL_DIR, "platform/plugins/cloudflare/scripts/list-cf-domains.sh");
|
|
2203
|
+
const setupLink = resolve(process.env.HOME ?? "/root", "setup-tunnel.sh");
|
|
2204
|
+
const resetLink = resolve(process.env.HOME ?? "/root", "reset-tunnel.sh");
|
|
2205
|
+
const listLink = resolve(process.env.HOME ?? "/root", "list-cf-domains.sh");
|
|
2206
|
+
for (const src of [setupSrc, resetSrc, listSrc]) {
|
|
2207
|
+
try {
|
|
2208
|
+
chmodSync(src, 0o755);
|
|
2209
|
+
}
|
|
2210
|
+
catch (err) {
|
|
2211
|
+
console.error(`Setup failed: tunnel script missing or chmod failed: ${src}`);
|
|
2212
|
+
console.error(`[create-maxy:error] tunnel script missing or chmod failed: ${src}`);
|
|
2213
|
+
console.error(` ${err.message}`);
|
|
2214
|
+
process.exit(1);
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
createTunnelSymlink(setupLink, setupSrc);
|
|
2218
|
+
createTunnelSymlink(resetLink, resetSrc);
|
|
2219
|
+
createTunnelSymlink(listLink, listSrc);
|
|
2220
|
+
}
|
|
2221
|
+
// ---------------------------------------------------------------------------
|
|
2222
|
+
// Account discovery (shared between installService + installCrons)
|
|
2223
|
+
//
|
|
2224
|
+
// Task 955 — `installService` stamps `Environment=ACCOUNT_ID=` into the brand
|
|
2225
|
+
// systemd unit so the writeNodeWithEdges gate has a non-undefined identity to
|
|
2226
|
+
// compare against; `installCrons` needs the same value to scope cron stdout
|
|
2227
|
+
// to the per-account log dir + stamp ACCOUNT_ID into the cron entry env.
|
|
2228
|
+
// Both pull from `INSTALL_DIR/data/accounts/<uuid>/account.json` written by
|
|
2229
|
+
// seed-neo4j.sh during setupAccount(). One reader, one shape, one source of
|
|
2230
|
+
// truth — without sharing, the two callers would drift on classification of
|
|
2231
|
+
// `corrupt account.json` (one might count it, the other might not) and the
|
|
2232
|
+
// gate would reject writes the cron's "scoped" log was already happily
|
|
2233
|
+
// publishing under that uuid.
|
|
2234
|
+
// ---------------------------------------------------------------------------
|
|
2235
|
+
function resolveInstallAccountId() {
|
|
2236
|
+
const accountsDir = join(INSTALL_DIR, "data/accounts");
|
|
2237
|
+
if (!existsSync(accountsDir))
|
|
2238
|
+
return "";
|
|
2239
|
+
try {
|
|
2240
|
+
for (const d of readdirSync(accountsDir)) {
|
|
2241
|
+
if (existsSync(join(accountsDir, d, "account.json")))
|
|
2242
|
+
return d;
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
catch { /* directory unreadable */ }
|
|
2246
|
+
return "";
|
|
2247
|
+
}
|
|
2248
|
+
// ---------------------------------------------------------------------------
|
|
2249
|
+
// Cron Registration
|
|
2250
|
+
//
|
|
2251
|
+
// Registers platform cron jobs (heartbeat, email-fetch, email-auto-respond).
|
|
2252
|
+
// Uses BEGIN/END markers for idempotent replacement on re-install.
|
|
2253
|
+
// Crons persist across reboots — registered once at install time.
|
|
2254
|
+
// Email crons run unconditionally; scripts exit early if unconfigured.
|
|
2255
|
+
// ---------------------------------------------------------------------------
|
|
2256
|
+
const CRON_BLOCK_BEGIN = `# BEGIN ${BRAND.productName.toUpperCase()} CRONS`;
|
|
2257
|
+
const CRON_BLOCK_END = `# END ${BRAND.productName.toUpperCase()} CRONS`;
|
|
2258
|
+
function installCrons() {
|
|
2259
|
+
if (!isLinux())
|
|
2260
|
+
return;
|
|
2261
|
+
const nodeBin = spawnSync("which", ["node"], { encoding: "utf-8" }).stdout.trim() || "/usr/bin/node";
|
|
2262
|
+
const platformRoot = join(INSTALL_DIR, "platform");
|
|
2263
|
+
// Account discovery shared with installService — see resolveInstallAccountId.
|
|
2264
|
+
const accountId = resolveInstallAccountId();
|
|
2265
|
+
const accountsDir = join(INSTALL_DIR, "data/accounts");
|
|
2266
|
+
if (!accountId) {
|
|
2267
|
+
console.error(" Cron jobs: skipped — no account found. Crons will register on the next install after account creation.");
|
|
2268
|
+
logFile(" cron registration skipped: no account directory with account.json found");
|
|
2269
|
+
return;
|
|
2270
|
+
}
|
|
2271
|
+
const accountLogDir = join(accountsDir, accountId, "logs");
|
|
2272
|
+
// NEO4J_URI is explicit per brand so a secondary-brand install (dedicated
|
|
2273
|
+
// Neo4j on a non-default port) doesn't silently fall back to
|
|
2274
|
+
// bolt://localhost:7687 in cron — cron inherits none of the user's shell
|
|
2275
|
+
// env or .env file. Without this, the scheduler writes and reads from the
|
|
2276
|
+
// shared default instance, producing cross-install event leaks (Task 571).
|
|
2277
|
+
const accountEnv = `ACCOUNT_ID=${accountId} NEO4J_URI=bolt://localhost:${NEO4J_PORT} `;
|
|
2278
|
+
const cronEntries = [
|
|
2279
|
+
`* * * * * mkdir -p ${accountLogDir} && PLATFORM_ROOT=${platformRoot} ${accountEnv}${nodeBin} ${platformRoot}/plugins/scheduling/mcp/dist/scripts/check-due-events.js >> ${accountLogDir}/check-due-events.log 2>&1 # heartbeat`,
|
|
2280
|
+
`* * * * * mkdir -p ${accountLogDir} && PLATFORM_ROOT=${platformRoot} ${accountEnv}${nodeBin} ${platformRoot}/plugins/email/mcp/dist/scripts/email-fetch.js >> ${accountLogDir}/email-fetch.log 2>&1 # email-fetch`,
|
|
2281
|
+
`* * * * * mkdir -p ${accountLogDir} && PLATFORM_ROOT=${platformRoot} ${accountEnv}${nodeBin} ${platformRoot}/plugins/email/mcp/dist/scripts/email-auto-respond.js >> ${accountLogDir}/email-auto-respond.log 2>&1 # email-auto-respond`,
|
|
2282
|
+
];
|
|
2283
|
+
// Read existing crontab (empty string if none)
|
|
2284
|
+
const existing = spawnSync("crontab", ["-l"], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
2285
|
+
const currentCrontab = existing.status === 0 ? existing.stdout : "";
|
|
2286
|
+
// Strip any existing Maxy cron block
|
|
2287
|
+
const blockPattern = new RegExp(`${CRON_BLOCK_BEGIN}[\\s\\S]*?${CRON_BLOCK_END}\\n?`, "g");
|
|
2288
|
+
const cleaned = currentCrontab.replace(blockPattern, "").trimEnd();
|
|
2289
|
+
// Build new crontab with Maxy block
|
|
2290
|
+
const newBlock = [
|
|
2291
|
+
CRON_BLOCK_BEGIN,
|
|
2292
|
+
...cronEntries,
|
|
2293
|
+
CRON_BLOCK_END,
|
|
2294
|
+
].join("\n");
|
|
2295
|
+
const newCrontab = cleaned ? `${cleaned}\n${newBlock}\n` : `${newBlock}\n`;
|
|
2296
|
+
// Write the new crontab
|
|
2297
|
+
const write = spawnSync("crontab", ["-"], {
|
|
2298
|
+
input: newCrontab,
|
|
2299
|
+
encoding: "utf-8",
|
|
2300
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
2301
|
+
});
|
|
2302
|
+
if (write.status === 0) {
|
|
2303
|
+
console.log(" Cron jobs: registered (heartbeat, email-fetch, email-auto-respond)");
|
|
2304
|
+
}
|
|
2305
|
+
else {
|
|
2306
|
+
console.error(` Cron jobs: failed to register — ${(write.stderr || "").trim()}`);
|
|
2307
|
+
logFile(` crontab write failed: ${write.stderr}`);
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
// Task 664 retired the ttyd/tmux/xterm admin terminal stack. Upgrades run
|
|
2311
|
+
// via the action runner — `systemd-run --user` transient units spawned by
|
|
2312
|
+
// POST /api/admin/actions/upgrade — whose lifetime is independent of
|
|
2313
|
+
// maxy-ui, achieving the Task 647 invariant structurally rather than via
|
|
2314
|
+
// a peer edge service proxying a ttyd process. The installer no longer
|
|
2315
|
+
// provisions the ttyd binary, writes a tmux conf, or installs a ttyd
|
|
2316
|
+
// systemd unit. The corresponding admin UI (RemoteTerminal, TerminalOverlay,
|
|
2317
|
+
// xterm.js) was deleted in the same task.
|
|
2318
|
+
// Task 838 — reverse-DNS LaunchAgent label. Becomes both the plist's
|
|
2319
|
+
// <key>Label</key> value and the file basename
|
|
2320
|
+
// (`~/Library/LaunchAgents/com.rubytech.<hostname>.plist`). Per-brand so
|
|
2321
|
+
// installing brand B never displaces brand A's agent (mirrors the per-brand
|
|
2322
|
+
// systemd unit invariant from Task 662).
|
|
2323
|
+
function launchdLabel() {
|
|
2324
|
+
return `com.rubytech.${BRAND.hostname}`;
|
|
2325
|
+
}
|
|
2326
|
+
function launchAgentsDir() {
|
|
2327
|
+
return resolve(process.env.HOME ?? "/", "Library/LaunchAgents");
|
|
2328
|
+
}
|
|
2329
|
+
function plistPath() {
|
|
2330
|
+
return join(launchAgentsDir(), `${launchdLabel()}.plist`);
|
|
2331
|
+
}
|
|
2332
|
+
function gui() {
|
|
2333
|
+
// launchd's gui domain is keyed on the user's UID. process.getuid is only
|
|
2334
|
+
// defined on POSIX (always present on darwin); typed conservatively.
|
|
2335
|
+
const uid = typeof process.getuid === "function" ? process.getuid() : 0;
|
|
2336
|
+
return `gui/${uid}`;
|
|
2337
|
+
}
|
|
2338
|
+
// Task 838 — darwin LaunchAgent supervisor. Mirrors the systemd-user body
|
|
2339
|
+
// below at the success-criteria level: process registered with the user's
|
|
2340
|
+
// session manager, KeepAlive respawns on crash, RunAtLoad starts the agent
|
|
2341
|
+
// on every login. Out-of-scope today (Task 839): brew-resolved node path,
|
|
2342
|
+
// dedicated Neo4j on a non-default port. Falls back to /usr/local/bin/node
|
|
2343
|
+
// (homebrew Intel + manual Apple-silicon installs) — Task 839 will replace
|
|
2344
|
+
// this with the resolver.
|
|
2345
|
+
function installServiceDarwin() {
|
|
2346
|
+
const persistDir = resolve(process.env.HOME ?? "/", BRAND.configDir);
|
|
2347
|
+
const logsDir = join(persistDir, "logs");
|
|
2348
|
+
mkdirSync(logsDir, { recursive: true });
|
|
2349
|
+
// Write install-time config to .env (the server reads it directly on
|
|
2350
|
+
// darwin; launchd does not have systemd's EnvironmentFile primitive).
|
|
2351
|
+
const envPath = join(persistDir, ".env");
|
|
2352
|
+
try {
|
|
2353
|
+
let envContent = "";
|
|
2354
|
+
try {
|
|
2355
|
+
envContent = readFileSync(envPath, "utf-8");
|
|
2356
|
+
}
|
|
2357
|
+
catch { /* first install */ }
|
|
2358
|
+
for (const [key, value] of [
|
|
2359
|
+
["DISPLAY_MODE", DISPLAY_MODE],
|
|
2360
|
+
["EMBED_MODEL", EMBED_MODEL],
|
|
2361
|
+
["EMBED_DIMENSIONS", String(EMBED_DIMS)],
|
|
2362
|
+
["NEO4J_URI", `bolt://localhost:${NEO4J_PORT}`],
|
|
2363
|
+
["PORT", String(PORT)],
|
|
2364
|
+
["MAXY_PLATFORM_ROOT", `${INSTALL_DIR}/platform`],
|
|
2365
|
+
]) {
|
|
2366
|
+
const re = new RegExp(`^${key}=.*$`, "m");
|
|
2367
|
+
if (re.test(envContent)) {
|
|
2368
|
+
envContent = envContent.replace(re, `${key}=${value}`);
|
|
2369
|
+
}
|
|
2370
|
+
else {
|
|
2371
|
+
envContent = envContent.trimEnd() + (envContent.length > 0 ? "\n" : "") + `${key}=${value}\n`;
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
writeFileSync(envPath, envContent);
|
|
2375
|
+
logFile(` .env: DISPLAY_MODE=${DISPLAY_MODE}, EMBED_MODEL=${EMBED_MODEL}, EMBED_DIMENSIONS=${EMBED_DIMS}, NEO4J_URI=bolt://localhost:${NEO4J_PORT}, PORT=${PORT}`);
|
|
2376
|
+
}
|
|
2377
|
+
catch (err) {
|
|
2378
|
+
console.error(` WARNING: failed to write .env to ${envPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
2379
|
+
}
|
|
2380
|
+
// Render the plist. The wrapper shell script reads .env before exec'ing
|
|
2381
|
+
// node so the runtime config (PORT, MAXY_PLATFORM_ROOT, NEO4J_URI) lands
|
|
2382
|
+
// in the child env. Without this, ProgramArguments executes node directly
|
|
2383
|
+
// and the .env values are unread — server binds the wrong port.
|
|
2384
|
+
const wrapperPath = join(persistDir, "launchd-wrapper.sh");
|
|
2385
|
+
// Resolve node binary at install time so the wrapper picks the right
|
|
2386
|
+
// path on both Intel (/usr/local/bin/node) and Apple Silicon
|
|
2387
|
+
// (/opt/homebrew/bin/node) — Homebrew's prefix differs by arch.
|
|
2388
|
+
const nodeProbe = spawnSync("command", ["-v", "node"], { encoding: "utf-8", shell: true });
|
|
2389
|
+
const nodeBin = (nodeProbe.stdout ?? "").trim() || "/usr/local/bin/node";
|
|
2390
|
+
const wrapperBody = [
|
|
2391
|
+
"#!/bin/bash",
|
|
2392
|
+
"# Task 838 — generated by create-maxy installService(). Reads .env then",
|
|
2393
|
+
"# execs node so launchd's child inherits PORT, NEO4J_URI, etc. Replaces",
|
|
2394
|
+
"# the systemd EnvironmentFile= directive that has no launchd analogue.",
|
|
2395
|
+
`set -a; [ -f "${envPath}" ] && . "${envPath}"; set +a`,
|
|
2396
|
+
`cd "${INSTALL_DIR}/server"`,
|
|
2397
|
+
`exec ${nodeBin} --require ./server-init.cjs server.js`,
|
|
2398
|
+
"",
|
|
2399
|
+
].join("\n");
|
|
2400
|
+
writeFileSync(wrapperPath, wrapperBody);
|
|
2401
|
+
chmodSync(wrapperPath, 0o755);
|
|
2402
|
+
const label = launchdLabel();
|
|
2403
|
+
const plist = renderPlist({
|
|
2404
|
+
label,
|
|
2405
|
+
programArguments: ["/bin/bash", wrapperPath],
|
|
2406
|
+
stdoutPath: join(logsDir, "server.log"),
|
|
2407
|
+
stderrPath: join(logsDir, "server.log"),
|
|
2408
|
+
keepAlive: true,
|
|
2409
|
+
runAtLoad: true,
|
|
2410
|
+
workingDirectory: `${INSTALL_DIR}/server`,
|
|
2411
|
+
});
|
|
2412
|
+
mkdirSync(launchAgentsDir(), { recursive: true });
|
|
2413
|
+
const path = plistPath();
|
|
2414
|
+
writeFileSync(path, plist);
|
|
2415
|
+
logFile(` ${path} written (${plist.length} bytes)`);
|
|
2416
|
+
// Idempotent re-install: bootout the previous instance (if any) first so
|
|
2417
|
+
// the second `bootstrap` does not exit 5 ("already loaded"). Best-effort —
|
|
2418
|
+
// a missing service is the expected case on fresh installs.
|
|
2419
|
+
spawnSync("launchctl", ["bootout", `${gui()}/${label}`], { stdio: "pipe" });
|
|
2420
|
+
const bootstrap = spawnSync("launchctl", ["bootstrap", gui(), path], {
|
|
2421
|
+
stdio: "pipe",
|
|
2422
|
+
encoding: "utf-8",
|
|
2423
|
+
timeout: 15_000,
|
|
2424
|
+
});
|
|
2425
|
+
if (bootstrap.status === 0) {
|
|
2426
|
+
console.log(` [launchd] bootstrap ${gui()}/${label} ok`);
|
|
2427
|
+
logFile(` [launchd] bootstrap ${gui()}/${label} ok`);
|
|
2428
|
+
}
|
|
2429
|
+
else {
|
|
2430
|
+
const stderr = (bootstrap.stderr ?? "").trim();
|
|
2431
|
+
console.error(` [launchd] bootstrap returned ${bootstrap.status}: ${stderr}`);
|
|
2432
|
+
logFile(` [launchd] bootstrap returned ${bootstrap.status}: ${stderr}`);
|
|
2433
|
+
throw new Error(`launchctl bootstrap ${gui()} ${path} failed (exit ${bootstrap.status}): ${stderr}`);
|
|
2434
|
+
}
|
|
2435
|
+
// Wait for the server to come up.
|
|
2436
|
+
console.log(" Waiting for web server...");
|
|
2437
|
+
let webServerUp = false;
|
|
2438
|
+
for (let i = 0; i < 20; i++) {
|
|
2439
|
+
try {
|
|
2440
|
+
execFileSync("curl", ["-sf", `http://localhost:${PORT}`, "-o", "/dev/null"], { timeout: 3000 });
|
|
2441
|
+
webServerUp = true;
|
|
2442
|
+
break;
|
|
2443
|
+
}
|
|
2444
|
+
catch {
|
|
2445
|
+
spawnSync("sleep", ["2"]);
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
2448
|
+
if (!webServerUp) {
|
|
2449
|
+
console.log(` Server may still be starting. Check http://localhost:${PORT} in a moment.`);
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
function installService() {
|
|
2453
|
+
log("11", TOTAL, `Starting ${BRAND.productName}...`);
|
|
2454
|
+
// Task 838 — branch on the Task 836 ternary instead of the legacy
|
|
2455
|
+
// `isLinux()` boolean. Linux falls through to the systemd-user body
|
|
2456
|
+
// below; darwin renders + bootstraps a LaunchAgent and returns;
|
|
2457
|
+
// unsupported throws the literal refusal at requireSupportedPlatform.
|
|
2458
|
+
const platform = requireSupportedPlatform(process.platform);
|
|
2459
|
+
if (platform === "darwin") {
|
|
2460
|
+
installServiceDarwin();
|
|
2461
|
+
return;
|
|
2462
|
+
}
|
|
2463
|
+
// Persist UDP buffer sizes for cloudflared QUIC stability (applied on every boot via sysctl.d)
|
|
2464
|
+
const sysctlTmpPath = `/tmp/99-${BRAND.hostname}-quic.conf`;
|
|
2465
|
+
const sysctlDestPath = `/etc/sysctl.d/99-${BRAND.hostname}-quic.conf`;
|
|
2466
|
+
try {
|
|
2467
|
+
const sysctlConf = "net.core.rmem_max=7340032\nnet.core.wmem_max=7340032\n";
|
|
2468
|
+
writeFileSync(sysctlTmpPath, sysctlConf);
|
|
2469
|
+
console.log(" [privileged] cp");
|
|
2470
|
+
shell("cp", [sysctlTmpPath, sysctlDestPath], { sudo: true });
|
|
2471
|
+
spawnSync("rm", ["-f", sysctlTmpPath]);
|
|
2472
|
+
spawnSync("sudo", ["sysctl", "--system"], { stdio: "ignore", timeout: 10_000 });
|
|
2473
|
+
}
|
|
2474
|
+
catch { /* non-critical — values applied on next reboot */ }
|
|
2475
|
+
const serviceDir = resolve(process.env.HOME ?? "/root", ".config/systemd/user");
|
|
2476
|
+
mkdirSync(serviceDir, { recursive: true });
|
|
2477
|
+
// Create systemd user service
|
|
2478
|
+
const persistDir = resolve(process.env.HOME ?? "/root", BRAND.configDir);
|
|
2479
|
+
mkdirSync(join(persistDir, "logs"), { recursive: true });
|
|
2480
|
+
// Write install-time config to .env (systemd reads via EnvironmentFile).
|
|
2481
|
+
// Preserves existing .env content (e.g. PORT overrides) — only
|
|
2482
|
+
// replaces or appends each managed line.
|
|
2483
|
+
const envPath = join(persistDir, ".env");
|
|
2484
|
+
try {
|
|
2485
|
+
let envContent = "";
|
|
2486
|
+
try {
|
|
2487
|
+
envContent = readFileSync(envPath, "utf-8");
|
|
2488
|
+
}
|
|
2489
|
+
catch { /* first install */ }
|
|
2490
|
+
// DISPLAY_MODE
|
|
2491
|
+
if (/^DISPLAY_MODE=.*/m.test(envContent)) {
|
|
2492
|
+
envContent = envContent.replace(/^DISPLAY_MODE=.*/m, `DISPLAY_MODE=${DISPLAY_MODE}`);
|
|
2493
|
+
}
|
|
2494
|
+
else {
|
|
2495
|
+
envContent = envContent.trimEnd() + (envContent.length > 0 ? "\n" : "") + `DISPLAY_MODE=${DISPLAY_MODE}\n`;
|
|
2496
|
+
}
|
|
2497
|
+
// EMBED_MODEL
|
|
2498
|
+
if (/^EMBED_MODEL=.*/m.test(envContent)) {
|
|
2499
|
+
envContent = envContent.replace(/^EMBED_MODEL=.*/m, `EMBED_MODEL=${EMBED_MODEL}`);
|
|
2500
|
+
}
|
|
2501
|
+
else {
|
|
2502
|
+
envContent = envContent.trimEnd() + (envContent.length > 0 ? "\n" : "") + `EMBED_MODEL=${EMBED_MODEL}\n`;
|
|
2503
|
+
}
|
|
2504
|
+
// EMBED_DIMENSIONS
|
|
2505
|
+
if (/^EMBED_DIMENSIONS=.*/m.test(envContent)) {
|
|
2506
|
+
envContent = envContent.replace(/^EMBED_DIMENSIONS=.*/m, `EMBED_DIMENSIONS=${EMBED_DIMS}`);
|
|
2507
|
+
}
|
|
2508
|
+
else {
|
|
2509
|
+
envContent = envContent.trimEnd() + (envContent.length > 0 ? "\n" : "") + `EMBED_DIMENSIONS=${EMBED_DIMS}\n`;
|
|
2510
|
+
}
|
|
2511
|
+
// NEO4J_URI — always written so the platform connects to the correct instance.
|
|
2512
|
+
// For shared instances this is bolt://localhost:7687 (the default seed-neo4j.sh
|
|
2513
|
+
// would use anyway), but writing it explicitly makes the .env self-documenting
|
|
2514
|
+
// and ensures upgrade detection works for any future port change.
|
|
2515
|
+
const neo4jUri = `bolt://localhost:${NEO4J_PORT}`;
|
|
2516
|
+
if (/^NEO4J_URI=.*/m.test(envContent)) {
|
|
2517
|
+
envContent = envContent.replace(/^NEO4J_URI=.*/m, `NEO4J_URI=${neo4jUri}`);
|
|
2518
|
+
}
|
|
2519
|
+
else {
|
|
2520
|
+
envContent = envContent.trimEnd() + (envContent.length > 0 ? "\n" : "") + `NEO4J_URI=${neo4jUri}\n`;
|
|
2521
|
+
}
|
|
2522
|
+
writeFileSync(envPath, envContent);
|
|
2523
|
+
logFile(` .env: DISPLAY_MODE=${DISPLAY_MODE}, EMBED_MODEL=${EMBED_MODEL}, EMBED_DIMENSIONS=${EMBED_DIMS}, NEO4J_URI=${neo4jUri}`);
|
|
2524
|
+
}
|
|
2525
|
+
catch (err) {
|
|
2526
|
+
console.error(` WARNING: failed to write .env to ${envPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
2527
|
+
}
|
|
2528
|
+
// Propagate to child processes — seed-neo4j.sh reads both variables.
|
|
2529
|
+
process.env.EMBED_DIMENSIONS = String(EMBED_DIMS);
|
|
2530
|
+
process.env.NEO4J_URI = `bolt://localhost:${NEO4J_PORT}`;
|
|
2531
|
+
// Task 647: maxy-ui runs on an internal-only port so a restart does not
|
|
2532
|
+
// drop the public TCP socket. maxy-edge.service owns the public port and
|
|
2533
|
+
// the VNC stack; maxy-ui sits behind it on 127.0.0.1:MAXY_UI_INTERNAL_PORT.
|
|
2534
|
+
// PORT + 1 (derived) avoids a fixed-port collision if the operator chose a
|
|
2535
|
+
// non-default --port.
|
|
2536
|
+
//
|
|
2537
|
+
// Task 666 — PORT in maxy.service's Environment block is the PUBLIC port,
|
|
2538
|
+
// not the internal one. Previously we wrote `Environment=PORT=<internal>`,
|
|
2539
|
+
// which collided with the install-time reader further down, which
|
|
2540
|
+
// correctly treats Environment=PORT= as public. The overload caused +1
|
|
2541
|
+
// drift per upgrade: each run read the internal value, treated it as
|
|
2542
|
+
// public, wrote internal = old_internal + 1. maxy-ui now binds
|
|
2543
|
+
// MAXY_UI_INTERNAL_PORT (with a fallback to PORT for mixed-state installs).
|
|
2544
|
+
const MAXY_UI_INTERNAL_PORT = PORT + 1;
|
|
2545
|
+
const edgeUnitShort = `${BRAND.hostname}-edge`;
|
|
2546
|
+
const edgeUnitName = `${edgeUnitShort}.service`;
|
|
2547
|
+
// Per-brand X display (Task 553). Same value used for the edge unit's
|
|
2548
|
+
// DISPLAY env (stamped via __VNC_DISPLAY__ a few lines down) so the main
|
|
2549
|
+
// brand service and the edge service agree on which display Chromium runs.
|
|
2550
|
+
// Task 924 + 959 — brand.json (BRAND) is the single source of truth for
|
|
2551
|
+
// these fields at install time. The vncDisplay-derived offset rule lives
|
|
2552
|
+
// in the brand-creation tooling; at this point in the installer BRAND
|
|
2553
|
+
// already represents a parsed, validated brand manifest, and any missing
|
|
2554
|
+
// field is a brand-publish defect. Loud-fail rather than silently
|
|
2555
|
+
// substituting an offset (silent-fallback-masks-root-cause recurrence).
|
|
2556
|
+
if (typeof BRAND.vncDisplay !== "number") {
|
|
2557
|
+
console.error(`[create-maxy] error reason=cdp-port-unresolved brand=${BRAND.configDir} field=vncDisplay`);
|
|
2558
|
+
throw new Error(`brand.json missing required field: vncDisplay`);
|
|
2559
|
+
}
|
|
2560
|
+
const VNC_DISPLAY = BRAND.vncDisplay;
|
|
2561
|
+
for (const field of ["rfbPort", "websockifyPort", "cdpPort"]) {
|
|
2562
|
+
if (typeof BRAND[field] !== "number") {
|
|
2563
|
+
console.error(`[create-maxy] error reason=cdp-port-unresolved brand=${BRAND.configDir} field=${field}`);
|
|
2564
|
+
throw new Error(`brand.json missing required field: ${field}`);
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
const RFB_PORT = BRAND.rfbPort;
|
|
2568
|
+
const WEBSOCKIFY_PORT_BRAND = BRAND.websockifyPort;
|
|
2569
|
+
const CDP_PORT_BRAND = BRAND.cdpPort;
|
|
2570
|
+
const CLAUDE_SESSION_MANAGER_PORT_BRAND = BRAND.claudeSessionManagerPort;
|
|
2571
|
+
// Task 924/938 pre-flight — refuse to write service files if any of the
|
|
2572
|
+
// three brand-scoped ports is already held by a process that is NOT this
|
|
2573
|
+
// brand's own on-demand browser nor a peer brand's edge stack.
|
|
2574
|
+
//
|
|
2575
|
+
// Classification (Task 938 chromium, Task 939 Xtigervnc + websockify) reads
|
|
2576
|
+
// `/proc/<pid>/cmdline` and applies a holder-specific argv anchor. Task 938
|
|
2577
|
+
// covered chromium-only via `--user-data-dir=`; Task 939 closed the gap on
|
|
2578
|
+
// Xtigervnc (no such flag — anchor on the `:N` display literal) and
|
|
2579
|
+
// websockify (anchor on bind port). Brand identities (configDir,
|
|
2580
|
+
// vncDisplay, websockifyPort) come from brand-registry.json which the
|
|
2581
|
+
// bundler stamps at build time from every brands/<brand>/brand.json.
|
|
2582
|
+
//
|
|
2583
|
+
// Decisions per holder:
|
|
2584
|
+
// OWN_BRAND — SIGTERM, recheck, SIGKILL on stragglers, exit-1 only if
|
|
2585
|
+
// the port is still held after both signals.
|
|
2586
|
+
// PEER_BRAND — log OK and return (per-brand port sets are disjoint).
|
|
2587
|
+
// UNRELATED — refuse to write service files; emit operator override.
|
|
2588
|
+
// macOS dev hosts (no ss) fall through the catch and skip pre-flight
|
|
2589
|
+
// entirely — the runtime check in vnc.sh covers Linux production.
|
|
2590
|
+
const ownBrand = {
|
|
2591
|
+
configDir: BRAND.configDir,
|
|
2592
|
+
vncDisplay: VNC_DISPLAY,
|
|
2593
|
+
websockifyPort: WEBSOCKIFY_PORT_BRAND,
|
|
2594
|
+
};
|
|
2595
|
+
// Peer registry — load from payload/platform/config/brand-registry.json
|
|
2596
|
+
// when present (Task 939+ bundles). Older bundles ship without the
|
|
2597
|
+
// registry; in that case peerBrands stays empty. PEER_BRAND classification
|
|
2598
|
+
// for Xtigervnc/websockify is a defence-in-depth case anyway (port sets
|
|
2599
|
+
// are disjoint by Task 924), so the empty-list fallback is safe — peer
|
|
2600
|
+
// chromium will fall through to UNRELATED, matching pre-Task 938 behaviour
|
|
2601
|
+
// for the only realistic scenario (a stale peer browser on the wrong CDP
|
|
2602
|
+
// port).
|
|
2603
|
+
const peerBrands = (() => {
|
|
2604
|
+
const registryPath = join(PAYLOAD_DIR, "platform", "config", "brand-registry.json");
|
|
2605
|
+
if (!existsSync(registryPath)) {
|
|
2606
|
+
logFile(` [preflight] brand-registry.json not in payload — peer matching disabled`);
|
|
2607
|
+
return [];
|
|
2608
|
+
}
|
|
2609
|
+
try {
|
|
2610
|
+
const raw = JSON.parse(readFileSync(registryPath, "utf-8"));
|
|
2611
|
+
const entries = [];
|
|
2612
|
+
for (const b of raw.brands ?? []) {
|
|
2613
|
+
if (b.hostname === BRAND.hostname)
|
|
2614
|
+
continue;
|
|
2615
|
+
if (typeof b.configDir !== "string" || typeof b.vncDisplay !== "number" || typeof b.websockifyPort !== "number")
|
|
2616
|
+
continue;
|
|
2617
|
+
entries.push({ configDir: b.configDir, vncDisplay: b.vncDisplay, websockifyPort: b.websockifyPort });
|
|
2618
|
+
}
|
|
2619
|
+
return entries;
|
|
2620
|
+
}
|
|
2621
|
+
catch (err) {
|
|
2622
|
+
logFile(` [preflight] brand-registry.json parse failed: ${err instanceof Error ? err.message : String(err)} — peer matching disabled`);
|
|
2623
|
+
return [];
|
|
2624
|
+
}
|
|
2625
|
+
})();
|
|
2626
|
+
const ssReadHolder = (port) => {
|
|
2627
|
+
return execFileSync("ss", ["-tlnpH", `sport = :${port}`], {
|
|
2628
|
+
encoding: "utf-8", timeout: 3000, stdio: ["ignore", "pipe", "ignore"],
|
|
2629
|
+
});
|
|
2630
|
+
};
|
|
2631
|
+
// Pass raw NUL-separated cmdline to the classifier so it can argv-anchor
|
|
2632
|
+
// on `--user-data-dir=`. Replacing NUL with space here would defeat that.
|
|
2633
|
+
const readCmdline = (pid) => readFileSync(`/proc/${pid}/cmdline`, "utf-8");
|
|
2634
|
+
const sleepMs = (ms) => { spawnSync("sleep", [(ms / 1000).toString()]); };
|
|
2635
|
+
// Tightly scoped variant for retry-path ss reads. Failures here (timeout,
|
|
2636
|
+
// ENOMEM, signal) are structural — never the macOS-no-ss case (we already
|
|
2637
|
+
// succeeded once) — so they get a structured exit, not a stack trace.
|
|
2638
|
+
const ssReadOrAbort = (label, port) => {
|
|
2639
|
+
try {
|
|
2640
|
+
return ssReadHolder(port);
|
|
2641
|
+
}
|
|
2642
|
+
catch (err) {
|
|
2643
|
+
console.error(` ERROR: [preflight] ${label}=${port} ss recheck failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2644
|
+
console.error(` Resolve manually before retrying.`);
|
|
2645
|
+
process.exit(1);
|
|
2646
|
+
}
|
|
2647
|
+
};
|
|
2648
|
+
// Distinguish ESRCH (process already gone — expected) from EPERM/EINVAL
|
|
2649
|
+
// (alarming — signals we can't deliver, possibly a recycled pid). Returns
|
|
2650
|
+
// true for clean kill or ESRCH, false otherwise (caller logs a warning).
|
|
2651
|
+
const killNoThrow = (pid, signal) => {
|
|
2652
|
+
try {
|
|
2653
|
+
process.kill(pid, signal);
|
|
2654
|
+
return true;
|
|
2655
|
+
}
|
|
2656
|
+
catch (err) {
|
|
2657
|
+
const code = err.code;
|
|
2658
|
+
if (code === "ESRCH")
|
|
2659
|
+
return true;
|
|
2660
|
+
logFile(` [preflight] kill(${pid}, ${signal}) failed code=${code ?? "unknown"}`);
|
|
2661
|
+
return false;
|
|
2662
|
+
}
|
|
2663
|
+
};
|
|
2664
|
+
const classify = (ssOutput) => classifyPortHolder({
|
|
2665
|
+
ssOutput, ownBrand, peerBrands, getCmdline: readCmdline,
|
|
2666
|
+
});
|
|
2667
|
+
// Task 939 — log line varies by detected holder so the operator can see
|
|
2668
|
+
// which OWN_BRAND stack is being killed. The kill loop is identical for
|
|
2669
|
+
// all three holders (SIGTERM → 300ms → recheck → SIGKILL → recheck), so
|
|
2670
|
+
// only the announce line differs.
|
|
2671
|
+
const ownBrandAnnounceLine = (label, port, c) => {
|
|
2672
|
+
if (c.holderType === "xtigervnc") {
|
|
2673
|
+
return ` [preflight] ${label}=${port} held by OWN brand Xtigervnc display=:${c.vncDisplay} pid=${c.pid} — sending SIGTERM`;
|
|
2674
|
+
}
|
|
2675
|
+
if (c.holderType === "websockify") {
|
|
2676
|
+
return ` [preflight] ${label}=${port} held by OWN brand websockify pid=${c.pid} — sending SIGTERM`;
|
|
2677
|
+
}
|
|
2678
|
+
// Default — chromium / unknown OWN_BRAND
|
|
2679
|
+
return ` [preflight] ${label}=${port} held by OWN brand process pid=${c.pid} profile=${c.profilePath} — sending SIGTERM`;
|
|
2680
|
+
};
|
|
2681
|
+
const checkInstallPortFree = (label, port) => {
|
|
2682
|
+
let firstSsOutput;
|
|
2683
|
+
try {
|
|
2684
|
+
firstSsOutput = ssReadHolder(port);
|
|
2685
|
+
}
|
|
2686
|
+
catch (err) {
|
|
2687
|
+
// ss may not be present on macOS dev hosts — skip the pre-flight there
|
|
2688
|
+
// rather than abort the install. The runtime check in vnc.sh covers
|
|
2689
|
+
// production-like Linux installs where this matters. This catch is
|
|
2690
|
+
// narrow on purpose: only the first ss invocation may legitimately
|
|
2691
|
+
// fail (binary missing); retry-path failures use ssReadOrAbort.
|
|
2692
|
+
logFile(` [preflight] ${label}=${port} check skipped: ${err instanceof Error ? err.message : String(err)}`);
|
|
2693
|
+
return;
|
|
2694
|
+
}
|
|
2695
|
+
let r = classify(firstSsOutput);
|
|
2696
|
+
// ENOENT race — process exited between ss and cmdline read. Port is
|
|
2697
|
+
// probably free now; one re-check resolves it deterministically.
|
|
2698
|
+
if (r.cmdlineReadFailed)
|
|
2699
|
+
r = classify(ssReadOrAbort(label, port));
|
|
2700
|
+
if (r.kind === "EMPTY")
|
|
2701
|
+
return;
|
|
2702
|
+
if (r.kind === "PEER_BRAND") {
|
|
2703
|
+
logFile(` [preflight] ${label}=${port} held by a peer brand's stack — OK (per-brand ports are disjoint by construction)`);
|
|
2704
|
+
return;
|
|
2705
|
+
}
|
|
2706
|
+
if (r.kind === "OWN_BRAND" && r.pid !== undefined) {
|
|
2707
|
+
logFile(ownBrandAnnounceLine(label, port, r));
|
|
2708
|
+
killNoThrow(r.pid, "SIGTERM");
|
|
2709
|
+
sleepMs(300);
|
|
2710
|
+
const after = classify(ssReadOrAbort(label, port));
|
|
2711
|
+
if (after.kind === "EMPTY") {
|
|
2712
|
+
logFile(` [preflight] ${label}=${port} freed`);
|
|
2713
|
+
return;
|
|
2714
|
+
}
|
|
2715
|
+
if (after.kind === "OWN_BRAND" && after.pid === r.pid) {
|
|
2716
|
+
logFile(` [preflight] ${label}=${port} survived SIGTERM — sending SIGKILL`);
|
|
2717
|
+
killNoThrow(r.pid, "SIGKILL");
|
|
2718
|
+
sleepMs(300);
|
|
2719
|
+
const final = classify(ssReadOrAbort(label, port));
|
|
2720
|
+
if (final.kind === "EMPTY") {
|
|
2721
|
+
logFile(` [preflight] ${label}=${port} freed`);
|
|
2722
|
+
return;
|
|
2723
|
+
}
|
|
2724
|
+
console.error(` ERROR: [preflight] ${label}=${port} OWN_BRAND auto-kill failed pid=${r.pid} — resolve manually before retrying.`);
|
|
2725
|
+
process.exit(1);
|
|
2726
|
+
}
|
|
2727
|
+
// A different OWN_BRAND pid took the port. The brand's user services
|
|
2728
|
+
// are respawning Chromium — installer cannot win this race. Stop the
|
|
2729
|
+
// services, then retry.
|
|
2730
|
+
if (after.kind === "OWN_BRAND") {
|
|
2731
|
+
console.error(` ERROR: [preflight] ${label}=${port} brand respawned a new OWN_BRAND pid (was ${r.pid}, now ${after.pid}).`);
|
|
2732
|
+
console.error(` Stop the brand's user services first: \`systemctl --user stop ${BRAND.hostname}-edge ${BRAND.hostname}\`, then re-run the installer.`);
|
|
2733
|
+
process.exit(1);
|
|
2734
|
+
}
|
|
2735
|
+
// PEER_BRAND or UNRELATED took the port post-kill — fall through to
|
|
2736
|
+
// those branches by re-classifying the surviving holder.
|
|
2737
|
+
r = after;
|
|
2738
|
+
if (r.kind === "EMPTY")
|
|
2739
|
+
return;
|
|
2740
|
+
if (r.kind === "PEER_BRAND") {
|
|
2741
|
+
logFile(` [preflight] ${label}=${port} now held by a peer brand's stack — OK`);
|
|
2742
|
+
return;
|
|
2743
|
+
}
|
|
2744
|
+
// r.kind === "UNRELATED" — fall through to the operator-override block.
|
|
2745
|
+
}
|
|
2746
|
+
// UNRELATED — preserve the operator-override path verbatim.
|
|
2747
|
+
console.error(` ERROR: [preflight:collision] brand=${BRAND.hostname} ${label}=${port} held by an unrelated process:`);
|
|
2748
|
+
console.error(` ${firstSsOutput.trim()}`);
|
|
2749
|
+
if (r.cmdline)
|
|
2750
|
+
console.error(` cmdline: ${r.cmdline}`);
|
|
2751
|
+
console.error(` Refusing to write service files; resolve the collision before retrying.`);
|
|
2752
|
+
console.error(` Operator override: edit brands/${BRAND.hostname}/brand.json, set/add \`${label}\` to a free port, re-bundle, re-install.`);
|
|
2753
|
+
process.exit(1);
|
|
2754
|
+
};
|
|
2755
|
+
checkInstallPortFree("rfbPort", RFB_PORT);
|
|
2756
|
+
checkInstallPortFree("websockifyPort", WEBSOCKIFY_PORT_BRAND);
|
|
2757
|
+
checkInstallPortFree("cdpPort", CDP_PORT_BRAND);
|
|
2758
|
+
// Task 955 — ACCOUNT_ID stamped into the brand unit so the writeNodeWithEdges
|
|
2759
|
+
// gate at platform/lib/graph-write/src/index.ts:170 has a real identity to
|
|
2760
|
+
// compare against (instead of process.env.ACCOUNT_ID === undefined). Resolved
|
|
2761
|
+
// here AFTER setupAccount() ran upstream — seed-neo4j.sh wrote account.json,
|
|
2762
|
+
// so an empty resolution at this point is a corrupted install (e.g. the seed
|
|
2763
|
+
// failed silently, or accounts/ was wiped between setup and unit-write).
|
|
2764
|
+
const installAccountId = resolveInstallAccountId();
|
|
2765
|
+
if (!installAccountId) {
|
|
2766
|
+
throw new Error(`installService: no account discovered at ${INSTALL_DIR}/data/accounts/<uuid>/account.json — ` +
|
|
2767
|
+
`setupAccount() (seed-neo4j.sh) should have created one. Refusing to write a systemd unit ` +
|
|
2768
|
+
`without ACCOUNT_ID; the boot validator would FATAL on every restart.`);
|
|
2769
|
+
}
|
|
2770
|
+
const serviceFile = buildMaxyUnitFile({
|
|
2771
|
+
productName: BRAND.productName,
|
|
2772
|
+
brandHostname: BRAND.hostname,
|
|
2773
|
+
neo4jDedicated: NEO4J_DEDICATED,
|
|
2774
|
+
installDir: INSTALL_DIR,
|
|
2775
|
+
persistDir,
|
|
2776
|
+
port: PORT,
|
|
2777
|
+
maxyUiInternalPort: MAXY_UI_INTERNAL_PORT,
|
|
2778
|
+
vncDisplay: VNC_DISPLAY,
|
|
2779
|
+
rfbPort: RFB_PORT,
|
|
2780
|
+
websockifyPort: WEBSOCKIFY_PORT_BRAND,
|
|
2781
|
+
cdpPort: CDP_PORT_BRAND,
|
|
2782
|
+
claudeSessionManagerPort: CLAUDE_SESSION_MANAGER_PORT_BRAND, // Task 001 (maxy-code)
|
|
2783
|
+
chromiumBin: RESOLVED_CHROMIUM_BIN, // Task 929
|
|
2784
|
+
accountId: installAccountId, // Task 955
|
|
2785
|
+
});
|
|
2786
|
+
writeFileSync(join(serviceDir, BRAND.serviceName), serviceFile);
|
|
2787
|
+
// Task 001 (maxy-code) — write the claude-session-manager unit. The main
|
|
2788
|
+
// brand unit Requires= + After= this one, so systemd starts it first.
|
|
2789
|
+
const claudeSessionManagerUnitName = `${BRAND.hostname}-claude-session-manager.service`;
|
|
2790
|
+
const claudeSessionManagerUnit = buildClaudeSessionManagerUnitFile({
|
|
2791
|
+
productName: BRAND.productName,
|
|
2792
|
+
brandHostname: BRAND.hostname,
|
|
2793
|
+
installDir: INSTALL_DIR,
|
|
2794
|
+
persistDir,
|
|
2795
|
+
claudeSessionManagerPort: CLAUDE_SESSION_MANAGER_PORT_BRAND,
|
|
2796
|
+
accountId: installAccountId,
|
|
2797
|
+
});
|
|
2798
|
+
writeFileSync(join(serviceDir, claudeSessionManagerUnitName), claudeSessionManagerUnit);
|
|
2799
|
+
logFile(` ${claudeSessionManagerUnitName}: CLAUDE_SESSION_MANAGER_PORT=${CLAUDE_SESSION_MANAGER_PORT_BRAND}`);
|
|
2800
|
+
// Task 647 — the edge service: always-on front door that owns the public
|
|
2801
|
+
// port (PORT) and the VNC stack (Xtigervnc + websockify). Its lifecycle is
|
|
2802
|
+
// independent of the main brand service, so an in-place upgrade triggered
|
|
2803
|
+
// from the admin terminal can restart the main brand service without
|
|
2804
|
+
// disconnecting the browser's remote terminal WebSocket.
|
|
2805
|
+
//
|
|
2806
|
+
// Task 662: the unit is per-brand so two brands on the same device each
|
|
2807
|
+
// own their own edge listener on their own EDGE_PORT — installing brand B
|
|
2808
|
+
// never rewrites brand A's unit or steals brand A's public port. Upgrades
|
|
2809
|
+
// from pre-662 installs require the manual recovery paragraph in
|
|
2810
|
+
// .docs/deployment.md before re-running this installer; auto-migration is
|
|
2811
|
+
// intentionally scoped out.
|
|
2812
|
+
// VNC_DISPLAY (defined above for the main brand unit) is also stamped into
|
|
2813
|
+
// the edge unit so both services agree on the X display. Per-brand display
|
|
2814
|
+
// closes the cross-brand cookie leak documented in Task 553.
|
|
2815
|
+
const edgeTemplatePath = resolve(INSTALL_DIR, "platform/templates/systemd/edge.service.template");
|
|
2816
|
+
if (existsSync(edgeTemplatePath)) {
|
|
2817
|
+
const edgeServiceContent = readFileSync(edgeTemplatePath, "utf-8")
|
|
2818
|
+
.replace(/__INSTALL_DIR__/g, INSTALL_DIR)
|
|
2819
|
+
.replace(/__EDGE_PORT__/g, String(PORT))
|
|
2820
|
+
.replace(/__MAXY_UI_PORT__/g, String(MAXY_UI_INTERNAL_PORT))
|
|
2821
|
+
.replace(/__PERSIST_DIR__/g, persistDir)
|
|
2822
|
+
.replace(/__VNC_DISPLAY__/g, String(VNC_DISPLAY))
|
|
2823
|
+
.replace(/__WEBSOCKIFY_PORT__/g, String(WEBSOCKIFY_PORT_BRAND))
|
|
2824
|
+
.replace(/__CDP_PORT__/g, String(CDP_PORT_BRAND))
|
|
2825
|
+
.replace(/__RFB_PORT__/g, String(RFB_PORT));
|
|
2826
|
+
writeFileSync(join(serviceDir, edgeUnitName), edgeServiceContent);
|
|
2827
|
+
logFile(` ${edgeUnitName}: EDGE_PORT=${PORT} MAXY_UI_PORT=${MAXY_UI_INTERNAL_PORT} VNC_DISPLAY=:${VNC_DISPLAY} RFB_PORT=${RFB_PORT} WEBSOCKIFY_PORT=${WEBSOCKIFY_PORT_BRAND} CDP_PORT=${CDP_PORT_BRAND}`);
|
|
2828
|
+
}
|
|
2829
|
+
else {
|
|
2830
|
+
console.error(` WARNING: edge.service.template missing at ${edgeTemplatePath} — VNC transport unavailable`);
|
|
2831
|
+
}
|
|
2832
|
+
// Task 560: the unit declares Environment=PATH=%h/.local/bin:... so the graph
|
|
2833
|
+
// MCP shim's spawn("uvx", ...) resolves against uv's install location. Without
|
|
2834
|
+
// this line, the service inherits the default systemd-user PATH — which
|
|
2835
|
+
// excludes ~/.local/bin — and the shim exits ENOENT before any query lands.
|
|
2836
|
+
logFile(` ${BRAND.serviceName}: PATH env includes %h/.local/bin for uvx resolution`);
|
|
2837
|
+
// WiFi AP provisioning service — system-level (requires root for hostapd/dnsmasq).
|
|
2838
|
+
// Runs at boot to check connectivity. If no saved WiFi and no ethernet, activates
|
|
2839
|
+
// a temporary AP with a captive portal for first-time WiFi configuration.
|
|
2840
|
+
const currentUser = execFileSync("whoami", [], { encoding: "utf-8" }).trim();
|
|
2841
|
+
const wifiProvisionService = `[Unit]
|
|
2842
|
+
Description=${BRAND.productName} WiFi Provisioning
|
|
2843
|
+
After=NetworkManager.service
|
|
2844
|
+
Before=${BRAND.serviceName}
|
|
2845
|
+
|
|
2846
|
+
[Service]
|
|
2847
|
+
Type=simple
|
|
2848
|
+
ExecStart=/bin/bash ${INSTALL_DIR}/platform/scripts/wifi-provision.sh
|
|
2849
|
+
ExecStopPost=/bin/bash ${INSTALL_DIR}/platform/scripts/wifi-provision.sh cleanup
|
|
2850
|
+
Environment=MAXY_PLATFORM_ROOT=${INSTALL_DIR}/platform
|
|
2851
|
+
Environment=INSTALL_USER=${currentUser}
|
|
2852
|
+
Environment=INSTALL_HOME=${process.env.HOME ?? "/home/" + currentUser}
|
|
2853
|
+
TimeoutStartSec=300
|
|
2854
|
+
TimeoutStopSec=30
|
|
2855
|
+
|
|
2856
|
+
[Install]
|
|
2857
|
+
WantedBy=multi-user.target
|
|
2858
|
+
`;
|
|
2859
|
+
const systemServiceDir = "/etc/systemd/system";
|
|
2860
|
+
const wifiProvisionPath = join(systemServiceDir, "wifi-provision.service");
|
|
2861
|
+
try {
|
|
2862
|
+
const tmpPath = "/tmp/wifi-provision.service";
|
|
2863
|
+
writeFileSync(tmpPath, wifiProvisionService);
|
|
2864
|
+
console.log(" [privileged] cp");
|
|
2865
|
+
shell("cp", [tmpPath, wifiProvisionPath], { sudo: true });
|
|
2866
|
+
spawnSync("rm", ["-f", tmpPath]);
|
|
2867
|
+
spawnSync("sudo", ["systemctl", "daemon-reload"], { stdio: "inherit" });
|
|
2868
|
+
spawnSync("sudo", ["systemctl", "enable", "wifi-provision"], { stdio: "inherit" });
|
|
2869
|
+
logFile(" WiFi provisioning service installed and enabled");
|
|
2870
|
+
}
|
|
2871
|
+
catch (err) {
|
|
2872
|
+
console.error(` WARNING: Failed to install wifi-provision service: ${err instanceof Error ? err.message : String(err)}`);
|
|
2873
|
+
}
|
|
2874
|
+
// Disable hostapd and dnsmasq system services (we manage them manually
|
|
2875
|
+
// from wifi-provision.sh — the system services would conflict).
|
|
2876
|
+
spawnSync("sudo", ["systemctl", "stop", "hostapd"], { stdio: "ignore" });
|
|
2877
|
+
spawnSync("sudo", ["systemctl", "disable", "hostapd"], { stdio: "ignore" });
|
|
2878
|
+
spawnSync("sudo", ["systemctl", "stop", "dnsmasq"], { stdio: "ignore" });
|
|
2879
|
+
spawnSync("sudo", ["systemctl", "disable", "dnsmasq"], { stdio: "ignore" });
|
|
2880
|
+
// Enable lingering so user services run without login
|
|
2881
|
+
try {
|
|
2882
|
+
spawnSync("sudo", ["loginctl", "enable-linger", currentUser], { stdio: "inherit" });
|
|
2883
|
+
}
|
|
2884
|
+
catch { /* not critical */ }
|
|
2885
|
+
// Reload and (re)start.
|
|
2886
|
+
//
|
|
2887
|
+
// Task 647 ordering: on upgrades, the old main brand service still holds
|
|
2888
|
+
// the public port (PORT). Stop it FIRST so the edge can bind that socket;
|
|
2889
|
+
// starting the edge first would race against the old main brand service
|
|
2890
|
+
// and fail with EADDRINUSE. Fresh installs: the stop is a no-op.
|
|
2891
|
+
const unitName = BRAND.serviceName.replace(".service", "");
|
|
2892
|
+
const claudeSessionManagerUnitShort = claudeSessionManagerUnitName.replace(".service", "");
|
|
2893
|
+
spawnSync("systemctl", ["--user", "stop", unitName], { stdio: "inherit" });
|
|
2894
|
+
spawnSync("systemctl", ["--user", "daemon-reload"], { stdio: "inherit" });
|
|
2895
|
+
spawnSync("systemctl", ["--user", "enable", edgeUnitShort], { stdio: "inherit" });
|
|
2896
|
+
spawnSync("systemctl", ["--user", "enable", claudeSessionManagerUnitShort], { stdio: "inherit" });
|
|
2897
|
+
spawnSync("systemctl", ["--user", "enable", unitName], { stdio: "inherit" });
|
|
2898
|
+
// edge first: binds public port + starts VNC stack. Then claude-session-manager
|
|
2899
|
+
// (so the main brand unit's Requires= is satisfied). Then main brand service.
|
|
2900
|
+
spawnSync("systemctl", ["--user", "restart", edgeUnitShort], { stdio: "inherit" });
|
|
2901
|
+
spawnSync("systemctl", ["--user", "restart", claudeSessionManagerUnitShort], { stdio: "inherit" });
|
|
2902
|
+
spawnSync("systemctl", ["--user", "restart", unitName], { stdio: "inherit" });
|
|
2903
|
+
// Wait for the server to come up
|
|
2904
|
+
console.log(" Waiting for web server...");
|
|
2905
|
+
let webServerUp = false;
|
|
2906
|
+
for (let i = 0; i < 20; i++) {
|
|
2907
|
+
try {
|
|
2908
|
+
execFileSync("curl", ["-sf", `http://localhost:${PORT}`, "-o", "/dev/null"], { timeout: 3000 });
|
|
2909
|
+
webServerUp = true;
|
|
2910
|
+
break;
|
|
2911
|
+
}
|
|
2912
|
+
catch {
|
|
2913
|
+
spawnSync("sleep", ["2"]);
|
|
2914
|
+
}
|
|
2915
|
+
}
|
|
2916
|
+
if (!webServerUp) {
|
|
2917
|
+
console.log(` Server may still be starting. Check http://${DEVICE_HOSTNAME}.local:${PORT} in a moment.`);
|
|
2918
|
+
}
|
|
2919
|
+
// Register cron jobs — depends on web server, independent of CDP
|
|
2920
|
+
installCrons();
|
|
2921
|
+
// Validate CDP: the programmatic Playwright MCP server connects to Chromium
|
|
2922
|
+
// via --cdp-endpoint http://127.0.0.1:9222. In virtual (VNC) mode, Chromium is
|
|
2923
|
+
// started by vnc.sh (ExecStartPre) before the web server — a failed probe here
|
|
2924
|
+
// indicates a genuine VNC boot failure and must fail the install loudly.
|
|
2925
|
+
// In native mode, Chromium is launched on-demand by the server and is not
|
|
2926
|
+
// expected to be bound at install time — the probe is skipped.
|
|
2927
|
+
if (DISPLAY_MODE === "native") {
|
|
2928
|
+
console.log(" [cdp-check] skipped reason=native-display (on-demand Chromium)");
|
|
2929
|
+
}
|
|
2930
|
+
else {
|
|
2931
|
+
console.log(` Verifying browser automation (CDP on port ${CDP_PORT_BRAND})...`);
|
|
2932
|
+
const cdpCheck = spawnSync("curl", ["-sf", `http://127.0.0.1:${CDP_PORT_BRAND}/json/version`, "-o", "/dev/null"], {
|
|
2933
|
+
timeout: 5000,
|
|
2934
|
+
stdio: "pipe",
|
|
2935
|
+
});
|
|
2936
|
+
if (cdpCheck.status === 0) {
|
|
2937
|
+
console.log(" Browser automation ready (CDP connected).");
|
|
2938
|
+
}
|
|
2939
|
+
else {
|
|
2940
|
+
const vncLogPath = resolve(process.env.HOME ?? "/root", BRAND.configDir, "logs/vnc-boot.log");
|
|
2941
|
+
let vncLog = "";
|
|
2942
|
+
try {
|
|
2943
|
+
vncLog = readFileSync(vncLogPath, "utf-8").slice(-2000);
|
|
2944
|
+
}
|
|
2945
|
+
catch {
|
|
2946
|
+
vncLog = `(no boot log found at ${vncLogPath})`;
|
|
2947
|
+
}
|
|
2948
|
+
console.error("");
|
|
2949
|
+
console.error(`Setup failed: Browser automation unavailable — CDP port ${CDP_PORT_BRAND} not responding`);
|
|
2950
|
+
console.error(` ERROR: Browser automation unavailable — CDP port ${CDP_PORT_BRAND} not responding.`);
|
|
2951
|
+
console.error(" Chromium should be started by vnc.sh (ExecStartPre). Check the boot log:");
|
|
2952
|
+
console.error("");
|
|
2953
|
+
console.error(vncLog);
|
|
2954
|
+
process.exit(1);
|
|
2955
|
+
}
|
|
2956
|
+
}
|
|
2957
|
+
// Tunnel script smoke check — the cloudflare SKILL contract invokes
|
|
2958
|
+
// ~/setup-tunnel.sh directly. If the install-time symlink creation in
|
|
2959
|
+
// installTunnelScripts() didn't land, fail the install loudly here rather
|
|
2960
|
+
// than shipping the exact Task 456 gap Task 555 exists to close.
|
|
2961
|
+
for (const linkPath of [
|
|
2962
|
+
resolve(process.env.HOME ?? "/root", "setup-tunnel.sh"),
|
|
2963
|
+
resolve(process.env.HOME ?? "/root", "reset-tunnel.sh"),
|
|
2964
|
+
resolve(process.env.HOME ?? "/root", "list-cf-domains.sh"),
|
|
2965
|
+
]) {
|
|
2966
|
+
try {
|
|
2967
|
+
accessSync(linkPath, fsConstants.X_OK);
|
|
2968
|
+
console.log(` [create-maxy] verify: ${linkPath} executable ✓`);
|
|
2969
|
+
}
|
|
2970
|
+
catch (err) {
|
|
2971
|
+
console.error("");
|
|
2972
|
+
console.error(`Setup failed: tunnel script symlink missing or not executable: ${linkPath}`);
|
|
2973
|
+
console.error(` [create-maxy:error] tunnel script symlink missing or not executable: ${linkPath}`);
|
|
2974
|
+
console.error(` ${err.message}`);
|
|
2975
|
+
process.exit(1);
|
|
2976
|
+
}
|
|
2977
|
+
}
|
|
2978
|
+
}
|
|
2979
|
+
// ---------------------------------------------------------------------------
|
|
2980
|
+
// Main
|
|
2981
|
+
// ---------------------------------------------------------------------------
|
|
2982
|
+
// Route to uninstall if --uninstall flag is present
|
|
2983
|
+
const _args = process.argv.slice(2);
|
|
2984
|
+
if (_args.includes("--uninstall")) {
|
|
2985
|
+
const { runUninstall } = await import("./uninstall.js");
|
|
2986
|
+
const exportIdx = _args.indexOf("--export-data");
|
|
2987
|
+
const exportPath = exportIdx !== -1 ? _args[exportIdx + 1] : undefined;
|
|
2988
|
+
const skipConfirm = _args.includes("--yes");
|
|
2989
|
+
if (exportIdx !== -1 && !exportPath) {
|
|
2990
|
+
console.error("Setup failed: --export-data requires a path argument");
|
|
2991
|
+
console.error("--export-data requires a path argument.");
|
|
2992
|
+
process.exit(1);
|
|
2993
|
+
}
|
|
2994
|
+
await runUninstall({ exportPath, skipConfirm });
|
|
2995
|
+
process.exit(0);
|
|
2996
|
+
}
|
|
2997
|
+
// ---------------------------------------------------------------------------
|
|
2998
|
+
// Port — install-time flag, not a brand attribute.
|
|
2999
|
+
//
|
|
3000
|
+
// Priority: --port flag > .env override > existing service file > default 19200.
|
|
3001
|
+
// systemd applies EnvironmentFile=-~/.{brand}/.env AFTER Environment=PORT,
|
|
3002
|
+
// so .env is the final runtime truth and must be checked first on upgrade.
|
|
3003
|
+
//
|
|
3004
|
+
// Task 666 — this block also performs one-shot port-drift recovery against
|
|
3005
|
+
// maxy-edge.service's Environment=EDGE_PORT=. See port-resolution.ts for the
|
|
3006
|
+
// pure logic; unit tests live at __tests__/port-canonicalisation.test.mjs.
|
|
3007
|
+
// ---------------------------------------------------------------------------
|
|
3008
|
+
let portFlag;
|
|
3009
|
+
const portIdx = _args.indexOf("--port");
|
|
3010
|
+
if (portIdx !== -1) {
|
|
3011
|
+
const raw = _args[portIdx + 1];
|
|
3012
|
+
const parsed = raw ? parseInt(raw, 10) : NaN;
|
|
3013
|
+
if (isNaN(parsed) || parsed < 1024 || parsed > 65535) {
|
|
3014
|
+
console.error(`Setup failed: --port requires a numeric value between 1024 and 65535 (got: ${raw ?? "nothing"})`);
|
|
3015
|
+
console.error(`Error: --port requires a numeric value between 1024 and 65535 (got: ${raw ?? "nothing"}).`);
|
|
3016
|
+
process.exit(1);
|
|
3017
|
+
}
|
|
3018
|
+
portFlag = parsed;
|
|
3019
|
+
}
|
|
3020
|
+
const _portResolution = resolveInstallPortFromFs({
|
|
3021
|
+
portFlag,
|
|
3022
|
+
persistDir: resolve(process.env.HOME ?? "/root", BRAND.configDir),
|
|
3023
|
+
serviceDir: resolve(process.env.HOME ?? "/root", ".config/systemd/user"),
|
|
3024
|
+
brandServiceFileName: BRAND.serviceName,
|
|
3025
|
+
brandEdgeServiceFileName: `${BRAND.hostname}-edge.service`,
|
|
3026
|
+
});
|
|
3027
|
+
let PORT = _portResolution.port;
|
|
3028
|
+
let PORT_SOURCE = _portResolution.source;
|
|
3029
|
+
for (const line of _portResolution.driftLogs)
|
|
3030
|
+
console.log(line);
|
|
3031
|
+
// ---------------------------------------------------------------------------
|
|
3032
|
+
// Hostname — install-time flag, not solely a brand attribute.
|
|
3033
|
+
//
|
|
3034
|
+
// Priority: --hostname flag > OS detection (same-brand upgrade) > OS preservation
|
|
3035
|
+
// (another brand's service detected) > BRAND.hostname (fresh install).
|
|
3036
|
+
// When --hostname is provided, it is set unconditionally — no detection, no preservation.
|
|
3037
|
+
// ---------------------------------------------------------------------------
|
|
3038
|
+
let HOSTNAME_FLAG;
|
|
3039
|
+
const hostnameIdx = _args.indexOf("--hostname");
|
|
3040
|
+
if (hostnameIdx !== -1) {
|
|
3041
|
+
const raw = _args[hostnameIdx + 1];
|
|
3042
|
+
if (!raw || raw.startsWith("--")) {
|
|
3043
|
+
console.error("Setup failed: --hostname requires a value");
|
|
3044
|
+
console.error("Error: --hostname requires a value (e.g. --hostname muvin).");
|
|
3045
|
+
process.exit(1);
|
|
3046
|
+
}
|
|
3047
|
+
// RFC 1123: lowercase alphanumeric + hyphens, max 63 chars, no leading/trailing hyphen
|
|
3048
|
+
if (!/^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/.test(raw)) {
|
|
3049
|
+
console.error(`Setup failed: --hostname value '${raw}' is invalid`);
|
|
3050
|
+
console.error(`Error: --hostname value '${raw}' is invalid. Must be lowercase letters, digits, and hyphens only (max 63 chars, no leading/trailing hyphen).`);
|
|
3051
|
+
process.exit(1);
|
|
3052
|
+
}
|
|
3053
|
+
HOSTNAME_FLAG = raw;
|
|
3054
|
+
}
|
|
3055
|
+
let DISPLAY_MODE = "virtual";
|
|
3056
|
+
let DISPLAY_MODE_SOURCE = "default";
|
|
3057
|
+
const displayIdx = _args.indexOf("--display");
|
|
3058
|
+
if (displayIdx !== -1) {
|
|
3059
|
+
const raw = _args[displayIdx + 1];
|
|
3060
|
+
if (!raw || raw.startsWith("--")) {
|
|
3061
|
+
console.error("Setup failed: --display requires a value");
|
|
3062
|
+
console.error("Error: --display requires a value: native or virtual.");
|
|
3063
|
+
process.exit(1);
|
|
3064
|
+
}
|
|
3065
|
+
if (raw !== "native" && raw !== "virtual") {
|
|
3066
|
+
console.error(`Setup failed: --display value '${raw}' is invalid`);
|
|
3067
|
+
console.error(`Error: --display value '${raw}' is invalid. Must be 'native' or 'virtual'.`);
|
|
3068
|
+
process.exit(1);
|
|
3069
|
+
}
|
|
3070
|
+
DISPLAY_MODE = raw;
|
|
3071
|
+
DISPLAY_MODE_SOURCE = "--display flag";
|
|
3072
|
+
}
|
|
3073
|
+
else {
|
|
3074
|
+
// Upgrade detection: preserve existing DISPLAY_MODE from .env
|
|
3075
|
+
const persistDir = resolve(process.env.HOME ?? "/root", BRAND.configDir);
|
|
3076
|
+
const envPath = join(persistDir, ".env");
|
|
3077
|
+
try {
|
|
3078
|
+
if (existsSync(envPath)) {
|
|
3079
|
+
const envContent = readFileSync(envPath, "utf-8");
|
|
3080
|
+
const envMatch = envContent.match(/^DISPLAY_MODE=(native|virtual)$/m);
|
|
3081
|
+
if (envMatch) {
|
|
3082
|
+
DISPLAY_MODE = envMatch[1];
|
|
3083
|
+
DISPLAY_MODE_SOURCE = ".env (preserved)";
|
|
3084
|
+
}
|
|
3085
|
+
}
|
|
3086
|
+
}
|
|
3087
|
+
catch { /* non-critical */ }
|
|
3088
|
+
}
|
|
3089
|
+
// ---------------------------------------------------------------------------
|
|
3090
|
+
// Embedding model — install-time flag, not a brand attribute.
|
|
3091
|
+
//
|
|
3092
|
+
// Controls which Ollama embedding model is pulled and the dimension count used
|
|
3093
|
+
// for Neo4j vector indexes. Dimensions are fixed at schema-creation time; changing
|
|
3094
|
+
// them later requires dropping and recreating all vector indexes.
|
|
3095
|
+
//
|
|
3096
|
+
// Priority: --embed-model flag > .env (upgrade) > default nomic-embed-text.
|
|
3097
|
+
// --embed-dimensions is required for models not in the curated lookup table.
|
|
3098
|
+
// --embed-dimensions without --embed-model is rejected (dimensions are model-specific).
|
|
3099
|
+
// ---------------------------------------------------------------------------
|
|
3100
|
+
const EMBED_MODEL_DIMS = {
|
|
3101
|
+
"nomic-embed-text": 768,
|
|
3102
|
+
"nomic-embed-text-v1.5": 768,
|
|
3103
|
+
"mxbai-embed-large": 1024,
|
|
3104
|
+
"snowflake-arctic-embed:335m": 768,
|
|
3105
|
+
};
|
|
3106
|
+
const DEFAULT_EMBED_MODEL = "nomic-embed-text";
|
|
3107
|
+
const DEFAULT_EMBED_DIMS = 768;
|
|
3108
|
+
let EMBED_MODEL = DEFAULT_EMBED_MODEL;
|
|
3109
|
+
let EMBED_DIMS = DEFAULT_EMBED_DIMS;
|
|
3110
|
+
let EMBED_SOURCE = "default";
|
|
3111
|
+
const embedModelIdx = _args.indexOf("--embed-model");
|
|
3112
|
+
const embedDimsIdx = _args.indexOf("--embed-dimensions");
|
|
3113
|
+
if (embedDimsIdx !== -1 && embedModelIdx === -1) {
|
|
3114
|
+
console.error("Setup failed: --embed-dimensions requires --embed-model");
|
|
3115
|
+
console.error("Error: --embed-dimensions requires --embed-model (dimensions are model-specific).");
|
|
3116
|
+
process.exit(1);
|
|
3117
|
+
}
|
|
3118
|
+
if (embedModelIdx !== -1) {
|
|
3119
|
+
const raw = _args[embedModelIdx + 1];
|
|
3120
|
+
if (!raw || raw.startsWith("--")) {
|
|
3121
|
+
console.error("Setup failed: --embed-model requires a value");
|
|
3122
|
+
console.error("Error: --embed-model requires a value (e.g. --embed-model mxbai-embed-large).");
|
|
3123
|
+
process.exit(1);
|
|
3124
|
+
}
|
|
3125
|
+
EMBED_MODEL = raw;
|
|
3126
|
+
EMBED_SOURCE = "--embed-model flag";
|
|
3127
|
+
if (embedDimsIdx !== -1) {
|
|
3128
|
+
// Explicit dimensions override the lookup table
|
|
3129
|
+
const rawDims = _args[embedDimsIdx + 1];
|
|
3130
|
+
const parsed = rawDims ? parseInt(rawDims, 10) : NaN;
|
|
3131
|
+
if (isNaN(parsed) || parsed <= 0) {
|
|
3132
|
+
console.error(`Setup failed: --embed-dimensions requires a positive integer (got: ${rawDims ?? "nothing"})`);
|
|
3133
|
+
console.error(`Error: --embed-dimensions requires a positive integer (got: ${rawDims ?? "nothing"}).`);
|
|
3134
|
+
process.exit(1);
|
|
3135
|
+
}
|
|
3136
|
+
EMBED_DIMS = parsed;
|
|
3137
|
+
}
|
|
3138
|
+
else if (EMBED_MODEL in EMBED_MODEL_DIMS) {
|
|
3139
|
+
// Known model — resolve dimensions from lookup table
|
|
3140
|
+
EMBED_DIMS = EMBED_MODEL_DIMS[EMBED_MODEL];
|
|
3141
|
+
}
|
|
3142
|
+
else {
|
|
3143
|
+
// Unknown model without explicit dimensions — cannot proceed
|
|
3144
|
+
console.error(`Setup failed: unknown embedding model '${EMBED_MODEL}'`);
|
|
3145
|
+
console.error(`Error: Unknown embedding model '${EMBED_MODEL}'.`);
|
|
3146
|
+
console.error("Known models and their dimensions:");
|
|
3147
|
+
for (const [model, dims] of Object.entries(EMBED_MODEL_DIMS)) {
|
|
3148
|
+
console.error(` ${model} — ${dims} dimensions`);
|
|
3149
|
+
}
|
|
3150
|
+
console.error("\nFor a custom model, pass --embed-dimensions N explicitly:");
|
|
3151
|
+
console.error(` npx -y @rubytech/create-maxy --embed-model ${EMBED_MODEL} --embed-dimensions 512`);
|
|
3152
|
+
process.exit(1);
|
|
3153
|
+
}
|
|
3154
|
+
}
|
|
3155
|
+
else {
|
|
3156
|
+
// No --embed-model flag: check .env for upgrade preservation
|
|
3157
|
+
const persistDir = resolve(process.env.HOME ?? "/root", BRAND.configDir);
|
|
3158
|
+
const envPath = join(persistDir, ".env");
|
|
3159
|
+
try {
|
|
3160
|
+
if (existsSync(envPath)) {
|
|
3161
|
+
const envContent = readFileSync(envPath, "utf-8");
|
|
3162
|
+
const modelMatch = envContent.match(/^EMBED_MODEL=(.+)$/m);
|
|
3163
|
+
const dimsMatch = envContent.match(/^EMBED_DIMENSIONS=(\d+)$/m);
|
|
3164
|
+
if (modelMatch) {
|
|
3165
|
+
EMBED_MODEL = modelMatch[1];
|
|
3166
|
+
EMBED_SOURCE = ".env (preserved)";
|
|
3167
|
+
if (dimsMatch) {
|
|
3168
|
+
EMBED_DIMS = parseInt(dimsMatch[1], 10);
|
|
3169
|
+
}
|
|
3170
|
+
else if (EMBED_MODEL in EMBED_MODEL_DIMS) {
|
|
3171
|
+
EMBED_DIMS = EMBED_MODEL_DIMS[EMBED_MODEL];
|
|
3172
|
+
}
|
|
3173
|
+
// Unknown model without dims in .env: keep DEFAULT_EMBED_DIMS — the schema
|
|
3174
|
+
// was created with whatever dims were configured at original install time.
|
|
3175
|
+
}
|
|
3176
|
+
}
|
|
3177
|
+
}
|
|
3178
|
+
catch { /* non-critical */ }
|
|
3179
|
+
}
|
|
3180
|
+
// ---------------------------------------------------------------------------
|
|
3181
|
+
// Neo4j port — multi-brand data isolation.
|
|
3182
|
+
//
|
|
3183
|
+
// Default Maxy brand: 7687 (shared system Neo4j instance). Branded builds ship
|
|
3184
|
+
// a dedicated port in brand.json (e.g. Real Agent = 7688) so installing a
|
|
3185
|
+
// second brand on the same device gets its own database by default — Task 659.
|
|
3186
|
+
//
|
|
3187
|
+
// Priority: --neo4j-port flag > .env NEO4J_URI (upgrade preserve) > BRAND.neo4jPort > 7687.
|
|
3188
|
+
// ---------------------------------------------------------------------------
|
|
3189
|
+
const DEFAULT_NEO4J_PORT = 7687;
|
|
3190
|
+
let NEO4J_PORT = BRAND.neo4jPort ?? DEFAULT_NEO4J_PORT;
|
|
3191
|
+
let NEO4J_PORT_SOURCE = BRAND.neo4jPort ? "brand.json" : "default";
|
|
3192
|
+
const neo4jPortIdx = _args.indexOf("--neo4j-port");
|
|
3193
|
+
if (neo4jPortIdx !== -1) {
|
|
3194
|
+
const raw = _args[neo4jPortIdx + 1];
|
|
3195
|
+
const parsed = raw ? parseInt(raw, 10) : NaN;
|
|
3196
|
+
if (isNaN(parsed) || parsed < 1024 || parsed > 65535) {
|
|
3197
|
+
console.error(`Setup failed: --neo4j-port requires a numeric value between 1024 and 65535 (got: ${raw ?? "nothing"})`);
|
|
3198
|
+
console.error(`Error: --neo4j-port requires a numeric value between 1024 and 65535 (got: ${raw ?? "nothing"}).`);
|
|
3199
|
+
process.exit(1);
|
|
3200
|
+
}
|
|
3201
|
+
NEO4J_PORT = parsed;
|
|
3202
|
+
NEO4J_PORT_SOURCE = "--neo4j-port flag";
|
|
3203
|
+
}
|
|
3204
|
+
else {
|
|
3205
|
+
// Upgrade detection: check .env for NEO4J_URI=bolt://localhost:{port}.
|
|
3206
|
+
// Preserves an existing install's port even if brand.json now says different.
|
|
3207
|
+
const persistDir = resolve(process.env.HOME ?? "/root", BRAND.configDir);
|
|
3208
|
+
const envPath = join(persistDir, ".env");
|
|
3209
|
+
try {
|
|
3210
|
+
if (existsSync(envPath)) {
|
|
3211
|
+
const envContent = readFileSync(envPath, "utf-8");
|
|
3212
|
+
const uriMatch = envContent.match(/^NEO4J_URI=bolt:\/\/localhost:(\d+)$/m);
|
|
3213
|
+
if (uriMatch) {
|
|
3214
|
+
const envPort = parseInt(uriMatch[1], 10);
|
|
3215
|
+
if (envPort >= 1024 && envPort <= 65535) {
|
|
3216
|
+
NEO4J_PORT = envPort;
|
|
3217
|
+
NEO4J_PORT_SOURCE = ".env (preserved)";
|
|
3218
|
+
}
|
|
3219
|
+
}
|
|
3220
|
+
}
|
|
3221
|
+
}
|
|
3222
|
+
catch { /* non-critical */ }
|
|
3223
|
+
}
|
|
3224
|
+
// Dedicated = port differs from the default shared instance
|
|
3225
|
+
const NEO4J_DEDICATED = NEO4J_PORT !== DEFAULT_NEO4J_PORT;
|
|
3226
|
+
// ---------------------------------------------------------------------------
|
|
3227
|
+
// Task 664 removed the per-brand ttyd port — the admin terminal stack
|
|
3228
|
+
// (ttyd, tmux, xterm.js) was retired in favour of the action runner that
|
|
3229
|
+
// spawns transient `systemd-run --user` units per upgrade or setup-tunnel
|
|
3230
|
+
// invocation. No TCP listener on the device needs to be reserved for an
|
|
3231
|
+
// interactive-shell surface any more.
|
|
3232
|
+
const PKG_VERSION = JSON.parse(readFileSync(resolve(import.meta.dirname, "../package.json"), "utf-8")).version;
|
|
3233
|
+
// ---------------------------------------------------------------------------
|
|
3234
|
+
// Task 840 — pre-flight platform refusal.
|
|
3235
|
+
//
|
|
3236
|
+
// Runs BEFORE initLogging() (LOG_DIR creation), port resolution, and any
|
|
3237
|
+
// brew/scutil/hostnamectl probe. Only Linux + darwin are supported (Task 836);
|
|
3238
|
+
// on darwin, macOS major must be ≥ 14 (the floor required by Tasks 838/839).
|
|
3239
|
+
// Older macOS partially succeeds, then breaks at the supervisor or brew-cellar
|
|
3240
|
+
// layer with cryptic errors — refusing loudly here is the contract.
|
|
3241
|
+
//
|
|
3242
|
+
// Platform-header line is emitted on every install start so operators can grep
|
|
3243
|
+
// `[create-maxy] platform=` to confirm pre-flight ran. On darwin the line also
|
|
3244
|
+
// carries `macos=<v>` (Task 840 token) so the macOS-14 floor check is visible.
|
|
3245
|
+
// ---------------------------------------------------------------------------
|
|
3246
|
+
const PLATFORM = requireSupportedPlatform(process.platform);
|
|
3247
|
+
let MACOS_VERSION = null;
|
|
3248
|
+
if (PLATFORM === "darwin") {
|
|
3249
|
+
const swVers = spawnSync("sw_vers", [], { encoding: "utf-8", stdio: "pipe", timeout: 5_000 });
|
|
3250
|
+
const parsed = parseSwVers(swVers.stdout ?? "");
|
|
3251
|
+
if (!parsed) {
|
|
3252
|
+
const head = (swVers.stdout ?? "").split("\n").slice(0, 2).join(" | ");
|
|
3253
|
+
console.error(`[create-maxy] sw_vers stdout malformed: ${head}`);
|
|
3254
|
+
console.error(`[create-maxy] platform=darwin macos=<unknown> — refusing: macOS 14+ required`);
|
|
3255
|
+
process.exit(1);
|
|
3256
|
+
}
|
|
3257
|
+
MACOS_VERSION = parsed.version;
|
|
3258
|
+
if (!isSupportedMacosVersion(MACOS_VERSION)) {
|
|
3259
|
+
console.error(`[create-maxy] platform=darwin macos=${MACOS_VERSION} — refusing: macOS 14+ required`);
|
|
3260
|
+
process.exit(1);
|
|
3261
|
+
}
|
|
3262
|
+
}
|
|
3263
|
+
// Platform-header — first log line. Operators grep `[create-maxy] platform=`
|
|
3264
|
+
// to confirm pre-flight ran. The /* macos=<v> */ token is the Task 840 marker;
|
|
3265
|
+
// keep it on this same line so 3-way merges with parallel installer edits stay
|
|
3266
|
+
// mechanical (no rewrap, no split into two log lines).
|
|
3267
|
+
const PLATFORM_HEADER = `[create-maxy] platform=${PLATFORM} arch=${process.arch}` +
|
|
3268
|
+
(MACOS_VERSION ? ` macos=${MACOS_VERSION}` : ``) +
|
|
3269
|
+
` version=${PKG_VERSION}`;
|
|
3270
|
+
initLogging();
|
|
3271
|
+
console.log(PLATFORM_HEADER);
|
|
3272
|
+
console.log("================================================================");
|
|
3273
|
+
console.log(` ${BRAND.productName} — ${BRAND.tagline}. (${BRAND.hostname} v${PKG_VERSION})`);
|
|
3274
|
+
console.log("================================================================");
|
|
3275
|
+
console.log(` Install log: ${LOG_FILE}`);
|
|
3276
|
+
console.log(` Port: ${PORT} (${PORT_SOURCE})`);
|
|
3277
|
+
if (HOSTNAME_FLAG)
|
|
3278
|
+
console.log(` Hostname: ${HOSTNAME_FLAG} (from --hostname flag)`);
|
|
3279
|
+
console.log(` Display: ${DISPLAY_MODE} (${DISPLAY_MODE_SOURCE})`);
|
|
3280
|
+
console.log(` Embed model: ${EMBED_MODEL} (${EMBED_DIMS} dims, ${EMBED_SOURCE})`);
|
|
3281
|
+
console.log(` Neo4j: ${NEO4J_DEDICATED ? "dedicated" : "shared"} on bolt://localhost:${NEO4J_PORT} (${NEO4J_PORT_SOURCE})`);
|
|
3282
|
+
console.log("");
|
|
3283
|
+
logDiagnostics("pre-flight");
|
|
3284
|
+
logFile(` Neo4j instance: ${NEO4J_DEDICATED ? "dedicated" : "shared"} on bolt://localhost:${NEO4J_PORT}`);
|
|
3285
|
+
try {
|
|
3286
|
+
installSystemDeps();
|
|
3287
|
+
installNodejs();
|
|
3288
|
+
installClaudeCode();
|
|
3289
|
+
installNeo4j();
|
|
3290
|
+
setupDedicatedNeo4j();
|
|
3291
|
+
installOllama(EMBED_MODEL);
|
|
3292
|
+
installUv();
|
|
3293
|
+
installCloudflared();
|
|
3294
|
+
installWhisperCpp();
|
|
3295
|
+
deployPayload(); // Must happen before ensureNeo4jPassword — restores config backup
|
|
3296
|
+
// Task 929: write the resolved Chromium absolute path into the deployed
|
|
3297
|
+
// platform/config/ so vnc.sh, writeChromiumWrapper, and setup-tunnel.sh
|
|
3298
|
+
// all read the same value. Must run after deployPayload (config dir is
|
|
3299
|
+
// a payload subdirectory). Linux-only; no-op on darwin/non-linux.
|
|
3300
|
+
writeChromiumBinaryPathFile();
|
|
3301
|
+
// Task 744: scrub plaintext neo4j passwords from any pre-fix install-*.log.
|
|
3302
|
+
// Idempotent — re-running on already-redacted logs is a no-op. Runs after
|
|
3303
|
+
// payload deploy so the bundled redact-install-logs.sh is on disk.
|
|
3304
|
+
redactInstallLogs();
|
|
3305
|
+
ensureNeo4jPassword(); // Now config/.neo4j-password is available if it existed before
|
|
3306
|
+
provisionRemoteSessionSecret(); // Task 653: shared HMAC key readable by maxy-edge + maxy-ui
|
|
3307
|
+
buildPlatform();
|
|
3308
|
+
setupVncViewer();
|
|
3309
|
+
setupAccount();
|
|
3310
|
+
installTunnelScripts(); // ~/setup-tunnel.sh, ~/reset-tunnel.sh — the SKILL contract
|
|
3311
|
+
installService();
|
|
3312
|
+
console.log("");
|
|
3313
|
+
console.log("================================================================");
|
|
3314
|
+
console.log("");
|
|
3315
|
+
console.log(` Open in your browser: http://${DEVICE_HOSTNAME}.local:${PORT}`);
|
|
3316
|
+
console.log("");
|
|
3317
|
+
console.log("================================================================");
|
|
3318
|
+
}
|
|
3319
|
+
catch (err) {
|
|
3320
|
+
console.error("");
|
|
3321
|
+
console.error(`Setup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
3322
|
+
console.error(` Full log: ${LOG_FILE}`);
|
|
3323
|
+
logDiagnostics("post-failure");
|
|
3324
|
+
process.exit(1);
|
|
3325
|
+
}
|