lazy-gravity 0.6.0 → 0.6.2
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/bot/index.js +62 -17
- package/dist/bot/telegramMessageHandler.js +3 -0
- package/dist/services/cdpService.js +962 -105
- package/dist/services/responseMonitor.js +235 -48
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ResponseMonitor = exports.RESPONSE_SELECTORS = void 0;
|
|
4
|
+
exports.captureResponseMonitorBaseline = captureResponseMonitorBaseline;
|
|
4
5
|
const logger_1 = require("../utils/logger");
|
|
5
6
|
const assistantDomExtractor_1 = require("./assistantDomExtractor");
|
|
6
7
|
/** Lean DOM selectors for response extraction */
|
|
@@ -433,6 +434,125 @@ exports.RESPONSE_SELECTORS = {
|
|
|
433
434
|
return diag;
|
|
434
435
|
})()`,
|
|
435
436
|
};
|
|
437
|
+
/**
|
|
438
|
+
* Prefer the active runtime context first, then fall back to any discovered contexts.
|
|
439
|
+
*/
|
|
440
|
+
function getOrderedContextTargets(cdpService) {
|
|
441
|
+
const primaryId = cdpService.getPrimaryContextId?.() ?? null;
|
|
442
|
+
const rawContexts = cdpService.getContexts?.() ?? [];
|
|
443
|
+
const contexts = rawContexts
|
|
444
|
+
.filter((ctx) => ctx && typeof ctx.id === 'number')
|
|
445
|
+
.map((ctx) => ({
|
|
446
|
+
id: ctx.id,
|
|
447
|
+
name: typeof ctx.name === 'string' ? ctx.name : undefined,
|
|
448
|
+
url: typeof ctx.url === 'string' ? ctx.url : undefined,
|
|
449
|
+
}));
|
|
450
|
+
if (contexts.length === 0) {
|
|
451
|
+
return primaryId !== null ? [{ id: primaryId }] : [];
|
|
452
|
+
}
|
|
453
|
+
if (primaryId === null) {
|
|
454
|
+
return contexts;
|
|
455
|
+
}
|
|
456
|
+
const primary = contexts.find((ctx) => ctx.id === primaryId);
|
|
457
|
+
const ordered = primary ? [primary] : [{ id: primaryId }];
|
|
458
|
+
for (const ctx of contexts) {
|
|
459
|
+
if (ctx.id !== primaryId)
|
|
460
|
+
ordered.push(ctx);
|
|
461
|
+
}
|
|
462
|
+
return ordered;
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Evaluate an expression across known runtime contexts until one returns an acceptable value.
|
|
466
|
+
*/
|
|
467
|
+
async function evaluateAcrossContexts(cdpService, expression, accept, options) {
|
|
468
|
+
const awaitPromise = options?.awaitPromise ?? true;
|
|
469
|
+
const targets = getOrderedContextTargets(cdpService);
|
|
470
|
+
let firstValue = null;
|
|
471
|
+
let lastError = null;
|
|
472
|
+
if (targets.length === 0) {
|
|
473
|
+
const result = await cdpService.call('Runtime.evaluate', {
|
|
474
|
+
expression,
|
|
475
|
+
returnByValue: true,
|
|
476
|
+
awaitPromise,
|
|
477
|
+
});
|
|
478
|
+
return {
|
|
479
|
+
value: (result?.result?.value ?? null),
|
|
480
|
+
contextId: null,
|
|
481
|
+
contextName: null,
|
|
482
|
+
contextUrl: null,
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
for (const target of targets) {
|
|
486
|
+
try {
|
|
487
|
+
const result = await cdpService.call('Runtime.evaluate', {
|
|
488
|
+
expression,
|
|
489
|
+
returnByValue: true,
|
|
490
|
+
awaitPromise,
|
|
491
|
+
contextId: target.id,
|
|
492
|
+
});
|
|
493
|
+
const value = (result?.result?.value ?? null);
|
|
494
|
+
const probed = {
|
|
495
|
+
value,
|
|
496
|
+
contextId: target.id,
|
|
497
|
+
contextName: target.name ?? null,
|
|
498
|
+
contextUrl: target.url ?? null,
|
|
499
|
+
};
|
|
500
|
+
if (!firstValue)
|
|
501
|
+
firstValue = probed;
|
|
502
|
+
if (accept(value)) {
|
|
503
|
+
return probed;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
catch (error) {
|
|
507
|
+
lastError = error;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
if (firstValue) {
|
|
511
|
+
return firstValue;
|
|
512
|
+
}
|
|
513
|
+
if (lastError) {
|
|
514
|
+
throw lastError;
|
|
515
|
+
}
|
|
516
|
+
return {
|
|
517
|
+
value: null,
|
|
518
|
+
contextId: null,
|
|
519
|
+
contextName: null,
|
|
520
|
+
contextUrl: null,
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Capture the current assistant/output DOM state before sending a new prompt.
|
|
525
|
+
* This avoids races where a fast reply is mistaken for baseline text.
|
|
526
|
+
*/
|
|
527
|
+
async function captureResponseMonitorBaseline(cdpService) {
|
|
528
|
+
let text = null;
|
|
529
|
+
try {
|
|
530
|
+
const textResult = await evaluateAcrossContexts(cdpService, exports.RESPONSE_SELECTORS.RESPONSE_TEXT, (value) => typeof value === 'string' && value.trim().length > 0);
|
|
531
|
+
text = typeof textResult.value === 'string' ? textResult.value.trim() || null : null;
|
|
532
|
+
}
|
|
533
|
+
catch {
|
|
534
|
+
text = null;
|
|
535
|
+
}
|
|
536
|
+
const processLogKeys = new Set();
|
|
537
|
+
try {
|
|
538
|
+
const logResult = await evaluateAcrossContexts(cdpService, exports.RESPONSE_SELECTORS.PROCESS_LOGS, (value) => Array.isArray(value) && value.length > 0);
|
|
539
|
+
const logEntries = logResult.value;
|
|
540
|
+
if (Array.isArray(logEntries)) {
|
|
541
|
+
for (const entry of logEntries) {
|
|
542
|
+
const key = String(entry || '').replace(/\r/g, '').trim().slice(0, 200);
|
|
543
|
+
if (key)
|
|
544
|
+
processLogKeys.add(key);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
catch {
|
|
549
|
+
// best-effort baseline capture
|
|
550
|
+
}
|
|
551
|
+
return {
|
|
552
|
+
text,
|
|
553
|
+
processLogKeys: Array.from(processLogKeys),
|
|
554
|
+
};
|
|
555
|
+
}
|
|
436
556
|
/**
|
|
437
557
|
* Lean AI response monitor.
|
|
438
558
|
*
|
|
@@ -452,6 +572,8 @@ class ResponseMonitor {
|
|
|
452
572
|
onTimeout;
|
|
453
573
|
onPhaseChange;
|
|
454
574
|
onProcessLog;
|
|
575
|
+
initialBaselineText;
|
|
576
|
+
initialSeenProcessLogKeys;
|
|
455
577
|
pollTimer = null;
|
|
456
578
|
isRunning = false;
|
|
457
579
|
lastText = null;
|
|
@@ -462,6 +584,7 @@ class ResponseMonitor {
|
|
|
462
584
|
quotaDetected = false;
|
|
463
585
|
seenProcessLogKeys = new Set();
|
|
464
586
|
structuredDiagLogged = false;
|
|
587
|
+
lastContentContextId = null;
|
|
465
588
|
// CDP disconnect handling (#48)
|
|
466
589
|
isPaused = false;
|
|
467
590
|
onCdpDisconnected = null;
|
|
@@ -480,6 +603,8 @@ class ResponseMonitor {
|
|
|
480
603
|
this.onTimeout = options.onTimeout;
|
|
481
604
|
this.onPhaseChange = options.onPhaseChange;
|
|
482
605
|
this.onProcessLog = options.onProcessLog;
|
|
606
|
+
this.initialBaselineText = options.initialBaselineText;
|
|
607
|
+
this.initialSeenProcessLogKeys = options.initialSeenProcessLogKeys;
|
|
483
608
|
}
|
|
484
609
|
/** Start monitoring */
|
|
485
610
|
async start() {
|
|
@@ -508,28 +633,38 @@ class ResponseMonitor {
|
|
|
508
633
|
this.quotaDetected = false;
|
|
509
634
|
this.seenProcessLogKeys = new Set();
|
|
510
635
|
this.onPhaseChange?.(this.currentPhase, null);
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
const baseResult = await this.cdpService.call('Runtime.evaluate', this.buildEvaluateParams(exports.RESPONSE_SELECTORS.RESPONSE_TEXT));
|
|
514
|
-
const rawValue = baseResult?.result?.value;
|
|
515
|
-
this.baselineText = typeof rawValue === 'string' ? rawValue.trim() || null : null;
|
|
516
|
-
}
|
|
517
|
-
catch {
|
|
518
|
-
this.baselineText = null;
|
|
636
|
+
if (this.initialBaselineText !== undefined) {
|
|
637
|
+
this.baselineText = this.initialBaselineText;
|
|
519
638
|
}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
.filter((s) => s.length > 0)
|
|
528
|
-
.map((s) => s.slice(0, 200)));
|
|
639
|
+
else {
|
|
640
|
+
try {
|
|
641
|
+
const baseResult = await this.evaluateAcrossContexts(exports.RESPONSE_SELECTORS.RESPONSE_TEXT, (value) => typeof value === 'string' && value.trim().length > 0);
|
|
642
|
+
this.baselineText = typeof baseResult.value === 'string' ? baseResult.value.trim() || null : null;
|
|
643
|
+
}
|
|
644
|
+
catch {
|
|
645
|
+
this.baselineText = null;
|
|
529
646
|
}
|
|
530
647
|
}
|
|
531
|
-
|
|
532
|
-
|
|
648
|
+
if (this.initialSeenProcessLogKeys !== undefined) {
|
|
649
|
+
this.seenProcessLogKeys = new Set(this.initialSeenProcessLogKeys
|
|
650
|
+
.map((s) => (s || '').replace(/\r/g, '').trim())
|
|
651
|
+
.filter((s) => s.length > 0)
|
|
652
|
+
.map((s) => s.slice(0, 200)));
|
|
653
|
+
}
|
|
654
|
+
else {
|
|
655
|
+
try {
|
|
656
|
+
const logResult = await this.evaluateAcrossContexts(exports.RESPONSE_SELECTORS.PROCESS_LOGS, (value) => Array.isArray(value) && value.length > 0);
|
|
657
|
+
const logEntries = logResult.value;
|
|
658
|
+
if (Array.isArray(logEntries)) {
|
|
659
|
+
this.seenProcessLogKeys = new Set(logEntries
|
|
660
|
+
.map((s) => (s || '').replace(/\r/g, '').trim())
|
|
661
|
+
.filter((s) => s.length > 0)
|
|
662
|
+
.map((s) => s.slice(0, 200)));
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
catch {
|
|
666
|
+
// baseline capture only
|
|
667
|
+
}
|
|
533
668
|
}
|
|
534
669
|
// In structured mode, also capture activity lines from the structured
|
|
535
670
|
// extraction to align the baseline with polling logic. The PROCESS_LOGS
|
|
@@ -538,8 +673,8 @@ class ResponseMonitor {
|
|
|
538
673
|
// entries from previous turns leak into the process log as "new" entries.
|
|
539
674
|
if (this.extractionMode === 'structured') {
|
|
540
675
|
try {
|
|
541
|
-
const structuredBaseline = await this.
|
|
542
|
-
const baselineClassified = (0, assistantDomExtractor_1.classifyAssistantSegments)(structuredBaseline
|
|
676
|
+
const structuredBaseline = await this.evaluateAcrossContexts(exports.RESPONSE_SELECTORS.RESPONSE_STRUCTURED, (value) => (0, assistantDomExtractor_1.classifyAssistantSegments)(value).diagnostics.source === 'dom-structured');
|
|
677
|
+
const baselineClassified = (0, assistantDomExtractor_1.classifyAssistantSegments)(structuredBaseline.value);
|
|
543
678
|
if (baselineClassified.diagnostics.source === 'dom-structured') {
|
|
544
679
|
for (const line of baselineClassified.activityLines) {
|
|
545
680
|
const key = (line || '').replace(/\r/g, '').trim().slice(0, 200);
|
|
@@ -589,12 +724,15 @@ class ResponseMonitor {
|
|
|
589
724
|
/** Click the stop button to interrupt LLM generation */
|
|
590
725
|
async clickStopButton() {
|
|
591
726
|
try {
|
|
592
|
-
const result = await this.
|
|
593
|
-
const value = result
|
|
727
|
+
const result = await this.evaluateAcrossContexts(exports.RESPONSE_SELECTORS.CLICK_STOP_BUTTON, (value) => !!(value && typeof value === 'object' && value.ok));
|
|
728
|
+
const value = result.value;
|
|
594
729
|
if (this.isRunning) {
|
|
595
730
|
await this.stop();
|
|
596
731
|
}
|
|
597
|
-
|
|
732
|
+
if (value && typeof value.ok === 'boolean') {
|
|
733
|
+
return value;
|
|
734
|
+
}
|
|
735
|
+
return { ok: false, error: 'CDP evaluation returned empty' };
|
|
598
736
|
}
|
|
599
737
|
catch (error) {
|
|
600
738
|
return { ok: false, error: error.message || 'Failed to click stop button' };
|
|
@@ -693,17 +831,70 @@ class ResponseMonitor {
|
|
|
693
831
|
}
|
|
694
832
|
}, this.pollIntervalMs);
|
|
695
833
|
}
|
|
696
|
-
|
|
697
|
-
const
|
|
698
|
-
expression,
|
|
699
|
-
returnByValue: true,
|
|
834
|
+
async evaluateAcrossContexts(expression, accept) {
|
|
835
|
+
const result = await evaluateAcrossContexts(this.cdpService, expression, accept, {
|
|
700
836
|
awaitPromise: true,
|
|
701
|
-
};
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
837
|
+
});
|
|
838
|
+
if (result.contextId !== null
|
|
839
|
+
&& accept(result.value)
|
|
840
|
+
&& this.lastContentContextId !== result.contextId) {
|
|
841
|
+
this.lastContentContextId = result.contextId;
|
|
842
|
+
logger_1.logger.debug(`[ResponseMonitor] Using context ${result.contextId} (${result.contextName ?? 'unknown'} | ${result.contextUrl ?? 'no-url'})`);
|
|
843
|
+
}
|
|
844
|
+
return result;
|
|
845
|
+
}
|
|
846
|
+
async logStructuredExtractionDiagnostics(payload) {
|
|
847
|
+
try {
|
|
848
|
+
const dumpResult = await this.evaluateAcrossContexts(exports.RESPONSE_SELECTORS.DUMP_ALL_TEXTS, (value) => Array.isArray(value) && value.length > 0);
|
|
849
|
+
const dumpValue = Array.isArray(dumpResult.value) ? dumpResult.value : [];
|
|
850
|
+
const accepted = dumpValue.filter((entry) => !entry?.skip).slice(0, 5);
|
|
851
|
+
const skipped = dumpValue.filter((entry) => entry?.skip).slice(0, 5);
|
|
852
|
+
logger_1.logger.warn(`[ResponseMonitor:diag] Structured payload invalid — ${dumpValue.length} candidate(s), ` +
|
|
853
|
+
`${accepted.length} accepted, ${skipped.length} skipped ` +
|
|
854
|
+
`(context=${dumpResult.contextId ?? 'none'})`);
|
|
855
|
+
logger_1.logger.debug('[ResponseMonitor:diag] Candidate details:', JSON.stringify({
|
|
856
|
+
payloadType: payload === null ? 'null' : typeof payload,
|
|
857
|
+
contextId: dumpResult.contextId,
|
|
858
|
+
contextUrl: dumpResult.contextUrl,
|
|
859
|
+
totalCandidates: dumpValue.length,
|
|
860
|
+
accepted: accepted.map((entry) => ({
|
|
861
|
+
sel: entry.sel,
|
|
862
|
+
len: entry.len,
|
|
863
|
+
preview: entry.preview,
|
|
864
|
+
})),
|
|
865
|
+
skipped: skipped.map((entry) => ({
|
|
866
|
+
sel: entry.sel,
|
|
867
|
+
skip: entry.skip,
|
|
868
|
+
len: entry.len,
|
|
869
|
+
preview: entry.preview,
|
|
870
|
+
})),
|
|
871
|
+
}));
|
|
872
|
+
}
|
|
873
|
+
catch (error) {
|
|
874
|
+
logger_1.logger.warn('[ResponseMonitor:diag] DUMP_ALL_TEXTS failed:', error);
|
|
875
|
+
}
|
|
876
|
+
try {
|
|
877
|
+
const domResult = await this.evaluateAcrossContexts(exports.RESPONSE_SELECTORS.DOM_DIAGNOSTIC, (value) => !!value && typeof value === 'object' && (Array.isArray(value.allTextNodes)
|
|
878
|
+
|| Array.isArray(value.activityNodes)
|
|
879
|
+
|| Array.isArray(value.detailsDump)));
|
|
880
|
+
const domValue = domResult.value;
|
|
881
|
+
logger_1.logger.warn(`[ResponseMonitor:diag] DOM_DIAGNOSTIC — ` +
|
|
882
|
+
`details=${domValue?.detailsCount ?? 0}, ` +
|
|
883
|
+
`activity=${Array.isArray(domValue?.activityNodes) ? domValue.activityNodes.length : 0}, ` +
|
|
884
|
+
`textNodes=${Array.isArray(domValue?.allTextNodes) ? domValue.allTextNodes.length : 0} ` +
|
|
885
|
+
`(context=${domResult.contextId ?? 'none'})`);
|
|
886
|
+
logger_1.logger.debug('[ResponseMonitor:diag] DOM_DIAGNOSTIC details:', JSON.stringify({
|
|
887
|
+
contextId: domResult.contextId,
|
|
888
|
+
contextUrl: domResult.contextUrl,
|
|
889
|
+
detailsCount: domValue?.detailsCount ?? null,
|
|
890
|
+
detailsDump: Array.isArray(domValue?.detailsDump) ? domValue.detailsDump.slice(0, 3) : [],
|
|
891
|
+
activityNodes: Array.isArray(domValue?.activityNodes) ? domValue.activityNodes.slice(0, 5) : [],
|
|
892
|
+
allTextNodes: Array.isArray(domValue?.allTextNodes) ? domValue.allTextNodes.slice(0, 5) : [],
|
|
893
|
+
}));
|
|
894
|
+
}
|
|
895
|
+
catch (error) {
|
|
896
|
+
logger_1.logger.warn('[ResponseMonitor:diag] DOM_DIAGNOSTIC failed:', error);
|
|
705
897
|
}
|
|
706
|
-
return params;
|
|
707
898
|
}
|
|
708
899
|
/**
|
|
709
900
|
* Emit new process log entries, deduplicating against previously seen keys.
|
|
@@ -738,20 +929,20 @@ class ResponseMonitor {
|
|
|
738
929
|
async poll() {
|
|
739
930
|
try {
|
|
740
931
|
// 1. Stop button check
|
|
741
|
-
const stopResult = await this.
|
|
742
|
-
const stopValue = stopResult
|
|
932
|
+
const stopResult = await this.evaluateAcrossContexts(exports.RESPONSE_SELECTORS.STOP_BUTTON, (value) => !!(value && typeof value === 'object' && value.isGenerating));
|
|
933
|
+
const stopValue = stopResult.value;
|
|
743
934
|
const isGenerating = !!(stopValue && typeof stopValue === 'object' && stopValue.isGenerating);
|
|
744
935
|
// 2. Quota error check
|
|
745
|
-
const quotaResult = await this.
|
|
746
|
-
const quotaDetected = quotaResult
|
|
936
|
+
const quotaResult = await this.evaluateAcrossContexts(exports.RESPONSE_SELECTORS.QUOTA_ERROR, (value) => value === true);
|
|
937
|
+
const quotaDetected = quotaResult.value === true;
|
|
747
938
|
// 3. Text extraction (structured or legacy)
|
|
748
939
|
let currentText = null;
|
|
749
940
|
let structuredHandledLogs = false;
|
|
750
941
|
if (this.extractionMode === 'structured') {
|
|
751
942
|
// Structured: use DOM segment extraction with HTML-to-Markdown
|
|
752
943
|
try {
|
|
753
|
-
const structuredResult = await this.
|
|
754
|
-
const payload = structuredResult
|
|
944
|
+
const structuredResult = await this.evaluateAcrossContexts(exports.RESPONSE_SELECTORS.RESPONSE_STRUCTURED, (value) => (0, assistantDomExtractor_1.classifyAssistantSegments)(value).diagnostics.source === 'dom-structured');
|
|
945
|
+
const payload = structuredResult.value;
|
|
755
946
|
const classified = (0, assistantDomExtractor_1.classifyAssistantSegments)(payload);
|
|
756
947
|
if (classified.diagnostics.source === 'dom-structured') {
|
|
757
948
|
currentText = classified.finalOutputText.trim() || null;
|
|
@@ -768,6 +959,7 @@ class ResponseMonitor {
|
|
|
768
959
|
else if (!this.structuredDiagLogged) {
|
|
769
960
|
this.structuredDiagLogged = true;
|
|
770
961
|
logger_1.logger.warn('[ResponseMonitor:poll] Structured extraction failed — reason:', classified.diagnostics.fallbackReason ?? 'unknown', '| payload type:', typeof payload, '| payload:', payload === null ? 'null' : payload === undefined ? 'undefined' : 'object');
|
|
962
|
+
await this.logStructuredExtractionDiagnostics(payload);
|
|
771
963
|
}
|
|
772
964
|
}
|
|
773
965
|
catch (error) {
|
|
@@ -776,19 +968,14 @@ class ResponseMonitor {
|
|
|
776
968
|
}
|
|
777
969
|
// Legacy path (or fallback from structured)
|
|
778
970
|
if (currentText === null) {
|
|
779
|
-
const textResult = await this.
|
|
780
|
-
|
|
781
|
-
const exceptionDetail = textResult?.result?.exceptionDetails ?? textResult?.exceptionDetails;
|
|
782
|
-
if (exceptionDetail) {
|
|
783
|
-
logger_1.logger.warn('[ResponseMonitor:poll] RESPONSE_TEXT threw:', exceptionDetail.text ?? JSON.stringify(exceptionDetail).slice(0, 200));
|
|
784
|
-
}
|
|
785
|
-
currentText = typeof rawText === 'string' ? rawText.trim() || null : null;
|
|
971
|
+
const textResult = await this.evaluateAcrossContexts(exports.RESPONSE_SELECTORS.RESPONSE_TEXT, (value) => typeof value === 'string' && value.trim().length > 0);
|
|
972
|
+
currentText = typeof textResult.value === 'string' ? textResult.value.trim() || null : null;
|
|
786
973
|
}
|
|
787
974
|
// 4. Process log extraction — always when structured didn't handle it
|
|
788
975
|
if (!structuredHandledLogs) {
|
|
789
976
|
try {
|
|
790
|
-
const logResult = await this.
|
|
791
|
-
const logEntries = logResult
|
|
977
|
+
const logResult = await this.evaluateAcrossContexts(exports.RESPONSE_SELECTORS.PROCESS_LOGS, (value) => Array.isArray(value) && value.length > 0);
|
|
978
|
+
const logEntries = logResult.value;
|
|
792
979
|
if (Array.isArray(logEntries)) {
|
|
793
980
|
this.emitNewProcessLogs(logEntries);
|
|
794
981
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lazy-gravity",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "Control Antigravity from anywhere — a local, secure bot (Discord + Telegram) that lets you remotely operate Antigravity on your home PC from your smartphone.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|