linkgravity 1.5.5 → 1.5.7
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/bin/cli.js +15 -1
- package/bin/setup.js +21 -0
- package/hooks/hook.py +14 -1
- package/npm-scripts/ensure-env.js +40 -0
- package/npm-scripts/prepare.js +5 -7
- package/npm-scripts/register-hook.js +32 -23
- package/package.json +1 -2
- package/src/api/server.py +12 -3
- package/src/config.py +6 -0
- package/voice-service/receiver.js +21 -5
- package/voice-service/tts.js +2 -1
- package/npm-scripts/postinstall.js +0 -62
package/bin/cli.js
CHANGED
|
@@ -15,6 +15,7 @@ const {
|
|
|
15
15
|
} = require('./platforms');
|
|
16
16
|
|
|
17
17
|
const { python: pythonExe, isWin } = require('../npm-scripts/venv-paths');
|
|
18
|
+
const { isEnvironmentReady } = require('../npm-scripts/ensure-env');
|
|
18
19
|
|
|
19
20
|
const cmd = process.argv[2];
|
|
20
21
|
|
|
@@ -200,6 +201,10 @@ function verifyStartup() {
|
|
|
200
201
|
finish(false);
|
|
201
202
|
}, 30000);
|
|
202
203
|
|
|
204
|
+
const isBenignShutdownNoise = (str) =>
|
|
205
|
+
str.includes('asyncio.exceptions.CancelledError') &&
|
|
206
|
+
str.includes('Application.stop() complete');
|
|
207
|
+
|
|
203
208
|
const checkLog = (data) => {
|
|
204
209
|
if (settled) return;
|
|
205
210
|
const str = data.toString();
|
|
@@ -210,7 +215,8 @@ function verifyStartup() {
|
|
|
210
215
|
errorDetectionArmed &&
|
|
211
216
|
(str.includes('Traceback (most recent call last):') ||
|
|
212
217
|
str.includes('Error:') ||
|
|
213
|
-
str.includes('Exception:'))
|
|
218
|
+
str.includes('Exception:')) &&
|
|
219
|
+
!isBenignShutdownNoise(str)
|
|
214
220
|
) {
|
|
215
221
|
console.log(`\n\n${color.yellow}❌ Error detected during startup:${color.reset}`);
|
|
216
222
|
const errorLines = str
|
|
@@ -373,6 +379,14 @@ if (cmd === 'version' || cmd === '-v' || cmd === '--version') {
|
|
|
373
379
|
process.exit(1);
|
|
374
380
|
}
|
|
375
381
|
|
|
382
|
+
if (!isEnvironmentReady()) {
|
|
383
|
+
console.log(
|
|
384
|
+
`\n${color.yellow}⚠${color.reset} Python environment isn't set up yet - ` +
|
|
385
|
+
`run ${color.cyan}lgy setup${color.reset} first (it installs everything on its first run).\n`,
|
|
386
|
+
);
|
|
387
|
+
process.exit(1);
|
|
388
|
+
}
|
|
389
|
+
|
|
376
390
|
info('Starting LinkGravity daemon...');
|
|
377
391
|
runPm2(['start', LGY_SCRIPT_PATH, '--interpreter', pythonExe, '--name', LGY_PM2_NAME]);
|
|
378
392
|
verifyStartup().then((ok) => process.exit(ok ? 0 : 1));
|
package/bin/setup.js
CHANGED
|
@@ -446,6 +446,27 @@ async function runSetup() {
|
|
|
446
446
|
console.log();
|
|
447
447
|
p.intro(`${color.cyan}▶ LinkGravity Setup Wizard${color.reset}`);
|
|
448
448
|
|
|
449
|
+
const { ensureEnvironment, isEnvironmentReady } = require('../npm-scripts/ensure-env');
|
|
450
|
+
if (!isEnvironmentReady()) {
|
|
451
|
+
console.log();
|
|
452
|
+
ensureEnvironment();
|
|
453
|
+
console.log();
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const registerHook = require('../npm-scripts/register-hook');
|
|
457
|
+
if (!registerHook.isHookRegistered()) {
|
|
458
|
+
const consent = await p.confirm({
|
|
459
|
+
message:
|
|
460
|
+
"Register LinkGravity's approval hook with agy? (required for tool-call approval - lets LinkGravity gate agy's actions through Discord/Telegram/Slack)",
|
|
461
|
+
initialValue: true,
|
|
462
|
+
});
|
|
463
|
+
if (p.isCancel(consent)) {
|
|
464
|
+
p.cancel('Setup cancelled.');
|
|
465
|
+
process.exit(0);
|
|
466
|
+
}
|
|
467
|
+
if (consent) registerHook({ allowFirstTimeCreate: true });
|
|
468
|
+
}
|
|
469
|
+
|
|
449
470
|
while (true) {
|
|
450
471
|
const settings = getSettings();
|
|
451
472
|
const options = Object.entries(PLATFORMS).map(([key, def]) => {
|
package/hooks/hook.py
CHANGED
|
@@ -4,6 +4,17 @@ import os
|
|
|
4
4
|
import sys
|
|
5
5
|
import urllib.error
|
|
6
6
|
import urllib.request
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
LGY_CONFIG_FILE = Path.home() / ".gemini" / "linkgravity" / "lgy.json"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _load_approve_token():
|
|
13
|
+
try:
|
|
14
|
+
with open(LGY_CONFIG_FILE, encoding="utf-8") as f:
|
|
15
|
+
return json.load(f).get("approve_token", "")
|
|
16
|
+
except Exception:
|
|
17
|
+
return ""
|
|
7
18
|
|
|
8
19
|
|
|
9
20
|
def main():
|
|
@@ -34,7 +45,9 @@ def main():
|
|
|
34
45
|
).encode("utf-8")
|
|
35
46
|
|
|
36
47
|
req = urllib.request.Request(
|
|
37
|
-
"http://localhost:18080/approve",
|
|
48
|
+
"http://localhost:18080/approve",
|
|
49
|
+
data=payload,
|
|
50
|
+
headers={"Content-Type": "application/json", "X-LGY-Token": _load_approve_token()},
|
|
38
51
|
)
|
|
39
52
|
|
|
40
53
|
try:
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const { execSync } = require('child_process');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const { pip: venvPip, python: venvPython, workspaceDir, repoRoot } = require('./venv-paths');
|
|
7
|
+
|
|
8
|
+
const isWin = os.platform() === 'win32';
|
|
9
|
+
const pyCmd = isWin ? 'python' : 'python3';
|
|
10
|
+
|
|
11
|
+
function isEnvironmentReady() {
|
|
12
|
+
return fs.existsSync(venvPython);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function ensureEnvironment() {
|
|
16
|
+
if (isEnvironmentReady()) return;
|
|
17
|
+
|
|
18
|
+
console.log('⚙️ Setting up Python Virtual Environment...');
|
|
19
|
+
console.log(
|
|
20
|
+
` (in ${path.join(workspaceDir, 'venv')} - not inside this install, so it survives`,
|
|
21
|
+
);
|
|
22
|
+
console.log(' package updates/reinstalls and works the same whether this is a global');
|
|
23
|
+
console.log(' `npm install -g linkgravity` or a local dev clone.)');
|
|
24
|
+
|
|
25
|
+
fs.mkdirSync(workspaceDir, { recursive: true });
|
|
26
|
+
execSync(`${pyCmd} -m venv "${path.join(workspaceDir, 'venv')}"`, { stdio: 'inherit' });
|
|
27
|
+
|
|
28
|
+
console.log('📦 Installing Python dependencies...');
|
|
29
|
+
execSync(`"${venvPip}" install -r requirements.txt`, { stdio: 'inherit', cwd: repoRoot });
|
|
30
|
+
|
|
31
|
+
console.log('🎙️ Installing Voice Service dependencies...');
|
|
32
|
+
execSync('npm install', {
|
|
33
|
+
stdio: 'inherit',
|
|
34
|
+
cwd: path.join(repoRoot, 'voice-service'),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
console.log('✅ Environment ready.');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = { ensureEnvironment, isEnvironmentReady };
|
package/npm-scripts/prepare.js
CHANGED
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
// Runs at `prepare` time - which npm only triggers for a local `npm
|
|
3
3
|
// install` inside this repo (i.e. a git clone / contributor checkout),
|
|
4
4
|
// never for `npm install -g linkgravity` end users installing the published
|
|
5
|
-
// package from the registry. That's
|
|
6
|
-
//
|
|
7
|
-
// for everyone, including end users who don't need any of this.
|
|
5
|
+
// package from the registry. That's why dev-only setup (git hooks, lint
|
|
6
|
+
// tooling) lives here rather than running for every end user.
|
|
8
7
|
'use strict';
|
|
9
8
|
const { execSync } = require('child_process');
|
|
10
9
|
const fs = require('fs');
|
|
@@ -13,14 +12,13 @@ const { repoRoot, pip, preCommit } = require('./venv-paths');
|
|
|
13
12
|
if (!fs.existsSync(pip)) {
|
|
14
13
|
console.warn(
|
|
15
14
|
'⚠️ No venv found yet - skipping dev tooling install and git hook setup. ' +
|
|
16
|
-
'Run `
|
|
15
|
+
'Run `node bin/cli.js setup` to create it, then `npm install` again.',
|
|
17
16
|
);
|
|
18
17
|
return;
|
|
19
18
|
}
|
|
20
19
|
|
|
21
|
-
// 1. Dev-only Python tooling into the same venv
|
|
22
|
-
// created (
|
|
23
|
-
// order): ruff (editor/manual use) and pre-commit itself, which is
|
|
20
|
+
// 1. Dev-only Python tooling into the same venv `lgy setup` already
|
|
21
|
+
// created: ruff (editor/manual use) and pre-commit itself, which is
|
|
24
22
|
// what actually runs the hooks declared in .pre-commit-config.yaml.
|
|
25
23
|
try {
|
|
26
24
|
execSync(`"${pip}" install -r requirements-dev.txt`, { stdio: 'inherit', cwd: repoRoot });
|
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
-
//
|
|
3
|
-
// (schema: https://antigravity.google/docs/hooks). Runs on every `npm
|
|
4
|
-
// install` so paths stay correct if this checkout moves, identifying its
|
|
5
|
-
// own entries by `name` (not command string) so a stale path gets fixed
|
|
6
|
-
// in place rather than duplicated, leaving any other configured hooks
|
|
7
|
-
// untouched. Only ever overwrites `command` - never `type`/`timeout`,
|
|
8
|
-
// which the user may have customized - logging old/new values on change.
|
|
2
|
+
// Only ever overwrites `command` on an existing entry - never `type`/`timeout`, which the user may have customized.
|
|
9
3
|
const fs = require('fs');
|
|
10
4
|
const path = require('path');
|
|
11
5
|
const os = require('os');
|
|
@@ -13,11 +7,9 @@ const { repoRoot, python: venvPython } = require('./venv-paths');
|
|
|
13
7
|
|
|
14
8
|
const hooksJsonPath = path.join(os.homedir(), '.gemini', 'config', 'hooks.json');
|
|
15
9
|
|
|
16
|
-
// PreToolUse:
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
// keep going instead of ending the turn with that result lost. Flat
|
|
20
|
-
// array per schema (no matcher - nothing to match tool names against).
|
|
10
|
+
// PreToolUse/Stop meanings are agy's own hook contract: Stop fires when agy is about to
|
|
11
|
+
// end a turn, and if fullyIdle is false (an async run_command still in flight), stop_hook.py
|
|
12
|
+
// tells agy to keep going instead of losing that result.
|
|
21
13
|
const HOOK_REGISTRATIONS = [
|
|
22
14
|
{
|
|
23
15
|
eventType: 'PreToolUse',
|
|
@@ -35,9 +27,6 @@ const HOOK_REGISTRATIONS = [
|
|
|
35
27
|
},
|
|
36
28
|
];
|
|
37
29
|
|
|
38
|
-
// Hooks retired from HOOK_REGISTRATIONS but listed here so an existing
|
|
39
|
-
// install actually gets the stale entry removed from hooks.json, instead
|
|
40
|
-
// of a zombie entry pointing at a script that no longer exists.
|
|
41
30
|
const RETIRED_HOOKS = [{ eventType: 'PreInvocation', name: 'wait-ms-before-async-reminder' }];
|
|
42
31
|
|
|
43
32
|
function loadHooksConfig() {
|
|
@@ -47,9 +36,6 @@ function loadHooksConfig() {
|
|
|
47
36
|
try {
|
|
48
37
|
return JSON.parse(fs.readFileSync(hooksJsonPath, 'utf8'));
|
|
49
38
|
} catch (err) {
|
|
50
|
-
// Back up rather than silently clobbering whatever was there -
|
|
51
|
-
// it may have other hooks configured that have nothing to do
|
|
52
|
-
// with this project.
|
|
53
39
|
const backupPath = `${hooksJsonPath}.corrupted-${Date.now()}`;
|
|
54
40
|
fs.copyFileSync(hooksJsonPath, backupPath);
|
|
55
41
|
console.warn(
|
|
@@ -76,7 +62,6 @@ function findHookEntry(config, eventType, name, wrapInMatcher) {
|
|
|
76
62
|
}
|
|
77
63
|
return hookEntry;
|
|
78
64
|
}
|
|
79
|
-
// Flat array (Stop/PreInvocation/PostInvocation) - no matcher wrapper.
|
|
80
65
|
let hookEntry = config.hooks[eventType].find((h) => h.name === name);
|
|
81
66
|
if (!hookEntry) {
|
|
82
67
|
hookEntry = { name };
|
|
@@ -94,8 +79,6 @@ function removeRetiredHooks(config) {
|
|
|
94
79
|
const nextArr = [];
|
|
95
80
|
for (const entry of arr) {
|
|
96
81
|
if (Array.isArray(entry.hooks)) {
|
|
97
|
-
// Matcher-wrapped shape - drop the matcher block too if
|
|
98
|
-
// nothing's left in it.
|
|
99
82
|
const beforeLen = entry.hooks.length;
|
|
100
83
|
entry.hooks = entry.hooks.filter((h) => h.name !== retired.name);
|
|
101
84
|
if (entry.hooks.length !== beforeLen) {
|
|
@@ -106,7 +89,6 @@ function removeRetiredHooks(config) {
|
|
|
106
89
|
}
|
|
107
90
|
if (entry.hooks.length > 0) nextArr.push(entry);
|
|
108
91
|
} else {
|
|
109
|
-
// Flat shape.
|
|
110
92
|
if (entry.name === retired.name) {
|
|
111
93
|
removedAny = true;
|
|
112
94
|
console.log(
|
|
@@ -122,9 +104,24 @@ function removeRetiredHooks(config) {
|
|
|
122
104
|
return removedAny;
|
|
123
105
|
}
|
|
124
106
|
|
|
125
|
-
function registerHook() {
|
|
107
|
+
function registerHook({ allowFirstTimeCreate = true } = {}) {
|
|
126
108
|
const config = loadHooksConfig();
|
|
127
109
|
config.hooks = config.hooks || {};
|
|
110
|
+
|
|
111
|
+
const isFirstTime = HOOK_REGISTRATIONS.some((reg) => {
|
|
112
|
+
const arr = config.hooks[reg.eventType] || [];
|
|
113
|
+
return reg.wrapInMatcher
|
|
114
|
+
? !arr.some((m) => (m.hooks || []).some((h) => h.name === reg.name))
|
|
115
|
+
: !arr.some((h) => h.name === reg.name);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
if (isFirstTime && !allowFirstTimeCreate) {
|
|
119
|
+
console.log(
|
|
120
|
+
"ℹ️ LinkGravity's Discord/Telegram/Slack approval hook isn't registered with agy yet - run `lgy setup` to enable it.",
|
|
121
|
+
);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
128
125
|
let wroteChange = false;
|
|
129
126
|
let backedUp = false;
|
|
130
127
|
|
|
@@ -175,7 +172,19 @@ function registerHook() {
|
|
|
175
172
|
}
|
|
176
173
|
}
|
|
177
174
|
|
|
175
|
+
function isHookRegistered() {
|
|
176
|
+
const config = loadHooksConfig();
|
|
177
|
+
config.hooks = config.hooks || {};
|
|
178
|
+
return HOOK_REGISTRATIONS.every((reg) => {
|
|
179
|
+
const arr = config.hooks[reg.eventType] || [];
|
|
180
|
+
return reg.wrapInMatcher
|
|
181
|
+
? arr.some((m) => (m.hooks || []).some((h) => h.name === reg.name))
|
|
182
|
+
: arr.some((h) => h.name === reg.name);
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
178
186
|
module.exports = registerHook;
|
|
187
|
+
module.exports.isHookRegistered = isHookRegistered;
|
|
179
188
|
|
|
180
189
|
if (require.main === module) {
|
|
181
190
|
registerHook();
|
package/package.json
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "linkgravity",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.7",
|
|
4
4
|
"description": "Discord/Telegram bot bridge for the Antigravity (agy) CLI, with voice interaction support",
|
|
5
5
|
"scripts": {
|
|
6
|
-
"postinstall": "node npm-scripts/postinstall.js",
|
|
7
6
|
"start": "node npm-scripts/run-dev.js",
|
|
8
7
|
"dev": "node npm-scripts/run-dev.js",
|
|
9
8
|
"format": "prettier --write \"bin/**/*.js\" \"npm-scripts/**/*.js\" \"voice-service/*.js\"",
|
package/src/api/server.py
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
from aiohttp import web
|
|
2
2
|
|
|
3
|
-
from config import logger, session_manager
|
|
3
|
+
from config import bot_settings, logger, session_manager
|
|
4
|
+
|
|
5
|
+
LGY_TOKEN_HEADER = "X-LGY-Token"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@web.middleware
|
|
9
|
+
async def auth_middleware(request, handler):
|
|
10
|
+
if request.headers.get(LGY_TOKEN_HEADER) != bot_settings.get("approve_token"):
|
|
11
|
+
return web.json_response({"error": "unauthorized"}, status=403)
|
|
12
|
+
return await handler(request)
|
|
4
13
|
|
|
5
14
|
|
|
6
15
|
def is_tool_allowed(tool_name, tool_input):
|
|
@@ -20,7 +29,7 @@ def is_tool_allowed(tool_name, tool_input):
|
|
|
20
29
|
|
|
21
30
|
|
|
22
31
|
async def setup_webhook_server(bot):
|
|
23
|
-
app = web.Application(client_max_size=50 * 1024 * 1024)
|
|
32
|
+
app = web.Application(client_max_size=50 * 1024 * 1024, middlewares=[auth_middleware])
|
|
24
33
|
app["bot"] = bot
|
|
25
34
|
|
|
26
35
|
from api.ui_routes import handle_approve_request
|
|
@@ -41,6 +50,6 @@ async def setup_webhook_server(bot):
|
|
|
41
50
|
|
|
42
51
|
runner = web.AppRunner(app)
|
|
43
52
|
await runner.setup()
|
|
44
|
-
site = web.TCPSite(runner, "
|
|
53
|
+
site = web.TCPSite(runner, "127.0.0.1", 18080)
|
|
45
54
|
await site.start()
|
|
46
55
|
logger.info("Webhook / STT Server started on port 18080")
|
package/src/config.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import os
|
|
2
|
+
import secrets
|
|
2
3
|
from pathlib import Path
|
|
3
4
|
|
|
4
5
|
from core.atomic_io import atomic_write_json, safe_load_json
|
|
@@ -30,6 +31,7 @@ DEFAULT_LGY_CONFIG = {
|
|
|
30
31
|
"tts_enabled": True,
|
|
31
32
|
# Sticky default for /new sessions, set whenever /model succeeds.
|
|
32
33
|
"default_model": "",
|
|
34
|
+
"approve_token": "",
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
|
|
@@ -71,6 +73,10 @@ def save_bot_settings(data):
|
|
|
71
73
|
|
|
72
74
|
bot_settings = load_bot_settings()
|
|
73
75
|
|
|
76
|
+
if not bot_settings.get("approve_token"):
|
|
77
|
+
bot_settings["approve_token"] = secrets.token_hex(24)
|
|
78
|
+
save_bot_settings(bot_settings)
|
|
79
|
+
|
|
74
80
|
from core.logger import init_logger
|
|
75
81
|
from core.session_manager import SessionManager
|
|
76
82
|
|
|
@@ -5,6 +5,7 @@ const { googleSTT } = require('./stt');
|
|
|
5
5
|
const { getDetectorForUser, feedPCMToDetector, WAKE_MATCH_THRESHOLD } = require('./wakeword');
|
|
6
6
|
const { interruptTTS } = require('./tts');
|
|
7
7
|
const state = require('./state');
|
|
8
|
+
const { aglConfig } = require('./config');
|
|
8
9
|
const { activeStreams, enrollingUsers, isPlaying, wakeWordOptedOut, runtime, isGuildActive } =
|
|
9
10
|
state;
|
|
10
11
|
|
|
@@ -108,7 +109,10 @@ function setupReceiver(connection, guildId, client) {
|
|
|
108
109
|
partialSent = true;
|
|
109
110
|
fetch('http://127.0.0.1:18080/stt_partial', {
|
|
110
111
|
method: 'POST',
|
|
111
|
-
headers: {
|
|
112
|
+
headers: {
|
|
113
|
+
'Content-Type': 'application/json',
|
|
114
|
+
'X-LGY-Token': aglConfig.approve_token,
|
|
115
|
+
},
|
|
112
116
|
body: JSON.stringify({ guild_id: guildId, text }),
|
|
113
117
|
}).catch((err) =>
|
|
114
118
|
console.error(`[STT] Failed to send partial text to Python:`, err.message),
|
|
@@ -248,7 +252,10 @@ function setupReceiver(connection, guildId, client) {
|
|
|
248
252
|
`http://127.0.0.1:18080/enroll_sample?user_id=${encodeURIComponent(userId)}`,
|
|
249
253
|
{
|
|
250
254
|
method: 'POST',
|
|
251
|
-
headers: {
|
|
255
|
+
headers: {
|
|
256
|
+
'Content-Type': 'application/octet-stream',
|
|
257
|
+
'X-LGY-Token': aglConfig.approve_token,
|
|
258
|
+
},
|
|
252
259
|
body: wavBuffer,
|
|
253
260
|
},
|
|
254
261
|
);
|
|
@@ -263,7 +270,10 @@ function setupReceiver(connection, guildId, client) {
|
|
|
263
270
|
if (partialSent) {
|
|
264
271
|
fetch('http://127.0.0.1:18080/stt_partial_cancel', {
|
|
265
272
|
method: 'POST',
|
|
266
|
-
headers: {
|
|
273
|
+
headers: {
|
|
274
|
+
'Content-Type': 'application/json',
|
|
275
|
+
'X-LGY-Token': aglConfig.approve_token,
|
|
276
|
+
},
|
|
267
277
|
body: JSON.stringify({ guild_id: guildId }),
|
|
268
278
|
}).catch(() => {});
|
|
269
279
|
}
|
|
@@ -277,7 +287,10 @@ function setupReceiver(connection, guildId, client) {
|
|
|
277
287
|
if (partialSent) {
|
|
278
288
|
fetch('http://127.0.0.1:18080/stt_partial_cancel', {
|
|
279
289
|
method: 'POST',
|
|
280
|
-
headers: {
|
|
290
|
+
headers: {
|
|
291
|
+
'Content-Type': 'application/json',
|
|
292
|
+
'X-LGY-Token': aglConfig.approve_token,
|
|
293
|
+
},
|
|
281
294
|
body: JSON.stringify({ guild_id: guildId }),
|
|
282
295
|
}).catch(() => {});
|
|
283
296
|
}
|
|
@@ -291,7 +304,10 @@ function setupReceiver(connection, guildId, client) {
|
|
|
291
304
|
try {
|
|
292
305
|
await fetch('http://127.0.0.1:18080/stt_input', {
|
|
293
306
|
method: 'POST',
|
|
294
|
-
headers: {
|
|
307
|
+
headers: {
|
|
308
|
+
'Content-Type': 'application/json',
|
|
309
|
+
'X-LGY-Token': aglConfig.approve_token,
|
|
310
|
+
},
|
|
295
311
|
body: JSON.stringify({
|
|
296
312
|
user_id: userId,
|
|
297
313
|
guild_id: guildId,
|
package/voice-service/tts.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const { Readable } = require('stream');
|
|
2
2
|
const { createAudioPlayer, createAudioResource, AudioPlayerStatus } = require('@discordjs/voice');
|
|
3
3
|
const { players, audioQueues, isPlaying, connections, suppressNotifyMap } = require('./state');
|
|
4
|
+
const { aglConfig } = require('./config');
|
|
4
5
|
|
|
5
6
|
function interruptTTS(guildId) {
|
|
6
7
|
const player = players.get(guildId);
|
|
@@ -25,7 +26,7 @@ async function notifyTtsFinished(guild_id) {
|
|
|
25
26
|
try {
|
|
26
27
|
await fetch('http://127.0.0.1:18080/tts_finished', {
|
|
27
28
|
method: 'POST',
|
|
28
|
-
headers: { 'Content-Type': 'application/json' },
|
|
29
|
+
headers: { 'Content-Type': 'application/json', 'X-LGY-Token': aglConfig.approve_token },
|
|
29
30
|
body: JSON.stringify({ guild_id }),
|
|
30
31
|
});
|
|
31
32
|
} catch (err) {
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
const { execSync } = require('child_process');
|
|
2
|
-
const os = require('os');
|
|
3
|
-
const path = require('path');
|
|
4
|
-
const fs = require('fs');
|
|
5
|
-
const { pip: venvPip, workspaceDir } = require('./venv-paths');
|
|
6
|
-
|
|
7
|
-
console.log('⚙️ Setting up Python Virtual Environment...');
|
|
8
|
-
console.log(` (in ${path.join(workspaceDir, 'venv')} - not inside this install, so it survives`);
|
|
9
|
-
console.log(' package updates/reinstalls and works the same whether this is a global');
|
|
10
|
-
console.log(' `npm install -g linkgravity` or a local dev clone.)');
|
|
11
|
-
|
|
12
|
-
// Use python on Windows, python3 on Mac/Linux - this is the *system*
|
|
13
|
-
// python used only to create the venv below; once it exists, every
|
|
14
|
-
// other script (this one included) goes through venv-paths.js instead.
|
|
15
|
-
const isWin = os.platform() === 'win32';
|
|
16
|
-
const pyCmd = isWin ? 'python' : 'python3';
|
|
17
|
-
|
|
18
|
-
async function main() {
|
|
19
|
-
try {
|
|
20
|
-
// 1. Create Python virtual environment (venv) in the fixed
|
|
21
|
-
// workspace dir, not in cwd - see venv-paths.js for why.
|
|
22
|
-
fs.mkdirSync(workspaceDir, { recursive: true });
|
|
23
|
-
execSync(`${pyCmd} -m venv "${path.join(workspaceDir, 'venv')}"`, { stdio: 'inherit' });
|
|
24
|
-
|
|
25
|
-
// 2. Install Python packages
|
|
26
|
-
console.log('📦 Installing Python dependencies...');
|
|
27
|
-
execSync(`"${venvPip}" install -r requirements.txt`, { stdio: 'inherit' });
|
|
28
|
-
|
|
29
|
-
// 3. Install Node.js voice service packages (includes
|
|
30
|
-
// rustpotter-web, which handles both wake-word detection AND
|
|
31
|
-
// building .rpw reference files in-process - no separate
|
|
32
|
-
// binary download needed for either).
|
|
33
|
-
console.log('🎙️ Installing Voice Service dependencies...');
|
|
34
|
-
execSync('npm install', {
|
|
35
|
-
stdio: 'inherit',
|
|
36
|
-
cwd: path.join(__dirname, '..', 'voice-service'),
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
// 4. Register this install's location with agy as its
|
|
40
|
-
// tool-approval hook (~/.gemini/config/hooks.json) - always
|
|
41
|
-
// re-run so the registered path self-heals if this checkout
|
|
42
|
-
// gets moved/renamed later, instead of silently going stale.
|
|
43
|
-
// Guarded on its own: agy may not be installed/configured yet
|
|
44
|
-
// on a brand new machine, and that shouldn't fail the rest of
|
|
45
|
-
// the install - just means the hook needs registering once agy
|
|
46
|
-
// itself is set up (re-running `npm install` after does it).
|
|
47
|
-
try {
|
|
48
|
-
require('./register-hook')();
|
|
49
|
-
} catch (err) {
|
|
50
|
-
console.warn(
|
|
51
|
-
`⚠️ Couldn't register the agy tool-approval hook: ${err.message.split('\n')[0]}`,
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
console.log('✅ Installation complete!');
|
|
56
|
-
} catch (error) {
|
|
57
|
-
console.error('❌ Installation failed. Please ensure Python 3.10+ is installed.');
|
|
58
|
-
process.exit(1);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
main();
|