@respan/cli 0.6.4 → 0.6.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/dist/hooks/claude-code.cjs +45 -25
- package/dist/hooks/claude-code.js +49 -31
- package/dist/hooks/gemini-cli.cjs +2 -9
- package/dist/hooks/gemini-cli.js +3 -8
- package/oclif.manifest.json +341 -341
- package/package.json +1 -1
|
@@ -26,6 +26,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
26
26
|
var fs2 = __toESM(require("node:fs"), 1);
|
|
27
27
|
var os2 = __toESM(require("node:os"), 1);
|
|
28
28
|
var path2 = __toESM(require("node:path"), 1);
|
|
29
|
+
var import_node_child_process = require("node:child_process");
|
|
29
30
|
|
|
30
31
|
// src/hooks/shared.ts
|
|
31
32
|
var fs = __toESM(require("node:fs"), 1);
|
|
@@ -870,24 +871,16 @@ function findLatestTranscript() {
|
|
|
870
871
|
return null;
|
|
871
872
|
}
|
|
872
873
|
}
|
|
873
|
-
async function
|
|
874
|
+
async function mainWorker() {
|
|
874
875
|
const scriptStart = Date.now();
|
|
875
|
-
debug("
|
|
876
|
-
if ((process.env.TRACE_TO_RESPAN ?? "").toLowerCase() !== "true") {
|
|
877
|
-
debug("Tracing disabled (TRACE_TO_RESPAN != true)");
|
|
878
|
-
process.exit(0);
|
|
879
|
-
}
|
|
876
|
+
debug("Worker started");
|
|
880
877
|
const creds = resolveCredentials();
|
|
881
878
|
if (!creds) {
|
|
882
879
|
log("ERROR", "No API key found. Run: respan auth login");
|
|
883
|
-
|
|
884
|
-
}
|
|
885
|
-
const payload = readStdinPayload() ?? findLatestTranscript();
|
|
886
|
-
if (!payload) {
|
|
887
|
-
debug("No transcript file found");
|
|
888
|
-
process.exit(0);
|
|
880
|
+
return;
|
|
889
881
|
}
|
|
890
|
-
const
|
|
882
|
+
const sessionId = process.env._RESPAN_SESSION_ID;
|
|
883
|
+
const transcriptPath = process.env._RESPAN_TRANSCRIPT_PATH;
|
|
891
884
|
debug(`Processing session: ${sessionId}`);
|
|
892
885
|
let config = null;
|
|
893
886
|
try {
|
|
@@ -907,10 +900,8 @@ async function main() {
|
|
|
907
900
|
}
|
|
908
901
|
if (cwd) {
|
|
909
902
|
config = loadRespanConfig(path2.join(cwd, ".claude", "respan.json"));
|
|
910
|
-
debug(`Loaded respan.json config from ${cwd}`);
|
|
911
903
|
}
|
|
912
|
-
} catch
|
|
913
|
-
debug(`Failed to load config: ${e}`);
|
|
904
|
+
} catch {
|
|
914
905
|
}
|
|
915
906
|
const maxAttempts = 3;
|
|
916
907
|
let turns = 0;
|
|
@@ -932,20 +923,49 @@ async function main() {
|
|
|
932
923
|
}
|
|
933
924
|
if (turns > 0) break;
|
|
934
925
|
if (attempt < maxAttempts - 1) {
|
|
935
|
-
|
|
936
|
-
debug(`No turns processed (attempt ${attempt + 1}/${maxAttempts}), retrying in ${delay}ms...`);
|
|
937
|
-
await new Promise((r) => setTimeout(r, delay));
|
|
926
|
+
await new Promise((r) => setTimeout(r, 500 * (attempt + 1)));
|
|
938
927
|
}
|
|
939
928
|
}
|
|
940
929
|
const duration = (Date.now() - scriptStart) / 1e3;
|
|
941
930
|
log("INFO", `Processed ${turns} turns in ${duration.toFixed(1)}s`);
|
|
942
|
-
if (duration > 180) log("WARN", `Hook took ${duration.toFixed(1)}s (>3min)`);
|
|
943
931
|
} catch (e) {
|
|
944
932
|
log("ERROR", `Failed to process transcript: ${e}`);
|
|
945
|
-
if (DEBUG_MODE) debug(String(e.stack ?? e));
|
|
946
933
|
}
|
|
947
934
|
}
|
|
948
|
-
main()
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
935
|
+
function main() {
|
|
936
|
+
if (process.env._RESPAN_WORKER === "1") {
|
|
937
|
+
mainWorker().catch((e) => log("ERROR", `Worker crashed: ${e}`));
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
debug("Hook started");
|
|
941
|
+
if ((process.env.TRACE_TO_RESPAN ?? "").toLowerCase() !== "true") {
|
|
942
|
+
debug("Tracing disabled (TRACE_TO_RESPAN != true)");
|
|
943
|
+
process.exit(0);
|
|
944
|
+
}
|
|
945
|
+
const payload = readStdinPayload() ?? findLatestTranscript();
|
|
946
|
+
if (!payload) {
|
|
947
|
+
debug("No transcript file found");
|
|
948
|
+
process.exit(0);
|
|
949
|
+
}
|
|
950
|
+
const { sessionId, transcriptPath } = payload;
|
|
951
|
+
debug(`Forking worker for session: ${sessionId}`);
|
|
952
|
+
try {
|
|
953
|
+
const scriptPath = __filename || process.argv[1];
|
|
954
|
+
const child = (0, import_node_child_process.execFile)("node", [scriptPath], {
|
|
955
|
+
env: {
|
|
956
|
+
...process.env,
|
|
957
|
+
_RESPAN_WORKER: "1",
|
|
958
|
+
_RESPAN_SESSION_ID: sessionId,
|
|
959
|
+
_RESPAN_TRANSCRIPT_PATH: transcriptPath
|
|
960
|
+
},
|
|
961
|
+
stdio: "ignore",
|
|
962
|
+
detached: true
|
|
963
|
+
});
|
|
964
|
+
child.unref();
|
|
965
|
+
debug("Worker launched");
|
|
966
|
+
} catch (e) {
|
|
967
|
+
log("ERROR", `Failed to fork worker: ${e}`);
|
|
968
|
+
}
|
|
969
|
+
process.exit(0);
|
|
970
|
+
}
|
|
971
|
+
main();
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import * as fs from 'node:fs';
|
|
15
15
|
import * as os from 'node:os';
|
|
16
16
|
import * as path from 'node:path';
|
|
17
|
+
import { execFile } from 'node:child_process';
|
|
17
18
|
import { initLogging, log, debug, resolveCredentials, loadRespanConfig, loadState, saveState, acquireLock, sendSpans, addDefaultsToAll, resolveSpanFields, buildMetadata, nowISO, latencySeconds, truncate, } from './shared.js';
|
|
18
19
|
// ── Config ────────────────────────────────────────────────────────
|
|
19
20
|
const STATE_DIR = path.join(os.homedir(), '.claude', 'state');
|
|
@@ -550,27 +551,17 @@ function findLatestTranscript() {
|
|
|
550
551
|
}
|
|
551
552
|
}
|
|
552
553
|
// ── Main ──────────────────────────────────────────────────────────
|
|
553
|
-
async function
|
|
554
|
+
async function mainWorker() {
|
|
554
555
|
const scriptStart = Date.now();
|
|
555
|
-
debug('
|
|
556
|
-
if ((process.env.TRACE_TO_RESPAN ?? '').toLowerCase() !== 'true') {
|
|
557
|
-
debug('Tracing disabled (TRACE_TO_RESPAN != true)');
|
|
558
|
-
process.exit(0);
|
|
559
|
-
}
|
|
556
|
+
debug('Worker started');
|
|
560
557
|
const creds = resolveCredentials();
|
|
561
558
|
if (!creds) {
|
|
562
559
|
log('ERROR', 'No API key found. Run: respan auth login');
|
|
563
|
-
|
|
564
|
-
}
|
|
565
|
-
// Find transcript
|
|
566
|
-
const payload = readStdinPayload() ?? findLatestTranscript();
|
|
567
|
-
if (!payload) {
|
|
568
|
-
debug('No transcript file found');
|
|
569
|
-
process.exit(0);
|
|
560
|
+
return;
|
|
570
561
|
}
|
|
571
|
-
const
|
|
562
|
+
const sessionId = process.env._RESPAN_SESSION_ID;
|
|
563
|
+
const transcriptPath = process.env._RESPAN_TRANSCRIPT_PATH;
|
|
572
564
|
debug(`Processing session: ${sessionId}`);
|
|
573
|
-
// Load respan.json config from project directory
|
|
574
565
|
let config = null;
|
|
575
566
|
try {
|
|
576
567
|
const content = fs.readFileSync(transcriptPath, 'utf-8');
|
|
@@ -590,13 +581,9 @@ async function main() {
|
|
|
590
581
|
}
|
|
591
582
|
if (cwd) {
|
|
592
583
|
config = loadRespanConfig(path.join(cwd, '.claude', 'respan.json'));
|
|
593
|
-
debug(`Loaded respan.json config from ${cwd}`);
|
|
594
584
|
}
|
|
595
585
|
}
|
|
596
|
-
catch
|
|
597
|
-
debug(`Failed to load config: ${e}`);
|
|
598
|
-
}
|
|
599
|
-
// Process with retry
|
|
586
|
+
catch { }
|
|
600
587
|
const maxAttempts = 3;
|
|
601
588
|
let turns = 0;
|
|
602
589
|
try {
|
|
@@ -619,23 +606,54 @@ async function main() {
|
|
|
619
606
|
if (turns > 0)
|
|
620
607
|
break;
|
|
621
608
|
if (attempt < maxAttempts - 1) {
|
|
622
|
-
|
|
623
|
-
debug(`No turns processed (attempt ${attempt + 1}/${maxAttempts}), retrying in ${delay}ms...`);
|
|
624
|
-
await new Promise((r) => setTimeout(r, delay));
|
|
609
|
+
await new Promise((r) => setTimeout(r, 500 * (attempt + 1)));
|
|
625
610
|
}
|
|
626
611
|
}
|
|
627
612
|
const duration = (Date.now() - scriptStart) / 1000;
|
|
628
613
|
log('INFO', `Processed ${turns} turns in ${duration.toFixed(1)}s`);
|
|
629
|
-
if (duration > 180)
|
|
630
|
-
log('WARN', `Hook took ${duration.toFixed(1)}s (>3min)`);
|
|
631
614
|
}
|
|
632
615
|
catch (e) {
|
|
633
616
|
log('ERROR', `Failed to process transcript: ${e}`);
|
|
634
|
-
if (DEBUG_MODE)
|
|
635
|
-
debug(String(e.stack ?? e));
|
|
636
617
|
}
|
|
637
618
|
}
|
|
638
|
-
main()
|
|
639
|
-
|
|
640
|
-
process.
|
|
641
|
-
});
|
|
619
|
+
function main() {
|
|
620
|
+
// Worker mode: re-invoked as detached subprocess
|
|
621
|
+
if (process.env._RESPAN_WORKER === '1') {
|
|
622
|
+
mainWorker().catch((e) => log('ERROR', `Worker crashed: ${e}`));
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
debug('Hook started');
|
|
626
|
+
if ((process.env.TRACE_TO_RESPAN ?? '').toLowerCase() !== 'true') {
|
|
627
|
+
debug('Tracing disabled (TRACE_TO_RESPAN != true)');
|
|
628
|
+
process.exit(0);
|
|
629
|
+
}
|
|
630
|
+
const payload = readStdinPayload() ?? findLatestTranscript();
|
|
631
|
+
if (!payload) {
|
|
632
|
+
debug('No transcript file found');
|
|
633
|
+
process.exit(0);
|
|
634
|
+
}
|
|
635
|
+
// Fork self as detached worker so Claude Code doesn't block
|
|
636
|
+
const { sessionId, transcriptPath } = payload;
|
|
637
|
+
debug(`Forking worker for session: ${sessionId}`);
|
|
638
|
+
try {
|
|
639
|
+
const scriptPath = __filename || process.argv[1];
|
|
640
|
+
const child = execFile('node', [scriptPath], {
|
|
641
|
+
env: {
|
|
642
|
+
...process.env,
|
|
643
|
+
_RESPAN_WORKER: '1',
|
|
644
|
+
_RESPAN_SESSION_ID: sessionId,
|
|
645
|
+
_RESPAN_TRANSCRIPT_PATH: transcriptPath,
|
|
646
|
+
},
|
|
647
|
+
stdio: 'ignore',
|
|
648
|
+
detached: true,
|
|
649
|
+
});
|
|
650
|
+
child.unref();
|
|
651
|
+
debug('Worker launched');
|
|
652
|
+
}
|
|
653
|
+
catch (e) {
|
|
654
|
+
log('ERROR', `Failed to fork worker: ${e}`);
|
|
655
|
+
}
|
|
656
|
+
// Exit immediately so Claude Code doesn't block
|
|
657
|
+
process.exit(0);
|
|
658
|
+
}
|
|
659
|
+
main();
|
|
@@ -665,7 +665,6 @@ function processBeforeTool(hookData) {
|
|
|
665
665
|
pending.push({ name: toolName, input: toolInput, start_time: nowISO() });
|
|
666
666
|
state.pending_tools = pending;
|
|
667
667
|
saveStreamState(sessionId, state);
|
|
668
|
-
process.stdout.write("{}\n");
|
|
669
668
|
}
|
|
670
669
|
function processAfterTool(hookData) {
|
|
671
670
|
const sessionId = String(hookData.session_id ?? "unknown");
|
|
@@ -690,7 +689,6 @@ function processAfterTool(hookData) {
|
|
|
690
689
|
state.pending_tools = pending;
|
|
691
690
|
state.tool_details = completed;
|
|
692
691
|
saveStreamState(sessionId, state);
|
|
693
|
-
process.stdout.write("{}\n");
|
|
694
692
|
}
|
|
695
693
|
function processChunk(hookData) {
|
|
696
694
|
const sessionId = String(hookData.session_id ?? "unknown");
|
|
@@ -771,14 +769,12 @@ function processChunk(hookData) {
|
|
|
771
769
|
}
|
|
772
770
|
saveStreamState(sessionId, state);
|
|
773
771
|
debug(`Tool call via response parts (finish=${finishReason}), tool_turns=${state.tool_turns}`);
|
|
774
|
-
process.stdout.write("{}\n");
|
|
775
772
|
return;
|
|
776
773
|
}
|
|
777
774
|
const hasPendingTools = (state.pending_tools ?? []).length > 0;
|
|
778
775
|
const hadToolsThisTurn = (state.tool_turns ?? 0) > 0 || hasPendingTools;
|
|
779
776
|
const hasNewText = state.accumulated_text.length > (state.last_send_text_len ?? 0);
|
|
780
777
|
const shouldSend = hasNewText && state.accumulated_text && (isFinished || !hadToolsThisTurn && !toolCallDetected && !chunkText);
|
|
781
|
-
process.stdout.write("{}\n");
|
|
782
778
|
if (!shouldSend) {
|
|
783
779
|
if (toolCallDetected) saveStreamState(sessionId, state);
|
|
784
780
|
return;
|
|
@@ -819,10 +815,8 @@ function processChunk(hookData) {
|
|
|
819
815
|
function main() {
|
|
820
816
|
try {
|
|
821
817
|
const raw = fs2.readFileSync(0, "utf-8");
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
return;
|
|
825
|
-
}
|
|
818
|
+
process.stdout.write("{}\n");
|
|
819
|
+
if (!raw.trim()) return;
|
|
826
820
|
const hookData = JSON.parse(raw);
|
|
827
821
|
const event = String(hookData.hook_event_name ?? "");
|
|
828
822
|
const unlock = acquireLock(LOCK_PATH);
|
|
@@ -843,7 +837,6 @@ function main() {
|
|
|
843
837
|
} else {
|
|
844
838
|
log("ERROR", `Hook error: ${e}`);
|
|
845
839
|
}
|
|
846
|
-
process.stdout.write("{}\n");
|
|
847
840
|
}
|
|
848
841
|
}
|
|
849
842
|
main();
|
package/dist/hooks/gemini-cli.js
CHANGED
|
@@ -388,7 +388,6 @@ function processBeforeTool(hookData) {
|
|
|
388
388
|
pending.push({ name: toolName, input: toolInput, start_time: nowISO() });
|
|
389
389
|
state.pending_tools = pending;
|
|
390
390
|
saveStreamState(sessionId, state);
|
|
391
|
-
process.stdout.write('{}\n');
|
|
392
391
|
}
|
|
393
392
|
function processAfterTool(hookData) {
|
|
394
393
|
const sessionId = String(hookData.session_id ?? 'unknown');
|
|
@@ -415,7 +414,6 @@ function processAfterTool(hookData) {
|
|
|
415
414
|
state.pending_tools = pending;
|
|
416
415
|
state.tool_details = completed;
|
|
417
416
|
saveStreamState(sessionId, state);
|
|
418
|
-
process.stdout.write('{}\n');
|
|
419
417
|
}
|
|
420
418
|
// ── AfterModel chunk processing ──────────────────────────────────
|
|
421
419
|
function processChunk(hookData) {
|
|
@@ -507,7 +505,6 @@ function processChunk(hookData) {
|
|
|
507
505
|
}
|
|
508
506
|
saveStreamState(sessionId, state);
|
|
509
507
|
debug(`Tool call via response parts (finish=${finishReason}), tool_turns=${state.tool_turns}`);
|
|
510
|
-
process.stdout.write('{}\n');
|
|
511
508
|
return;
|
|
512
509
|
}
|
|
513
510
|
// Detect completion and send.
|
|
@@ -520,7 +517,6 @@ function processChunk(hookData) {
|
|
|
520
517
|
&& state.accumulated_text
|
|
521
518
|
&& (isFinished
|
|
522
519
|
|| (!hadToolsThisTurn && !toolCallDetected && !chunkText)));
|
|
523
|
-
process.stdout.write('{}\n');
|
|
524
520
|
if (!shouldSend) {
|
|
525
521
|
if (toolCallDetected)
|
|
526
522
|
saveStreamState(sessionId, state);
|
|
@@ -556,10 +552,10 @@ function processChunk(hookData) {
|
|
|
556
552
|
function main() {
|
|
557
553
|
try {
|
|
558
554
|
const raw = fs.readFileSync(0, 'utf-8');
|
|
559
|
-
|
|
560
|
-
|
|
555
|
+
// Respond to Gemini CLI immediately so it doesn't block
|
|
556
|
+
process.stdout.write('{}\n');
|
|
557
|
+
if (!raw.trim())
|
|
561
558
|
return;
|
|
562
|
-
}
|
|
563
559
|
const hookData = JSON.parse(raw);
|
|
564
560
|
const event = String(hookData.hook_event_name ?? '');
|
|
565
561
|
const unlock = acquireLock(LOCK_PATH);
|
|
@@ -585,7 +581,6 @@ function main() {
|
|
|
585
581
|
else {
|
|
586
582
|
log('ERROR', `Hook error: ${e}`);
|
|
587
583
|
}
|
|
588
|
-
process.stdout.write('{}\n');
|
|
589
584
|
}
|
|
590
585
|
}
|
|
591
586
|
main();
|