runsignal 0.1.6 → 0.1.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/runsignal.js +130 -65
- package/package.json +1 -1
package/bin/runsignal.js
CHANGED
|
@@ -18,6 +18,20 @@ const DEFAULT_APP_URL = process.env.RUNSIGNAL_APP_URL || 'https://runsignal.dev'
|
|
|
18
18
|
const CONFIG_DIR = path.join(os.homedir(), '.runsignal');
|
|
19
19
|
const CONFIG_PATH = path.join(CONFIG_DIR, 'config.json');
|
|
20
20
|
|
|
21
|
+
function extractErrorDetail(code, marker) {
|
|
22
|
+
const index = code.indexOf(marker);
|
|
23
|
+
if (index === -1) {
|
|
24
|
+
return '';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const detail = code.slice(index + marker.length).trim();
|
|
28
|
+
if (!detail) {
|
|
29
|
+
return '';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return detail.slice(0, 180);
|
|
33
|
+
}
|
|
34
|
+
|
|
21
35
|
function toUserMessage(errorCode) {
|
|
22
36
|
const code = String(errorCode || '');
|
|
23
37
|
if (code.includes('not_logged_in')) return 'You are not logged in. Run: runsignal login';
|
|
@@ -32,8 +46,13 @@ function toUserMessage(errorCode) {
|
|
|
32
46
|
if (code.includes('login_timeout')) return 'Login timed out. Retry runsignal login and validate quickly.';
|
|
33
47
|
if (code.includes('login_poll_failed'))
|
|
34
48
|
return 'Login polling failed. Check APP URL and server availability.';
|
|
35
|
-
if (code.includes('send_event_failed'))
|
|
49
|
+
if (code.includes('send_event_failed')) {
|
|
50
|
+
const detail = extractErrorDetail(code, 'send_event_failed:');
|
|
51
|
+
if (detail) {
|
|
52
|
+
return `Event send failed (${detail}). Check machine key, network and server logs.`;
|
|
53
|
+
}
|
|
36
54
|
return 'Event send failed. Check machine key, network and server logs.';
|
|
55
|
+
}
|
|
37
56
|
if (code.includes('status_failed')) return 'Status check failed. Verify event id and machine ownership.';
|
|
38
57
|
if (code.includes('event_type_invalid'))
|
|
39
58
|
return 'Invalid event type. Use approval_required, task_completed, error, quota_warning, question.';
|
|
@@ -101,6 +120,39 @@ function sleep(ms) {
|
|
|
101
120
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
102
121
|
}
|
|
103
122
|
|
|
123
|
+
function tryOpenBrowser(url) {
|
|
124
|
+
if (!url) return false;
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
if (process.platform === 'win32') {
|
|
128
|
+
const child = spawn('cmd', ['/c', 'start', '', url], {
|
|
129
|
+
detached: true,
|
|
130
|
+
stdio: 'ignore'
|
|
131
|
+
});
|
|
132
|
+
child.unref();
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (process.platform === 'darwin') {
|
|
137
|
+
const child = spawn('open', [url], {
|
|
138
|
+
detached: true,
|
|
139
|
+
stdio: 'ignore'
|
|
140
|
+
});
|
|
141
|
+
child.unref();
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const child = spawn('xdg-open', [url], {
|
|
146
|
+
detached: true,
|
|
147
|
+
stdio: 'ignore'
|
|
148
|
+
});
|
|
149
|
+
child.unref();
|
|
150
|
+
return true;
|
|
151
|
+
} catch {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
104
156
|
const HOOK_START = '# >>> RunSignal hooks start >>>';
|
|
105
157
|
const HOOK_END = '# <<< RunSignal hooks end <<<';
|
|
106
158
|
|
|
@@ -479,7 +531,13 @@ async function runLogin(options) {
|
|
|
479
531
|
throw new Error('login_payload_invalid');
|
|
480
532
|
}
|
|
481
533
|
|
|
482
|
-
|
|
534
|
+
const verificationUrlWithCode = `${verificationUrl}?code=${encodeURIComponent(String(userCode))}`;
|
|
535
|
+
const opened = tryOpenBrowser(verificationUrlWithCode);
|
|
536
|
+
if (opened) {
|
|
537
|
+
console.log('Browser opened.');
|
|
538
|
+
} else {
|
|
539
|
+
console.log(`Open ${verificationUrlWithCode} and enter code: ${userCode}`);
|
|
540
|
+
}
|
|
483
541
|
|
|
484
542
|
let accessToken = null;
|
|
485
543
|
const deadline = Date.now() + 10 * 60 * 1000;
|
|
@@ -604,7 +662,7 @@ async function runWrap(commandParts) {
|
|
|
604
662
|
}
|
|
605
663
|
|
|
606
664
|
const repo = getRepoFromGit();
|
|
607
|
-
const branch = getBranchFromGit();
|
|
665
|
+
const branch = getBranchFromGit() || undefined;
|
|
608
666
|
const machineName = config.machine_name || getMachineName();
|
|
609
667
|
const agentLabel = inferAgentLabel(commandParts);
|
|
610
668
|
|
|
@@ -641,48 +699,52 @@ async function runWrap(commandParts) {
|
|
|
641
699
|
env: process.env
|
|
642
700
|
});
|
|
643
701
|
|
|
702
|
+
const startedAt = Date.now();
|
|
703
|
+
const sessionTarget = interactivePassthrough ? singleExecutable : commandString;
|
|
704
|
+
try {
|
|
705
|
+
await sendEvent({
|
|
706
|
+
apiFetch,
|
|
707
|
+
baseUrl,
|
|
708
|
+
machineApiKey,
|
|
709
|
+
event: {
|
|
710
|
+
message: `Session started: ${sessionTarget}`,
|
|
711
|
+
event_type: 'session_started',
|
|
712
|
+
repo,
|
|
713
|
+
branch,
|
|
714
|
+
severity: 'info',
|
|
715
|
+
context,
|
|
716
|
+
agent: agentLabel
|
|
717
|
+
}
|
|
718
|
+
});
|
|
719
|
+
} catch {
|
|
720
|
+
// non-blocking
|
|
721
|
+
}
|
|
722
|
+
|
|
644
723
|
let heartbeatInterval = null;
|
|
645
724
|
if (interactivePassthrough) {
|
|
646
|
-
const
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
725
|
+
const heartbeatMin = Number.parseInt(String(process.env.RUNSIGNAL_HEARTBEAT_MIN ?? '0'), 10);
|
|
726
|
+
if (Number.isFinite(heartbeatMin) && heartbeatMin > 0) {
|
|
727
|
+
heartbeatInterval = setInterval(() => {
|
|
728
|
+
const elapsedMin = Math.max(1, Math.floor((Date.now() - startedAt) / 60_000));
|
|
729
|
+
sendEvent({
|
|
730
|
+
apiFetch,
|
|
731
|
+
baseUrl,
|
|
732
|
+
machineApiKey,
|
|
733
|
+
event: {
|
|
734
|
+
message: `Session running (${elapsedMin} min): ${singleExecutable}`,
|
|
735
|
+
event_type: 'session_running',
|
|
736
|
+
repo,
|
|
737
|
+
branch,
|
|
738
|
+
severity: 'info',
|
|
739
|
+
context,
|
|
740
|
+
agent: agentLabel
|
|
741
|
+
}
|
|
742
|
+
}).catch(() => {
|
|
743
|
+
// non-blocking
|
|
744
|
+
});
|
|
745
|
+
}, heartbeatMin * 60_000);
|
|
746
|
+
heartbeatInterval.unref?.();
|
|
664
747
|
}
|
|
665
|
-
|
|
666
|
-
heartbeatInterval = setInterval(() => {
|
|
667
|
-
const elapsedMin = Math.max(1, Math.floor((Date.now() - startedAt) / 60_000));
|
|
668
|
-
sendEvent({
|
|
669
|
-
apiFetch,
|
|
670
|
-
baseUrl,
|
|
671
|
-
machineApiKey,
|
|
672
|
-
event: {
|
|
673
|
-
message: `Session running (${elapsedMin} min): ${singleExecutable}`,
|
|
674
|
-
event_type: 'session_running',
|
|
675
|
-
repo,
|
|
676
|
-
branch,
|
|
677
|
-
severity: 'info',
|
|
678
|
-
context,
|
|
679
|
-
agent: agentLabel
|
|
680
|
-
}
|
|
681
|
-
}).catch(() => {
|
|
682
|
-
// non-blocking
|
|
683
|
-
});
|
|
684
|
-
}, 120_000);
|
|
685
|
-
heartbeatInterval.unref?.();
|
|
686
748
|
}
|
|
687
749
|
|
|
688
750
|
let sawApproval = false;
|
|
@@ -791,28 +853,30 @@ async function runWrap(commandParts) {
|
|
|
791
853
|
}
|
|
792
854
|
await Promise.allSettled(Array.from(pendingTasks));
|
|
793
855
|
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
856
|
+
if (!interactivePassthrough || exitCode !== 0) {
|
|
857
|
+
const completionMessage =
|
|
858
|
+
exitCode === 0
|
|
859
|
+
? `Wrapped command completed: ${commandParts.join(' ')}`
|
|
860
|
+
: `Wrapped command failed (code ${exitCode}): ${lastInterestingLine || commandParts.join(' ')}`;
|
|
798
861
|
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
862
|
+
try {
|
|
863
|
+
await sendEvent({
|
|
864
|
+
apiFetch,
|
|
865
|
+
baseUrl,
|
|
866
|
+
machineApiKey,
|
|
867
|
+
event: {
|
|
868
|
+
message: completionMessage.slice(0, 1000),
|
|
869
|
+
event_type: exitCode === 0 ? 'task_completed' : 'error',
|
|
870
|
+
repo,
|
|
871
|
+
branch,
|
|
872
|
+
severity: exitCode === 0 ? 'info' : 'critical',
|
|
873
|
+
context,
|
|
874
|
+
agent: agentLabel
|
|
875
|
+
}
|
|
876
|
+
});
|
|
877
|
+
} catch {
|
|
878
|
+
// no-op
|
|
879
|
+
}
|
|
816
880
|
}
|
|
817
881
|
|
|
818
882
|
// Avoid hard process exit on Windows: let Node close handles cleanly.
|
|
@@ -830,7 +894,7 @@ async function runTest() {
|
|
|
830
894
|
}
|
|
831
895
|
|
|
832
896
|
const repo = getRepoFromGit();
|
|
833
|
-
const branch = getBranchFromGit();
|
|
897
|
+
const branch = getBranchFromGit() || undefined;
|
|
834
898
|
|
|
835
899
|
const result = await sendEvent({
|
|
836
900
|
apiFetch,
|
|
@@ -901,7 +965,8 @@ async function runSend(options) {
|
|
|
901
965
|
}
|
|
902
966
|
|
|
903
967
|
const repo = options.repo ? String(options.repo).trim() : getRepoFromGit();
|
|
904
|
-
const
|
|
968
|
+
const branchRaw = options.branch ? String(options.branch).trim() : getBranchFromGit();
|
|
969
|
+
const branch = branchRaw || undefined;
|
|
905
970
|
const agent = options.agent ? String(options.agent).trim() : 'manual-cli';
|
|
906
971
|
|
|
907
972
|
const result = await sendEvent({
|