@pellux/goodvibes-daemon 1.28.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/CHANGELOG.md +383 -0
- package/LICENSE +21 -0
- package/README.md +125 -0
- package/bin/goodvibes-daemon +100 -0
- package/bin/launcher-support.js +226 -0
- package/package.json +96 -0
- package/scripts/check-bun.sh +20 -0
- package/scripts/postinstall.js +244 -0
- package/src/cli/command-catalog.ts +828 -0
- package/src/cli/completion.ts +299 -0
- package/src/cli/help.ts +167 -0
- package/src/cli/index.ts +21 -0
- package/src/cli/parser.ts +55 -0
- package/src/cli/surface-catalog.ts +26 -0
- package/src/cli/types.ts +63 -0
- package/src/cluster/daemon-ws-call.ts +235 -0
- package/src/cluster/raw-reply-route.ts +111 -0
- package/src/config/checkpoint-settings.ts +113 -0
- package/src/config/run-daemon-config-migration.ts +47 -0
- package/src/config/secret-config.ts +175 -0
- package/src/config/secrets.ts +71 -0
- package/src/config/surface.ts +24 -0
- package/src/core/pairing-banner.ts +82 -0
- package/src/daemon/cli.ts +878 -0
- package/src/daemon/config-command.ts +281 -0
- package/src/daemon/handlers/context.ts +29 -0
- package/src/daemon/handlers/contracts.ts +43 -0
- package/src/daemon/handlers/credentials.ts +139 -0
- package/src/daemon/handlers/drafts/draft-store.ts +427 -0
- package/src/daemon/handlers/drafts/index.ts +17 -0
- package/src/daemon/handlers/drafts/register.ts +331 -0
- package/src/daemon/handlers/errors.ts +18 -0
- package/src/daemon/handlers/inbox/aggregator.ts +375 -0
- package/src/daemon/handlers/inbox/cursor-store.ts +512 -0
- package/src/daemon/handlers/inbox/index.ts +221 -0
- package/src/daemon/handlers/inbox/mapping.ts +192 -0
- package/src/daemon/handlers/inbox/poller.ts +239 -0
- package/src/daemon/handlers/inbox/provider-adapter.ts +171 -0
- package/src/daemon/handlers/inbox/providers/discord.ts +276 -0
- package/src/daemon/handlers/inbox/providers/email.ts +176 -0
- package/src/daemon/handlers/inbox/providers/imap-client.ts +300 -0
- package/src/daemon/handlers/inbox/providers/route-util.ts +24 -0
- package/src/daemon/handlers/inbox/providers/slack.ts +287 -0
- package/src/daemon/handlers/index.ts +117 -0
- package/src/daemon/handlers/register.ts +180 -0
- package/src/daemon/handlers/remote/backends/cloud-terminal.ts +143 -0
- package/src/daemon/handlers/remote/backends/docker.ts +79 -0
- package/src/daemon/handlers/remote/backends/index.ts +40 -0
- package/src/daemon/handlers/remote/backends/local-process.ts +113 -0
- package/src/daemon/handlers/remote/backends/process-runner.ts +127 -0
- package/src/daemon/handlers/remote/backends/ssh.ts +126 -0
- package/src/daemon/handlers/remote/backends/types.ts +97 -0
- package/src/daemon/handlers/remote/dispatcher.ts +181 -0
- package/src/daemon/handlers/remote/index.ts +120 -0
- package/src/daemon/handlers/remote/peer-registry.ts +357 -0
- package/src/daemon/handlers/remote/service.ts +191 -0
- package/src/daemon/handlers/routing/inbox-bridge.ts +71 -0
- package/src/daemon/handlers/routing/index.ts +261 -0
- package/src/daemon/handlers/routing/route-store.ts +319 -0
- package/src/daemon/handlers/routing/routing-resolver.ts +75 -0
- package/src/daemon/handlers/sqlite-store.ts +303 -0
- package/src/daemon/handlers/triage/index.ts +57 -0
- package/src/daemon/handlers/triage/integration.ts +213 -0
- package/src/daemon/handlers/triage/pipeline.ts +274 -0
- package/src/daemon/handlers/triage/scorer.ts +287 -0
- package/src/daemon/handlers/triage/tagger/discord.ts +187 -0
- package/src/daemon/handlers/triage/tagger/imap.ts +384 -0
- package/src/daemon/handlers/triage/tagger/index.ts +184 -0
- package/src/daemon/handlers/triage/tagger/shared.ts +70 -0
- package/src/daemon/handlers/triage/tagger/slack.ts +69 -0
- package/src/daemon/handlers/triage/types.ts +50 -0
- package/src/daemon/lifecycle.ts +41 -0
- package/src/daemon/local-daemon-state.ts +233 -0
- package/src/daemon/pair-command.ts +301 -0
- package/src/daemon/provision-wake-model.ts +81 -0
- package/src/daemon/send/channels.ts +200 -0
- package/src/daemon/send/command.ts +333 -0
- package/src/daemon/send/composition.ts +100 -0
- package/src/daemon/send/failure-text.ts +93 -0
- package/src/daemon/send/inert-text.ts +225 -0
- package/src/daemon/send/stdin.ts +24 -0
- package/src/daemon/service-commands.ts +530 -0
- package/src/daemon/sessions-command.ts +209 -0
- package/src/daemon/status-command.ts +481 -0
- package/src/daemon/webui-command.ts +339 -0
- package/src/runtime/boot-tasks.ts +110 -0
- package/src/runtime/cluster-composition.ts +124 -0
- package/src/runtime/cluster-group-composition.ts +284 -0
- package/src/runtime/conversation-rewind-port.ts +171 -0
- package/src/runtime/credential-composition.ts +54 -0
- package/src/runtime/daemon-handler-composition.ts +76 -0
- package/src/runtime/device-posture-composition.ts +115 -0
- package/src/runtime/disposal-wiring.ts +101 -0
- package/src/runtime/fleet-needs-input-push.ts +61 -0
- package/src/runtime/fleet-services.ts +41 -0
- package/src/runtime/hosted-session-composition.ts +128 -0
- package/src/runtime/index.ts +100 -0
- package/src/runtime/knowledge-services.ts +101 -0
- package/src/runtime/legacy-daemon-migration.ts +605 -0
- package/src/runtime/legacy-daemon-reconcile.ts +448 -0
- package/src/runtime/mail-composition.ts +65 -0
- package/src/runtime/notification-dispatch.ts +86 -0
- package/src/runtime/plugin-composition.ts +111 -0
- package/src/runtime/runtime-services-types.ts +268 -0
- package/src/runtime/services.ts +756 -0
- package/src/runtime/trigger-services.ts +62 -0
- package/src/runtime/trust/checkpoint-eligibility.ts +138 -0
- package/src/runtime/trust/trust-gated-approvals.ts +169 -0
- package/src/runtime/update-check.ts +61 -0
- package/src/runtime/workspace-checkpointing.ts +116 -0
- package/src/testing/daemon-fixture.ts +276 -0
- package/src/testing/hosted-session-failures.ts +92 -0
- package/src/version.ts +26 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Launcher support for the daemon package.
|
|
3
|
+
*
|
|
4
|
+
* The npm package carries source and this launcher; the daemon itself is a
|
|
5
|
+
* compiled binary published as a GitHub release asset. The postinstall places
|
|
6
|
+
* it, and this module is the self-heal path for an install whose lifecycle
|
|
7
|
+
* script was blocked (which is the default for an untrusted global package under
|
|
8
|
+
* bun): the first run downloads the release asset, verifies it against the
|
|
9
|
+
* release's SHA256SUMS.txt, and caches it in vendor/.
|
|
10
|
+
*
|
|
11
|
+
* Asset names are the ones the daemon has always published
|
|
12
|
+
* (`goodvibes-daemon-<os>-<arch>`), so the curl installer, the release manifest
|
|
13
|
+
* and the daemon's own updater all keep resolving the same files.
|
|
14
|
+
*
|
|
15
|
+
* The sqlite-vec native addon gets the same self-heal: `resolveSqliteVecPath()`
|
|
16
|
+
* (platform/state/sqlite-vec-loader.ts, reached from a compiled binary) expects
|
|
17
|
+
* it at `<execDir>/lib/sqlite-vec-<platform>-<arch>/vec0.<suffix>` —
|
|
18
|
+
* `vendor/lib/...` once the binary above is placed there — and a blocked
|
|
19
|
+
* postinstall never staged it either. Without this, a self-healed install
|
|
20
|
+
* would get the daemon binary back but stay on lexical-only search forever.
|
|
21
|
+
*/
|
|
22
|
+
import { accessSync, chmodSync, constants, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
23
|
+
import { createHash } from 'node:crypto';
|
|
24
|
+
import { spawnSync } from 'node:child_process';
|
|
25
|
+
import { join } from 'node:path';
|
|
26
|
+
|
|
27
|
+
const SUPPORTED_TARGETS = ['linux-x64', 'linux-arm64', 'darwin-x64', 'darwin-arm64'];
|
|
28
|
+
|
|
29
|
+
/** Names the sqlite-vec addon for a platform/arch — mirrors resolveSqliteVecAsset
|
|
30
|
+
* in the SDK's platform/runtime/self-update module. Reimplemented (rather than
|
|
31
|
+
* imported) so this launcher never depends on that package resolving: it must
|
|
32
|
+
* keep working from the packaged bin/ directory alone, with no bundler step
|
|
33
|
+
* and no node_modules resolution beyond what's already vendored. */
|
|
34
|
+
export function resolveSqliteVecAddonName(platform, arch) {
|
|
35
|
+
if ((platform !== 'linux' && platform !== 'darwin') || (arch !== 'x64' && arch !== 'arm64')) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
const suffix = platform === 'darwin' ? 'dylib' : 'so';
|
|
39
|
+
const dirName = `sqlite-vec-${platform}-${arch}`;
|
|
40
|
+
return { assetName: `${dirName}.${suffix}`, dirName, fileName: `vec0.${suffix}` };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function isExecutable(path) {
|
|
44
|
+
try {
|
|
45
|
+
accessSync(path, constants.X_OK);
|
|
46
|
+
return true;
|
|
47
|
+
} catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function run(command, args) {
|
|
53
|
+
const child = spawnSync(command, args, { stdio: 'inherit' });
|
|
54
|
+
if (child.error) {
|
|
55
|
+
throw child.error;
|
|
56
|
+
}
|
|
57
|
+
process.exit(child.status ?? 1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function isSourceCheckout(packageRoot) {
|
|
61
|
+
return isExecutable(join(packageRoot, 'node_modules', '.bin', 'bun')) ||
|
|
62
|
+
isExecutable(join(packageRoot, 'node_modules', '.bin', 'tsc')) ||
|
|
63
|
+
fileExists(join(packageRoot, 'tsconfig.json'));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function supportedTargetsText() {
|
|
67
|
+
return SUPPORTED_TARGETS.join(', ');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** The published asset name for a platform, unchanged from what the daemon has always released. */
|
|
71
|
+
export function resolveArtifactName(platform, arch) {
|
|
72
|
+
if (platform === 'linux' && arch === 'x64') return 'goodvibes-daemon-linux-x64';
|
|
73
|
+
if (platform === 'linux' && arch === 'arm64') return 'goodvibes-daemon-linux-arm64';
|
|
74
|
+
if (platform === 'darwin' && arch === 'x64') return 'goodvibes-daemon-macos-x64';
|
|
75
|
+
if (platform === 'darwin' && arch === 'arm64') return 'goodvibes-daemon-macos-arm64';
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export async function ensureVendoredBinary({ packageRoot, artifactName }) {
|
|
80
|
+
const vendorDir = join(packageRoot, 'vendor');
|
|
81
|
+
const destination = join(vendorDir, artifactName);
|
|
82
|
+
if (isExecutable(destination)) {
|
|
83
|
+
return destination;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const pkg = JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf8'));
|
|
87
|
+
const releaseBaseUrl =
|
|
88
|
+
process.env.GOODVIBES_RELEASE_BASE_URL?.trim() ||
|
|
89
|
+
`${resolveRepositoryBaseUrl(pkg)}/releases/download/v${pkg.version}`;
|
|
90
|
+
|
|
91
|
+
mkdirSync(vendorDir, { recursive: true });
|
|
92
|
+
|
|
93
|
+
const checksumText = await downloadText(`${releaseBaseUrl}/SHA256SUMS.txt`);
|
|
94
|
+
writeFileSync(join(vendorDir, 'SHA256SUMS.txt'), checksumText);
|
|
95
|
+
const checksums = parseChecksumFile(checksumText);
|
|
96
|
+
|
|
97
|
+
const tempDestination = `${destination}.download`;
|
|
98
|
+
rmSync(tempDestination, { force: true });
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
const binary = await downloadBuffer(`${releaseBaseUrl}/${artifactName}`);
|
|
102
|
+
const actual = sha256(binary);
|
|
103
|
+
const expected = checksums.get(artifactName);
|
|
104
|
+
if (expected && expected !== actual) {
|
|
105
|
+
throw new Error(`checksum mismatch for ${artifactName}: expected ${expected}, got ${actual}`);
|
|
106
|
+
}
|
|
107
|
+
writeFileSync(tempDestination, binary);
|
|
108
|
+
prepareBinary(tempDestination);
|
|
109
|
+
rmSync(destination, { force: true });
|
|
110
|
+
writeFileSync(destination, readFileSync(tempDestination));
|
|
111
|
+
prepareBinary(destination);
|
|
112
|
+
} finally {
|
|
113
|
+
rmSync(tempDestination, { force: true });
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return destination;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Self-heal counterpart to `ensureVendoredBinary` for the sqlite-vec native
|
|
121
|
+
* addon. Same shape, same verification posture (a missing manifest entry does
|
|
122
|
+
* not block the install, matching `ensureVendoredBinary` above), same
|
|
123
|
+
* vendor/ destination — placed at `vendor/lib/sqlite-vec-<platform>-<arch>/
|
|
124
|
+
* vec0.<suffix>`, which is exactly where `resolveSqliteVecPath()` looks
|
|
125
|
+
* relative to the vendored binary this module also places. A platform/arch
|
|
126
|
+
* with no addon (`resolveSqliteVecAddonName` returns null) is a no-op, not an
|
|
127
|
+
* error — same as an unsupported binary target.
|
|
128
|
+
*/
|
|
129
|
+
export async function ensureVendoredSqliteVecAddon({ packageRoot, platform, arch }) {
|
|
130
|
+
const asset = resolveSqliteVecAddonName(platform, arch);
|
|
131
|
+
if (!asset) return null;
|
|
132
|
+
|
|
133
|
+
const addonDir = join(packageRoot, 'vendor', 'lib', asset.dirName);
|
|
134
|
+
const destination = join(addonDir, asset.fileName);
|
|
135
|
+
if (fileExists(destination)) {
|
|
136
|
+
return destination;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const pkg = JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf8'));
|
|
140
|
+
const releaseBaseUrl =
|
|
141
|
+
process.env.GOODVIBES_RELEASE_BASE_URL?.trim() ||
|
|
142
|
+
`${resolveRepositoryBaseUrl(pkg)}/releases/download/v${pkg.version}`;
|
|
143
|
+
|
|
144
|
+
mkdirSync(addonDir, { recursive: true });
|
|
145
|
+
|
|
146
|
+
const checksumText = await downloadText(`${releaseBaseUrl}/SHA256SUMS.txt`);
|
|
147
|
+
const checksums = parseChecksumFile(checksumText);
|
|
148
|
+
|
|
149
|
+
const tempDestination = `${destination}.download`;
|
|
150
|
+
rmSync(tempDestination, { force: true });
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
const addon = await downloadBuffer(`${releaseBaseUrl}/${asset.assetName}`);
|
|
154
|
+
const actual = sha256(addon);
|
|
155
|
+
const expected = checksums.get(asset.assetName);
|
|
156
|
+
if (expected && expected !== actual) {
|
|
157
|
+
throw new Error(`checksum mismatch for ${asset.assetName}: expected ${expected}, got ${actual}`);
|
|
158
|
+
}
|
|
159
|
+
writeFileSync(tempDestination, addon);
|
|
160
|
+
rmSync(destination, { force: true });
|
|
161
|
+
writeFileSync(destination, readFileSync(tempDestination));
|
|
162
|
+
} finally {
|
|
163
|
+
rmSync(tempDestination, { force: true });
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return destination;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function fileExists(path) {
|
|
170
|
+
try {
|
|
171
|
+
accessSync(path, constants.F_OK);
|
|
172
|
+
return true;
|
|
173
|
+
} catch {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function prepareBinary(path) {
|
|
179
|
+
if (process.platform !== 'win32') {
|
|
180
|
+
chmodSync(path, 0o755);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function resolveRepositoryBaseUrl(pkg) {
|
|
185
|
+
const repositoryUrl = typeof pkg.repository?.url === 'string' ? pkg.repository.url : '';
|
|
186
|
+
const normalized = repositoryUrl
|
|
187
|
+
.replace(/^git\+/, '')
|
|
188
|
+
.replace(/\.git$/, '')
|
|
189
|
+
.replace(/^git@github\.com:/, 'https://github.com/');
|
|
190
|
+
if (!normalized.startsWith('https://github.com/')) {
|
|
191
|
+
throw new Error(`unsupported repository URL for binary downloads: ${repositoryUrl || '(missing)'}`);
|
|
192
|
+
}
|
|
193
|
+
return normalized;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async function downloadText(url) {
|
|
197
|
+
const response = await fetch(url);
|
|
198
|
+
if (!response.ok) {
|
|
199
|
+
throw new Error(`download failed (${response.status}) for ${url}`);
|
|
200
|
+
}
|
|
201
|
+
return await response.text();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async function downloadBuffer(url) {
|
|
205
|
+
const response = await fetch(url);
|
|
206
|
+
if (!response.ok) {
|
|
207
|
+
throw new Error(`download failed (${response.status}) for ${url}`);
|
|
208
|
+
}
|
|
209
|
+
return Buffer.from(await response.arrayBuffer());
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function sha256(buffer) {
|
|
213
|
+
return createHash('sha256').update(buffer).digest('hex');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function parseChecksumFile(contents) {
|
|
217
|
+
const checksums = new Map();
|
|
218
|
+
for (const rawLine of contents.split(/\r?\n/)) {
|
|
219
|
+
const line = rawLine.trim();
|
|
220
|
+
if (!line) continue;
|
|
221
|
+
const match = line.match(/^([a-f0-9]{64})\s+\*?(.+)$/i);
|
|
222
|
+
if (!match) continue;
|
|
223
|
+
checksums.set(match[2], match[1].toLowerCase());
|
|
224
|
+
}
|
|
225
|
+
return checksums;
|
|
226
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pellux/goodvibes-daemon",
|
|
3
|
+
"version": "1.28.0",
|
|
4
|
+
"description": "The GoodVibes daemon \u2014 the one long-running host for the control plane, channels, cluster membership, scheduled work, knowledge and memory stores, and the verb families every GoodVibes client calls.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/daemon/cli.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"goodvibes-daemon": "bin/goodvibes-daemon"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bin",
|
|
12
|
+
"src",
|
|
13
|
+
"!src/test",
|
|
14
|
+
"!src/**/*.test.ts",
|
|
15
|
+
"scripts/check-bun.sh",
|
|
16
|
+
"scripts/postinstall.js",
|
|
17
|
+
"README.md",
|
|
18
|
+
"CHANGELOG.md"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"daemon": "bun run src/daemon/cli.ts",
|
|
22
|
+
"prebuild": "bun run scripts/prebuild.ts",
|
|
23
|
+
"build": "goodvibes-build-binaries",
|
|
24
|
+
"build:all": "goodvibes-build-binaries --all",
|
|
25
|
+
"build:linux-x64": "goodvibes-build-binaries --target linux-x64",
|
|
26
|
+
"build:linux-arm64": "goodvibes-build-binaries --target linux-arm64",
|
|
27
|
+
"build:macos-x64": "goodvibes-build-binaries --target darwin-x64",
|
|
28
|
+
"build:macos-arm64": "goodvibes-build-binaries --target darwin-arm64",
|
|
29
|
+
"smoke": "goodvibes-post-build-smoke",
|
|
30
|
+
"smoke:boot": "bun run scripts/boot-smoke.ts",
|
|
31
|
+
"smoke:hosted": "bun run scripts/hosted-session-proof.ts",
|
|
32
|
+
"typecheck": "bunx tsc --noEmit",
|
|
33
|
+
"typecheck:test": "bunx tsc --noEmit -p tsconfig.test.json",
|
|
34
|
+
"test": "bun run scripts/run-tests.ts",
|
|
35
|
+
"preinstall": "sh scripts/check-bun.sh",
|
|
36
|
+
"postinstall": "bun scripts/postinstall.js",
|
|
37
|
+
"postbuild": "bun scripts/postinstall.js --no-download",
|
|
38
|
+
"version": "bun run scripts/prebuild.ts",
|
|
39
|
+
"workflows:check": "bun run scripts/check-workflows.ts",
|
|
40
|
+
"changelog:check": "bun run scripts/check-changelog.ts",
|
|
41
|
+
"verify:tag-version": "bun run scripts/verify-release-tag-version.ts",
|
|
42
|
+
"publish:check": "bun run scripts/publish-check.ts",
|
|
43
|
+
"release": "bun run scripts/release.ts",
|
|
44
|
+
"release:dry": "bun run scripts/release.ts --dry-run"
|
|
45
|
+
},
|
|
46
|
+
"license": "MIT",
|
|
47
|
+
"repository": {
|
|
48
|
+
"type": "git",
|
|
49
|
+
"url": "git+https://github.com/mgd34msu/goodvibes-daemon.git"
|
|
50
|
+
},
|
|
51
|
+
"keywords": [
|
|
52
|
+
"goodvibes",
|
|
53
|
+
"daemon",
|
|
54
|
+
"control-plane",
|
|
55
|
+
"automation",
|
|
56
|
+
"channels"
|
|
57
|
+
],
|
|
58
|
+
"engines": {
|
|
59
|
+
"node": ">=20",
|
|
60
|
+
"bun": ">=1.3.10"
|
|
61
|
+
},
|
|
62
|
+
"dependencies": {
|
|
63
|
+
"@agentclientprotocol/sdk": "^0.21.0",
|
|
64
|
+
"@anthropic-ai/bedrock-sdk": "^0.28.1",
|
|
65
|
+
"@anthropic-ai/sdk": "^0.82.0",
|
|
66
|
+
"@ast-grep/napi": "^0.42.0",
|
|
67
|
+
"@pellux/goodvibes-sdk": "2.0.0",
|
|
68
|
+
"@pellux/goodvibes-terminal-shell": "2.0.0",
|
|
69
|
+
"bash-language-server": "^5.6.0",
|
|
70
|
+
"fuse.js": "^7.1.0",
|
|
71
|
+
"graphql": "^16.13.2",
|
|
72
|
+
"jszip": "^3.10.1",
|
|
73
|
+
"node-edge-tts": "^1.2.10",
|
|
74
|
+
"openai": "^6.29.0",
|
|
75
|
+
"pyright": "^1.1.408",
|
|
76
|
+
"simple-git": "^3.33.0",
|
|
77
|
+
"sql.js": "^1.14.1",
|
|
78
|
+
"sqlite-vec": "^0.1.9",
|
|
79
|
+
"tree-sitter-css": "^0.25.0",
|
|
80
|
+
"tree-sitter-javascript": "^0.25.0",
|
|
81
|
+
"tree-sitter-json": "^0.24.8",
|
|
82
|
+
"tree-sitter-python": "^0.25.0",
|
|
83
|
+
"tree-sitter-typescript": "^0.23.2",
|
|
84
|
+
"typescript-language-server": "^5.1.3",
|
|
85
|
+
"vscode-langservers-extracted": "^4.10.0",
|
|
86
|
+
"web-tree-sitter": "^0.26.7"
|
|
87
|
+
},
|
|
88
|
+
"devDependencies": {
|
|
89
|
+
"@pellux/goodvibes-toolchain": "2.0.0",
|
|
90
|
+
"@types/bun": "^1.3.10",
|
|
91
|
+
"typescript": "^5.9.3"
|
|
92
|
+
},
|
|
93
|
+
"overrides": {
|
|
94
|
+
"minimatch": "^10.2.5"
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
set -eu
|
|
3
|
+
|
|
4
|
+
if ! command -v bun >/dev/null 2>&1; then
|
|
5
|
+
cat >&2 <<'EOF'
|
|
6
|
+
goodvibes-daemon requires Bun.
|
|
7
|
+
|
|
8
|
+
Install Bun first, then install the daemon from the npm registry with:
|
|
9
|
+
|
|
10
|
+
bun add -g goodvibes-daemon
|
|
11
|
+
|
|
12
|
+
npm install -g goodvibes-daemon also works, but Bun must already be installed and available on PATH.
|
|
13
|
+
EOF
|
|
14
|
+
exit 1
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
if ! bun --version >/dev/null 2>&1; then
|
|
18
|
+
echo "goodvibes-daemon requires a working Bun executable on PATH." >&2
|
|
19
|
+
exit 1
|
|
20
|
+
fi
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* postinstall — place the compiled daemon binary AND the sqlite-vec native
|
|
4
|
+
* addon for this platform.
|
|
5
|
+
*
|
|
6
|
+
* The npm package carries source and the launcher; the daemon itself is a
|
|
7
|
+
* compiled binary published as a GitHub release asset of THIS repository. This
|
|
8
|
+
* downloads the one matching binary asset, verifies it against the release's
|
|
9
|
+
* SHA256SUMS.txt, and caches it in vendor/ where bin/goodvibes-daemon finds it.
|
|
10
|
+
*
|
|
11
|
+
* The sqlite-vec addon gets the identical treatment for the identical reason:
|
|
12
|
+
* `resolveSqliteVecPath()` (platform/state/sqlite-vec-loader.ts, reached from a
|
|
13
|
+
* compiled binary) looks for it at `<execDir>/lib/sqlite-vec-<platform>-<arch>/
|
|
14
|
+
* vec0.<suffix>` — `<execDir>` being vendor/ once bin/goodvibes-daemon has
|
|
15
|
+
* placed the binary there — and nothing else stages it for an npm install. Skip
|
|
16
|
+
* this and the addon is silently absent forever: the launcher's self-heal
|
|
17
|
+
* (bin/launcher-support.js) only ever re-fetches the BINARY, and the daemon's
|
|
18
|
+
* own auto-updater only refreshes the addon if a copy already exists on disk
|
|
19
|
+
* (there is never a first one to refresh). The daemon degrades to lexical
|
|
20
|
+
* search plus a log warning rather than failing loudly, so the loss is easy to
|
|
21
|
+
* miss — this download is what gives every npm install the same vector search
|
|
22
|
+
* a curl/install.sh install gets.
|
|
23
|
+
*
|
|
24
|
+
* What it deliberately does NOT do: deploy skills, deploy agents, or fetch the
|
|
25
|
+
* wake-word model. Skills and agents are surface artifacts that ship with the
|
|
26
|
+
* terminal app's own package, not this one. The wake-word model is fetched by
|
|
27
|
+
* the daemon itself — the installer
|
|
28
|
+
* runs `goodvibes-daemon provision-wake-model` on the placed binary, and every
|
|
29
|
+
* daemon start retries whatever is still missing — so pulling it here as well
|
|
30
|
+
* would be a second copy of a pin that already has one owner.
|
|
31
|
+
*
|
|
32
|
+
* A source checkout is skipped: a repository clone is a development tree, not an
|
|
33
|
+
* installation.
|
|
34
|
+
*/
|
|
35
|
+
import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
36
|
+
import { dirname, join } from 'node:path';
|
|
37
|
+
import { fileURLToPath } from 'node:url';
|
|
38
|
+
import {
|
|
39
|
+
CHECKSUM_MANIFEST_NAME,
|
|
40
|
+
parseChecksumFile,
|
|
41
|
+
resolveArtifactNames,
|
|
42
|
+
resolveSqliteVecAsset,
|
|
43
|
+
sha256,
|
|
44
|
+
verifyChecksum,
|
|
45
|
+
} from '@pellux/goodvibes-sdk/platform/runtime/self-update';
|
|
46
|
+
|
|
47
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
48
|
+
const projectRoot = join(__dirname, '..');
|
|
49
|
+
const pkg = JSON.parse(readFileSync(join(projectRoot, 'package.json'), 'utf8'));
|
|
50
|
+
const noDownload = process.argv.includes('--no-download') || process.env.GOODVIBES_SKIP_BINARY_DOWNLOAD === '1';
|
|
51
|
+
|
|
52
|
+
function isSourceCheckout() {
|
|
53
|
+
return existsSync(join(projectRoot, '.git')) || existsSync(join(projectRoot, 'bun.lock'));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function prepareBinary(path) {
|
|
57
|
+
if (process.platform !== 'win32') {
|
|
58
|
+
chmodSync(path, 0o755);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function resolveRepositoryBaseUrl() {
|
|
63
|
+
const repositoryUrl = typeof pkg.repository?.url === 'string' ? pkg.repository.url : '';
|
|
64
|
+
const normalized = repositoryUrl
|
|
65
|
+
.replace(/^git\+/, '')
|
|
66
|
+
.replace(/\.git$/, '')
|
|
67
|
+
.replace(/^git@github\.com:/, 'https://github.com/');
|
|
68
|
+
if (!normalized.startsWith('https://github.com/')) {
|
|
69
|
+
throw new Error(`unsupported repository URL for binary downloads: ${repositoryUrl || '(missing)'}`);
|
|
70
|
+
}
|
|
71
|
+
return normalized;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function downloadFile(url, destination) {
|
|
75
|
+
const response = await fetch(url);
|
|
76
|
+
if (!response.ok) {
|
|
77
|
+
throw new Error(`download failed (${response.status}) for ${url}`);
|
|
78
|
+
}
|
|
79
|
+
writeFileSync(destination, Buffer.from(await response.arrayBuffer()));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function downloadText(url) {
|
|
83
|
+
const response = await fetch(url);
|
|
84
|
+
if (!response.ok) {
|
|
85
|
+
throw new Error(`download failed (${response.status}) for ${url}`);
|
|
86
|
+
}
|
|
87
|
+
return await response.text();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function installPlatformBinary() {
|
|
91
|
+
const artifacts = resolveArtifactNames(process.platform, process.arch);
|
|
92
|
+
if (!artifacts) {
|
|
93
|
+
console.log(`postinstall: no prebuilt binary for ${process.platform}-${process.arch}; skipping binary install`);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (noDownload) {
|
|
98
|
+
console.log('postinstall: skipping binary install (--no-download)');
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (isSourceCheckout()) {
|
|
103
|
+
console.log('postinstall: source checkout detected; skipping release-binary install');
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const vendorDir = join(projectRoot, 'vendor');
|
|
108
|
+
mkdirSync(vendorDir, { recursive: true });
|
|
109
|
+
|
|
110
|
+
const localSourceDir = process.env.GOODVIBES_ASSET_SOURCE_DIR?.trim();
|
|
111
|
+
if (localSourceDir) {
|
|
112
|
+
const sourcePath = join(localSourceDir, artifacts.daemon);
|
|
113
|
+
if (!existsSync(sourcePath)) {
|
|
114
|
+
throw new Error(`missing local release artifact for postinstall smoke: ${sourcePath}`);
|
|
115
|
+
}
|
|
116
|
+
const destination = join(vendorDir, artifacts.daemon);
|
|
117
|
+
copyFileSync(sourcePath, destination);
|
|
118
|
+
prepareBinary(destination);
|
|
119
|
+
console.log(`postinstall: installed local smoke-test binary for ${process.platform}-${process.arch}`);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const releaseBaseUrl =
|
|
124
|
+
process.env.GOODVIBES_RELEASE_BASE_URL?.trim() ||
|
|
125
|
+
`${resolveRepositoryBaseUrl()}/releases/download/v${pkg.version}`;
|
|
126
|
+
|
|
127
|
+
const checksumText = await downloadText(`${releaseBaseUrl}/${CHECKSUM_MANIFEST_NAME}`);
|
|
128
|
+
writeFileSync(join(vendorDir, CHECKSUM_MANIFEST_NAME), checksumText);
|
|
129
|
+
const checksums = parseChecksumFile(checksumText);
|
|
130
|
+
|
|
131
|
+
const destination = join(vendorDir, artifacts.daemon);
|
|
132
|
+
const tempDestination = `${destination}.download`;
|
|
133
|
+
rmSync(tempDestination, { force: true });
|
|
134
|
+
await downloadFile(`${releaseBaseUrl}/${artifacts.daemon}`, tempDestination);
|
|
135
|
+
const actual = sha256(readFileSync(tempDestination));
|
|
136
|
+
const expected = checksums.get(artifacts.daemon);
|
|
137
|
+
try {
|
|
138
|
+
// A missing manifest entry is as fatal as a mismatch: an unverifiable
|
|
139
|
+
// download is not a verified one.
|
|
140
|
+
verifyChecksum(artifacts.daemon, actual, expected);
|
|
141
|
+
} catch (error) {
|
|
142
|
+
rmSync(tempDestination, { force: true });
|
|
143
|
+
throw error;
|
|
144
|
+
}
|
|
145
|
+
rmSync(destination, { force: true });
|
|
146
|
+
copyFileSync(tempDestination, destination);
|
|
147
|
+
rmSync(tempDestination, { force: true });
|
|
148
|
+
prepareBinary(destination);
|
|
149
|
+
|
|
150
|
+
console.log(`postinstall: installed the release daemon binary for ${process.platform}-${process.arch}`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Places the sqlite-vec native addon at `vendor/lib/sqlite-vec-<platform>-
|
|
155
|
+
* <arch>/vec0.<suffix>` — see the file banner for why this is a separate,
|
|
156
|
+
* equally load-bearing step from `installPlatformBinary`, not an optional
|
|
157
|
+
* extra. Mirrors that function's gating and verification exactly (skip on an
|
|
158
|
+
* unsupported target, `--no-download`, or a source checkout; the smoke-test
|
|
159
|
+
* local-source-dir seam; checksum-verify-then-place with no partial state on
|
|
160
|
+
* failure) so the two artifacts never drift in how they are trusted.
|
|
161
|
+
*/
|
|
162
|
+
async function installSqliteVecAddon() {
|
|
163
|
+
const asset = resolveSqliteVecAsset(process.platform, process.arch);
|
|
164
|
+
if (!asset) {
|
|
165
|
+
console.log(`postinstall: no sqlite-vec addon for ${process.platform}-${process.arch}; skipping addon install`);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (noDownload) {
|
|
170
|
+
console.log('postinstall: skipping sqlite-vec addon install (--no-download)');
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (isSourceCheckout()) {
|
|
175
|
+
console.log('postinstall: source checkout detected; skipping release sqlite-vec addon install');
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const addonDir = join(projectRoot, 'vendor', 'lib', asset.dirName);
|
|
180
|
+
mkdirSync(addonDir, { recursive: true });
|
|
181
|
+
const destination = join(addonDir, asset.fileName);
|
|
182
|
+
|
|
183
|
+
const localSourceDir = process.env.GOODVIBES_ASSET_SOURCE_DIR?.trim();
|
|
184
|
+
if (localSourceDir) {
|
|
185
|
+
const sourcePath = join(localSourceDir, 'lib', asset.dirName, asset.fileName);
|
|
186
|
+
if (!existsSync(sourcePath)) {
|
|
187
|
+
throw new Error(`missing local sqlite-vec addon for postinstall smoke: ${sourcePath}`);
|
|
188
|
+
}
|
|
189
|
+
copyFileSync(sourcePath, destination);
|
|
190
|
+
console.log(`postinstall: installed local smoke-test sqlite-vec addon for ${process.platform}-${process.arch}`);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const releaseBaseUrl =
|
|
195
|
+
process.env.GOODVIBES_RELEASE_BASE_URL?.trim() ||
|
|
196
|
+
`${resolveRepositoryBaseUrl()}/releases/download/v${pkg.version}`;
|
|
197
|
+
|
|
198
|
+
// A fresh manifest fetch (rather than sharing the one `installPlatformBinary`
|
|
199
|
+
// already wrote to vendor/) keeps this function independently correct and
|
|
200
|
+
// testable — the extra request is a one-time postinstall cost, not a
|
|
201
|
+
// per-boot one.
|
|
202
|
+
const checksumText = await downloadText(`${releaseBaseUrl}/${CHECKSUM_MANIFEST_NAME}`);
|
|
203
|
+
const checksums = parseChecksumFile(checksumText);
|
|
204
|
+
|
|
205
|
+
const tempDestination = `${destination}.download`;
|
|
206
|
+
rmSync(tempDestination, { force: true });
|
|
207
|
+
await downloadFile(`${releaseBaseUrl}/${asset.assetName}`, tempDestination);
|
|
208
|
+
const actual = sha256(readFileSync(tempDestination));
|
|
209
|
+
const expected = checksums.get(asset.assetName);
|
|
210
|
+
try {
|
|
211
|
+
verifyChecksum(asset.assetName, actual, expected);
|
|
212
|
+
} catch (error) {
|
|
213
|
+
rmSync(tempDestination, { force: true });
|
|
214
|
+
throw error;
|
|
215
|
+
}
|
|
216
|
+
rmSync(destination, { force: true });
|
|
217
|
+
copyFileSync(tempDestination, destination);
|
|
218
|
+
rmSync(tempDestination, { force: true });
|
|
219
|
+
|
|
220
|
+
console.log(`postinstall: installed the sqlite-vec addon for ${process.platform}-${process.arch}`);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async function main() {
|
|
224
|
+
await installPlatformBinary();
|
|
225
|
+
await installSqliteVecAddon();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Guarded so this module can be imported by tests (to exercise verifyChecksum,
|
|
229
|
+
// parseChecksumFile and friends) without triggering a real network install as a
|
|
230
|
+
// side effect of the import.
|
|
231
|
+
if (import.meta.main) {
|
|
232
|
+
await main();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export {
|
|
236
|
+
verifyChecksum,
|
|
237
|
+
parseChecksumFile,
|
|
238
|
+
sha256,
|
|
239
|
+
resolveArtifactNames,
|
|
240
|
+
resolveSqliteVecAsset,
|
|
241
|
+
CHECKSUM_MANIFEST_NAME,
|
|
242
|
+
installPlatformBinary,
|
|
243
|
+
installSqliteVecAddon,
|
|
244
|
+
};
|