runsignal 0.1.5 → 0.1.6
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 +113 -23
- package/package.json +1 -1
package/bin/runsignal.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import {Command} from 'commander';
|
|
4
4
|
import {spawn, execSync} from 'node:child_process';
|
|
5
5
|
import {promises as fs} from 'node:fs';
|
|
6
|
+
import {existsSync} from 'node:fs';
|
|
6
7
|
import os from 'node:os';
|
|
7
8
|
import path from 'node:path';
|
|
8
9
|
import {
|
|
@@ -122,43 +123,44 @@ function buildHookBlock(shellKind) {
|
|
|
122
123
|
return `${HOOK_START}
|
|
123
124
|
function RunSignal-Resolve-NativeCommand {
|
|
124
125
|
param([string]$Name)
|
|
125
|
-
$
|
|
126
|
-
if (-not $
|
|
127
|
-
|
|
126
|
+
$candidates = @(Get-Command $Name -CommandType Application -ErrorAction SilentlyContinue)
|
|
127
|
+
if (-not $candidates -or $candidates.Count -eq 0) { return $null }
|
|
128
|
+
$preferred = $candidates | Where-Object { $_.Source -match '\\.exe$' } | Select-Object -First 1
|
|
129
|
+
if ($preferred) { return $preferred.Source }
|
|
130
|
+
$launcher = $candidates | Where-Object { $_.Source -match '\\.(cmd|bat)$' } | Select-Object -First 1
|
|
131
|
+
if ($launcher) { return $launcher.Source }
|
|
132
|
+
$secondary = $candidates | Where-Object { $_.Source -match '\\.[A-Za-z0-9]+$' } | Select-Object -First 1
|
|
133
|
+
if ($secondary) { return $secondary.Source }
|
|
134
|
+
return $candidates[0].Source
|
|
128
135
|
}
|
|
129
136
|
function claude {
|
|
130
137
|
$native = RunSignal-Resolve-NativeCommand "claude"
|
|
131
138
|
if (-not $native) {
|
|
132
|
-
$
|
|
133
|
-
$fallback = Join-Path $
|
|
139
|
+
$userHome = [Environment]::GetFolderPath("UserProfile")
|
|
140
|
+
$fallback = Join-Path $userHome ".local\\bin\\claude.exe"
|
|
134
141
|
if (Test-Path $fallback) { $native = $fallback }
|
|
135
142
|
}
|
|
136
143
|
if (-not $native) { Write-Error "RunSignal hook: native claude executable not found. Add your native installer path to PATH (example: %USERPROFILE%\\.local\\bin)."; return }
|
|
137
|
-
if ($args.Count -eq 0) { & $native; return }
|
|
138
144
|
runsignal wrap -- $native @args
|
|
139
145
|
}
|
|
140
146
|
function codex {
|
|
141
147
|
$native = RunSignal-Resolve-NativeCommand "codex"
|
|
142
148
|
if (-not $native) { Write-Error "RunSignal hook: native codex executable not found."; return }
|
|
143
|
-
if ($args.Count -eq 0) { & $native; return }
|
|
144
149
|
runsignal wrap -- $native @args
|
|
145
150
|
}
|
|
146
151
|
function cursor {
|
|
147
152
|
$native = RunSignal-Resolve-NativeCommand "cursor"
|
|
148
153
|
if (-not $native) { Write-Error "RunSignal hook: native cursor executable not found."; return }
|
|
149
|
-
if ($args.Count -eq 0) { & $native; return }
|
|
150
154
|
runsignal wrap -- $native @args
|
|
151
155
|
}
|
|
152
156
|
function gemini {
|
|
153
157
|
$native = RunSignal-Resolve-NativeCommand "gemini"
|
|
154
158
|
if (-not $native) { Write-Error "RunSignal hook: native gemini executable not found."; return }
|
|
155
|
-
if ($args.Count -eq 0) { & $native; return }
|
|
156
159
|
runsignal wrap -- $native @args
|
|
157
160
|
}
|
|
158
161
|
function aider {
|
|
159
162
|
$native = RunSignal-Resolve-NativeCommand "aider"
|
|
160
163
|
if (-not $native) { Write-Error "RunSignal hook: native aider executable not found."; return }
|
|
161
|
-
if ($args.Count -eq 0) { & $native; return }
|
|
162
164
|
runsignal wrap -- $native @args
|
|
163
165
|
}
|
|
164
166
|
${HOOK_END}
|
|
@@ -183,35 +185,30 @@ claude() {
|
|
|
183
185
|
local native
|
|
184
186
|
native="$(runsignal_resolve_native claude)"
|
|
185
187
|
if [ -z "$native" ]; then echo "RunSignal hook: native claude executable not found." >&2; return 127; fi
|
|
186
|
-
if [ "$#" -eq 0 ]; then "$native"; return $?; fi
|
|
187
188
|
runsignal wrap -- "$native" "$@"
|
|
188
189
|
}
|
|
189
190
|
codex() {
|
|
190
191
|
local native
|
|
191
192
|
native="$(runsignal_resolve_native codex)"
|
|
192
193
|
if [ -z "$native" ]; then echo "RunSignal hook: native codex executable not found." >&2; return 127; fi
|
|
193
|
-
if [ "$#" -eq 0 ]; then "$native"; return $?; fi
|
|
194
194
|
runsignal wrap -- "$native" "$@"
|
|
195
195
|
}
|
|
196
196
|
cursor() {
|
|
197
197
|
local native
|
|
198
198
|
native="$(runsignal_resolve_native cursor)"
|
|
199
199
|
if [ -z "$native" ]; then echo "RunSignal hook: native cursor executable not found." >&2; return 127; fi
|
|
200
|
-
if [ "$#" -eq 0 ]; then "$native"; return $?; fi
|
|
201
200
|
runsignal wrap -- "$native" "$@"
|
|
202
201
|
}
|
|
203
202
|
gemini() {
|
|
204
203
|
local native
|
|
205
204
|
native="$(runsignal_resolve_native gemini)"
|
|
206
205
|
if [ -z "$native" ]; then echo "RunSignal hook: native gemini executable not found." >&2; return 127; fi
|
|
207
|
-
if [ "$#" -eq 0 ]; then "$native"; return $?; fi
|
|
208
206
|
runsignal wrap -- "$native" "$@"
|
|
209
207
|
}
|
|
210
208
|
aider() {
|
|
211
209
|
local native
|
|
212
210
|
native="$(runsignal_resolve_native aider)"
|
|
213
211
|
if [ -z "$native" ]; then echo "RunSignal hook: native aider executable not found." >&2; return 127; fi
|
|
214
|
-
if [ "$#" -eq 0 ]; then "$native"; return $?; fi
|
|
215
212
|
runsignal wrap -- "$native" "$@"
|
|
216
213
|
}
|
|
217
214
|
${HOOK_END}
|
|
@@ -435,6 +432,35 @@ function quoteForShell(arg) {
|
|
|
435
432
|
return `"${escaped}"`;
|
|
436
433
|
}
|
|
437
434
|
|
|
435
|
+
function stripWrappingQuotes(value) {
|
|
436
|
+
const raw = String(value || '').trim();
|
|
437
|
+
if ((raw.startsWith('"') && raw.endsWith('"')) || (raw.startsWith("'") && raw.endsWith("'"))) {
|
|
438
|
+
return raw.slice(1, -1);
|
|
439
|
+
}
|
|
440
|
+
return raw;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function isWindowsScriptLauncher(filePath) {
|
|
444
|
+
if (process.platform !== 'win32') return false;
|
|
445
|
+
const ext = path.extname(String(filePath || '')).toLowerCase();
|
|
446
|
+
return ext === '.cmd' || ext === '.bat' || ext === '.ps1';
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function normalizeWindowsExecutablePath(filePath) {
|
|
450
|
+
const raw = String(filePath || '');
|
|
451
|
+
if (process.platform !== 'win32') return raw;
|
|
452
|
+
if (!raw) return raw;
|
|
453
|
+
const ext = path.extname(raw).toLowerCase();
|
|
454
|
+
if (ext) return raw;
|
|
455
|
+
const exe = `${raw}.exe`;
|
|
456
|
+
if (existsSync(exe)) return exe;
|
|
457
|
+
const cmd = `${raw}.cmd`;
|
|
458
|
+
if (existsSync(cmd)) return cmd;
|
|
459
|
+
const bat = `${raw}.bat`;
|
|
460
|
+
if (existsSync(bat)) return bat;
|
|
461
|
+
return raw;
|
|
462
|
+
}
|
|
463
|
+
|
|
438
464
|
async function runLogin(options) {
|
|
439
465
|
const config = await readConfig();
|
|
440
466
|
const baseUrl = String(options.appUrl || config.app_url || DEFAULT_APP_URL).replace(/\/$/, '');
|
|
@@ -593,13 +619,71 @@ async function runWrap(commandParts) {
|
|
|
593
619
|
commandParts.length === 1
|
|
594
620
|
? String(commandParts[0])
|
|
595
621
|
: commandParts.map((part) => quoteForShell(part)).join(' ');
|
|
622
|
+
const singleCommand = String(commandParts[0] || '');
|
|
623
|
+
const singleExecutable = normalizeWindowsExecutablePath(stripWrappingQuotes(singleCommand));
|
|
596
624
|
const interactivePassthrough =
|
|
597
|
-
commandParts.length === 1 &&
|
|
598
|
-
const child =
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
625
|
+
commandParts.length === 1 && (existsSync(singleExecutable) || !/\s/.test(singleExecutable));
|
|
626
|
+
const child = interactivePassthrough
|
|
627
|
+
? isWindowsScriptLauncher(singleExecutable)
|
|
628
|
+
? spawn(singleExecutable, [], {
|
|
629
|
+
stdio: 'inherit',
|
|
630
|
+
shell: true,
|
|
631
|
+
env: process.env
|
|
632
|
+
})
|
|
633
|
+
: spawn(singleExecutable, [], {
|
|
634
|
+
stdio: 'inherit',
|
|
635
|
+
shell: false,
|
|
636
|
+
env: process.env
|
|
637
|
+
})
|
|
638
|
+
: spawn(commandString, {
|
|
639
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
640
|
+
shell: true,
|
|
641
|
+
env: process.env
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
let heartbeatInterval = null;
|
|
645
|
+
if (interactivePassthrough) {
|
|
646
|
+
const startedAt = Date.now();
|
|
647
|
+
try {
|
|
648
|
+
await sendEvent({
|
|
649
|
+
apiFetch,
|
|
650
|
+
baseUrl,
|
|
651
|
+
machineApiKey,
|
|
652
|
+
event: {
|
|
653
|
+
message: `Session started: ${singleExecutable}`,
|
|
654
|
+
event_type: 'session_started',
|
|
655
|
+
repo,
|
|
656
|
+
branch,
|
|
657
|
+
severity: 'info',
|
|
658
|
+
context,
|
|
659
|
+
agent: agentLabel
|
|
660
|
+
}
|
|
661
|
+
});
|
|
662
|
+
} catch {
|
|
663
|
+
// non-blocking
|
|
664
|
+
}
|
|
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
|
+
}
|
|
603
687
|
|
|
604
688
|
let sawApproval = false;
|
|
605
689
|
let sawQuestion = false;
|
|
@@ -699,8 +783,12 @@ async function runWrap(commandParts) {
|
|
|
699
783
|
}
|
|
700
784
|
|
|
701
785
|
const exitCode = await new Promise((resolve) => {
|
|
786
|
+
child.on('error', () => resolve(1));
|
|
702
787
|
child.on('close', (code) => resolve(code ?? 1));
|
|
703
788
|
});
|
|
789
|
+
if (heartbeatInterval) {
|
|
790
|
+
clearInterval(heartbeatInterval);
|
|
791
|
+
}
|
|
704
792
|
await Promise.allSettled(Array.from(pendingTasks));
|
|
705
793
|
|
|
706
794
|
const completionMessage =
|
|
@@ -774,7 +862,9 @@ function normalizeEventType(value) {
|
|
|
774
862
|
raw === 'task_completed' ||
|
|
775
863
|
raw === 'error' ||
|
|
776
864
|
raw === 'quota_warning' ||
|
|
777
|
-
raw === 'question'
|
|
865
|
+
raw === 'question' ||
|
|
866
|
+
raw === 'session_started' ||
|
|
867
|
+
raw === 'session_running'
|
|
778
868
|
) {
|
|
779
869
|
return raw;
|
|
780
870
|
}
|
|
@@ -944,7 +1034,7 @@ async function runSetup(options) {
|
|
|
944
1034
|
|
|
945
1035
|
const program = new Command();
|
|
946
1036
|
|
|
947
|
-
program.name('runsignal').description('RunSignal CLI').version('0.1.
|
|
1037
|
+
program.name('runsignal').description('RunSignal CLI').version('0.1.6');
|
|
948
1038
|
|
|
949
1039
|
program
|
|
950
1040
|
.command('setup')
|