linkshell-cli 0.2.19 → 0.2.21
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/cli/src/commands/setup.js +1 -1
- package/dist/cli/src/commands/setup.js.map +1 -1
- package/dist/cli/src/providers.d.ts +1 -1
- package/dist/cli/src/providers.js +33 -0
- package/dist/cli/src/providers.js.map +1 -1
- package/dist/cli/src/runtime/bridge-session.d.ts +11 -0
- package/dist/cli/src/runtime/bridge-session.js +225 -36
- package/dist/cli/src/runtime/bridge-session.js.map +1 -1
- package/dist/cli/tsconfig.tsbuildinfo +1 -1
- package/dist/shared-protocol/src/index.d.ts +26 -20
- package/dist/shared-protocol/src/index.js +1 -0
- package/dist/shared-protocol/src/index.js.map +1 -1
- package/package.json +2 -2
- package/src/commands/setup.ts +2 -2
- package/src/providers.ts +51 -1
- package/src/runtime/bridge-session.ts +232 -34
|
@@ -2,7 +2,7 @@ import * as pty from "node-pty";
|
|
|
2
2
|
import * as http from "node:http";
|
|
3
3
|
import WebSocket from "ws";
|
|
4
4
|
import { hostname, platform, homedir } from "node:os";
|
|
5
|
-
import { writeFileSync, readdirSync, statSync, unlinkSync } from "node:fs";
|
|
5
|
+
import { writeFileSync, readFileSync, readdirSync, statSync, unlinkSync, mkdirSync, existsSync } from "node:fs";
|
|
6
6
|
import { tmpdir } from "node:os";
|
|
7
7
|
import { join, basename, resolve } from "node:path";
|
|
8
8
|
import {
|
|
@@ -47,6 +47,7 @@ interface TerminalInstance {
|
|
|
47
47
|
provider: string;
|
|
48
48
|
scrollback: ScrollbackBuffer;
|
|
49
49
|
outputSeq: number;
|
|
50
|
+
statusSeq: number;
|
|
50
51
|
status: "running" | "exited";
|
|
51
52
|
hookServer?: http.Server;
|
|
52
53
|
hookPort?: number;
|
|
@@ -468,16 +469,16 @@ export class BridgeSession {
|
|
|
468
469
|
const provider = providerOverride ?? this.options.providerConfig.provider;
|
|
469
470
|
const args = [...this.options.providerConfig.args];
|
|
470
471
|
|
|
471
|
-
//
|
|
472
|
+
// Set up hook server for structured status (all supported providers)
|
|
472
473
|
let hookServer: http.Server | undefined;
|
|
473
474
|
let hookPort: number | undefined;
|
|
474
475
|
let hookConfigPath: string | undefined;
|
|
475
476
|
|
|
476
|
-
if (provider === "claude") {
|
|
477
|
-
const
|
|
478
|
-
hookServer = server;
|
|
479
|
-
hookPort = port;
|
|
480
|
-
hookConfigPath = configPath;
|
|
477
|
+
if (provider === "claude" || provider === "codex" || provider === "gemini" || provider === "copilot") {
|
|
478
|
+
const result = await this.setupHookServer(terminalId, args, provider);
|
|
479
|
+
hookServer = result.server;
|
|
480
|
+
hookPort = result.port;
|
|
481
|
+
hookConfigPath = result.configPath;
|
|
481
482
|
}
|
|
482
483
|
|
|
483
484
|
const term: TerminalInstance = {
|
|
@@ -498,6 +499,7 @@ export class BridgeSession {
|
|
|
498
499
|
provider,
|
|
499
500
|
scrollback: new ScrollbackBuffer(1000),
|
|
500
501
|
outputSeq: 0,
|
|
502
|
+
statusSeq: 0,
|
|
501
503
|
status: "running",
|
|
502
504
|
hookServer,
|
|
503
505
|
hookPort,
|
|
@@ -550,7 +552,7 @@ export class BridgeSession {
|
|
|
550
552
|
this.log(`spawned terminal ${terminalId} in ${cwd}`);
|
|
551
553
|
}
|
|
552
554
|
|
|
553
|
-
private async setupHookServer(terminalId: string, args: string[]): Promise<{
|
|
555
|
+
private async setupHookServer(terminalId: string, args: string[], provider: string): Promise<{
|
|
554
556
|
server: http.Server;
|
|
555
557
|
port: number;
|
|
556
558
|
configPath: string;
|
|
@@ -568,7 +570,7 @@ export class BridgeSession {
|
|
|
568
570
|
res.end("ok");
|
|
569
571
|
try {
|
|
570
572
|
const event = JSON.parse(body);
|
|
571
|
-
this.handleHookEvent(terminalId, event);
|
|
573
|
+
this.handleHookEvent(terminalId, event, provider);
|
|
572
574
|
} catch (e) {
|
|
573
575
|
this.log(`hook parse error: ${e}`);
|
|
574
576
|
}
|
|
@@ -583,32 +585,156 @@ export class BridgeSession {
|
|
|
583
585
|
});
|
|
584
586
|
server.on("error", reject);
|
|
585
587
|
});
|
|
586
|
-
this.log(`hook server for ${terminalId} listening on port ${port}`);
|
|
588
|
+
this.log(`hook server for ${terminalId} (${provider}) listening on port ${port}`);
|
|
587
589
|
|
|
588
|
-
// Write temporary hook config
|
|
589
|
-
const configPath = join(tmpdir(), `linkshell-hooks-${terminalId}.json`);
|
|
590
590
|
const curlCmd = `curl -s -X POST http://127.0.0.1:${port}/hook -H 'Content-Type: application/json' -d "$(cat)"`;
|
|
591
|
-
|
|
591
|
+
let configPath: string;
|
|
592
|
+
|
|
593
|
+
if (provider === "codex") {
|
|
594
|
+
configPath = this.setupCodexHooks(terminalId, curlCmd);
|
|
595
|
+
} else if (provider === "gemini") {
|
|
596
|
+
configPath = this.setupGeminiHooks(terminalId, curlCmd);
|
|
597
|
+
} else if (provider === "copilot") {
|
|
598
|
+
configPath = this.setupCopilotHooks(terminalId, curlCmd);
|
|
599
|
+
} else {
|
|
600
|
+
// Claude (default)
|
|
601
|
+
configPath = this.setupClaudeHooks(terminalId, curlCmd, args);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
return { server, port, configPath };
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
private setupClaudeHooks(terminalId: string, curlCmd: string, args: string[]): string {
|
|
608
|
+
const configPath = join(tmpdir(), `linkshell-hooks-${terminalId}.json`);
|
|
609
|
+
const hookEntry = { matcher: "", hooks: [{ type: "command", command: curlCmd, timeout: 5 }] };
|
|
610
|
+
const hookEntryWithMatcher = { matcher: "*", hooks: [{ type: "command", command: curlCmd, timeout: 5 }] };
|
|
611
|
+
const permissionEntry = { matcher: "*", hooks: [{ type: "command", command: curlCmd, timeout: 86400 }] };
|
|
592
612
|
const config = {
|
|
593
613
|
hooks: {
|
|
594
|
-
PreToolUse: [
|
|
595
|
-
PostToolUse: [
|
|
596
|
-
PostToolUseFailure: [
|
|
614
|
+
PreToolUse: [hookEntryWithMatcher],
|
|
615
|
+
PostToolUse: [hookEntryWithMatcher],
|
|
616
|
+
PostToolUseFailure: [hookEntryWithMatcher],
|
|
597
617
|
Stop: [hookEntry],
|
|
598
|
-
PermissionRequest: [
|
|
618
|
+
PermissionRequest: [permissionEntry],
|
|
619
|
+
UserPromptSubmit: [hookEntry],
|
|
620
|
+
SessionStart: [hookEntry],
|
|
599
621
|
},
|
|
600
622
|
};
|
|
601
623
|
writeFileSync(configPath, JSON.stringify(config));
|
|
602
|
-
this.log(`hook config written to ${configPath}`);
|
|
624
|
+
this.log(`claude hook config written to ${configPath}`);
|
|
603
625
|
|
|
604
626
|
// Inject --settings into Claude CLI args
|
|
605
627
|
args.push("--settings", configPath);
|
|
628
|
+
return configPath;
|
|
629
|
+
}
|
|
606
630
|
|
|
607
|
-
|
|
631
|
+
private setupCodexHooks(terminalId: string, curlCmd: string): string {
|
|
632
|
+
// Codex uses ~/.codex/hooks.json — nested format (no matcher)
|
|
633
|
+
const codexDir = join(homedir(), ".codex");
|
|
634
|
+
if (!existsSync(codexDir)) mkdirSync(codexDir, { recursive: true });
|
|
635
|
+
|
|
636
|
+
// Ensure codex_hooks = true in config.toml
|
|
637
|
+
const tomlPath = join(codexDir, "config.toml");
|
|
638
|
+
let tomlContent = "";
|
|
639
|
+
try { tomlContent = readFileSync(tomlPath, "utf8"); } catch { /* doesn't exist yet */ }
|
|
640
|
+
if (!tomlContent.includes("codex_hooks")) {
|
|
641
|
+
tomlContent += `\ncodex_hooks = true\n`;
|
|
642
|
+
writeFileSync(tomlPath, tomlContent);
|
|
643
|
+
this.log(`enabled codex_hooks in ${tomlPath}`);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
const hooksPath = join(codexDir, "hooks.json");
|
|
647
|
+
const hookEntry = { hooks: [{ type: "command", command: curlCmd, timeout: 5 }] };
|
|
648
|
+
const config = {
|
|
649
|
+
hooks: {
|
|
650
|
+
SessionStart: [hookEntry],
|
|
651
|
+
PreToolUse: [hookEntry],
|
|
652
|
+
PostToolUse: [hookEntry],
|
|
653
|
+
},
|
|
654
|
+
};
|
|
655
|
+
|
|
656
|
+
// Backup existing hooks.json if present and not ours
|
|
657
|
+
if (existsSync(hooksPath)) {
|
|
658
|
+
try {
|
|
659
|
+
const existing = readFileSync(hooksPath, "utf8");
|
|
660
|
+
if (!existing.includes(`127.0.0.1:`) || !existing.includes("/hook")) {
|
|
661
|
+
writeFileSync(`${hooksPath}.linkshell-backup`, existing);
|
|
662
|
+
this.log(`backed up existing codex hooks`);
|
|
663
|
+
}
|
|
664
|
+
} catch { /* ignore */ }
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
writeFileSync(hooksPath, JSON.stringify(config, null, 2));
|
|
668
|
+
this.log(`codex hook config written to ${hooksPath}`);
|
|
669
|
+
return hooksPath;
|
|
608
670
|
}
|
|
609
671
|
|
|
610
|
-
private
|
|
611
|
-
|
|
672
|
+
private setupGeminiHooks(terminalId: string, curlCmd: string): string {
|
|
673
|
+
// Gemini uses ~/.gemini/settings.json — nested format, timeout in milliseconds
|
|
674
|
+
const geminiDir = join(homedir(), ".gemini");
|
|
675
|
+
if (!existsSync(geminiDir)) mkdirSync(geminiDir, { recursive: true });
|
|
676
|
+
|
|
677
|
+
const settingsPath = join(geminiDir, "settings.json");
|
|
678
|
+
const hookEntry = { hooks: [{ type: "command", command: curlCmd, timeout: 5000 }] };
|
|
679
|
+
const hookConfig = {
|
|
680
|
+
SessionStart: [hookEntry],
|
|
681
|
+
BeforeTool: [hookEntry],
|
|
682
|
+
AfterTool: [hookEntry],
|
|
683
|
+
BeforeSubmitPrompt: [hookEntry],
|
|
684
|
+
AfterSubmitPrompt: [hookEntry],
|
|
685
|
+
SessionEnd: [hookEntry],
|
|
686
|
+
};
|
|
687
|
+
|
|
688
|
+
// Merge with existing settings if present
|
|
689
|
+
let existing: Record<string, unknown> = {};
|
|
690
|
+
try {
|
|
691
|
+
existing = JSON.parse(readFileSync(settingsPath, "utf8"));
|
|
692
|
+
} catch { /* doesn't exist yet */ }
|
|
693
|
+
|
|
694
|
+
// Backup existing hooks if present and not ours
|
|
695
|
+
if (existing.hooks && JSON.stringify(existing.hooks).indexOf("127.0.0.1:") === -1) {
|
|
696
|
+
writeFileSync(`${settingsPath}.linkshell-backup`, JSON.stringify(existing, null, 2));
|
|
697
|
+
this.log(`backed up existing gemini settings`);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
existing.hooks = hookConfig;
|
|
701
|
+
writeFileSync(settingsPath, JSON.stringify(existing, null, 2));
|
|
702
|
+
this.log(`gemini hook config written to ${settingsPath}`);
|
|
703
|
+
return settingsPath;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
private setupCopilotHooks(terminalId: string, curlCmd: string): string {
|
|
707
|
+
// Copilot uses ~/.copilot/hooks/<name>.json — flat format with `bash` key
|
|
708
|
+
const copilotDir = join(homedir(), ".copilot", "hooks");
|
|
709
|
+
if (!existsSync(copilotDir)) mkdirSync(copilotDir, { recursive: true });
|
|
710
|
+
|
|
711
|
+
const hooksPath = join(copilotDir, "linkshell.json");
|
|
712
|
+
const mkHook = (eventName: string) => ({
|
|
713
|
+
type: "command",
|
|
714
|
+
bash: `${curlCmd.replace('"$(cat)"', `'{"hook_event_name":"${eventName}"}'`)}`,
|
|
715
|
+
timeoutSec: 5,
|
|
716
|
+
});
|
|
717
|
+
const config = {
|
|
718
|
+
version: 1,
|
|
719
|
+
hooks: {
|
|
720
|
+
sessionStart: [mkHook("sessionStart")],
|
|
721
|
+
preToolUse: [mkHook("preToolUse")],
|
|
722
|
+
postToolUse: [mkHook("postToolUse")],
|
|
723
|
+
sessionEnd: [mkHook("sessionEnd")],
|
|
724
|
+
},
|
|
725
|
+
};
|
|
726
|
+
|
|
727
|
+
writeFileSync(hooksPath, JSON.stringify(config, null, 2));
|
|
728
|
+
this.log(`copilot hook config written to ${hooksPath}`);
|
|
729
|
+
return hooksPath;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
private handleHookEvent(terminalId: string, event: Record<string, unknown>, provider: string): void {
|
|
733
|
+
const rawHookName = (event.hook_event_name ?? event.event_name) as string | undefined;
|
|
734
|
+
if (!rawHookName) return;
|
|
735
|
+
|
|
736
|
+
// Normalize hook event names from different providers to unified names
|
|
737
|
+
const hookName = this.normalizeHookName(rawHookName, provider);
|
|
612
738
|
if (!hookName) return;
|
|
613
739
|
|
|
614
740
|
let phase: string;
|
|
@@ -620,34 +746,33 @@ export class BridgeSession {
|
|
|
620
746
|
switch (hookName) {
|
|
621
747
|
case "PreToolUse":
|
|
622
748
|
phase = "tool_use";
|
|
623
|
-
toolName = event.tool_name as string | undefined;
|
|
749
|
+
toolName = (event.tool_name ?? event.toolName) as string | undefined;
|
|
624
750
|
if (event.tool_input && typeof event.tool_input === "object") {
|
|
625
751
|
const input = event.tool_input as Record<string, unknown>;
|
|
626
752
|
toolInput = JSON.stringify(input).slice(0, 200);
|
|
753
|
+
} else if (event.toolInput && typeof event.toolInput === "object") {
|
|
754
|
+
toolInput = JSON.stringify(event.toolInput).slice(0, 200);
|
|
627
755
|
}
|
|
628
756
|
break;
|
|
629
757
|
case "PostToolUse":
|
|
630
758
|
phase = "thinking";
|
|
631
|
-
toolName = event.tool_name as string | undefined;
|
|
632
|
-
// Pop permission stack: tool completed
|
|
759
|
+
toolName = (event.tool_name ?? event.toolName) as string | undefined;
|
|
760
|
+
// Pop permission stack: tool completed
|
|
633
761
|
{
|
|
634
762
|
const stack = this.permissionStacks.get(terminalId);
|
|
635
763
|
if (stack && stack.length > 0) {
|
|
636
|
-
|
|
637
|
-
if (idx >= 0) stack.splice(idx, 1);
|
|
764
|
+
stack.pop();
|
|
638
765
|
if (stack.length === 0) this.permissionStacks.delete(terminalId);
|
|
639
766
|
}
|
|
640
767
|
}
|
|
641
768
|
break;
|
|
642
769
|
case "PostToolUseFailure":
|
|
643
770
|
phase = "error";
|
|
644
|
-
toolName = event.tool_name as string | undefined;
|
|
645
|
-
// Pop permission stack on failure too
|
|
771
|
+
toolName = (event.tool_name ?? event.toolName) as string | undefined;
|
|
646
772
|
{
|
|
647
773
|
const stack = this.permissionStacks.get(terminalId);
|
|
648
774
|
if (stack && stack.length > 0) {
|
|
649
|
-
|
|
650
|
-
if (idx >= 0) stack.splice(idx, 1);
|
|
775
|
+
stack.pop();
|
|
651
776
|
if (stack.length === 0) this.permissionStacks.delete(terminalId);
|
|
652
777
|
}
|
|
653
778
|
}
|
|
@@ -655,15 +780,16 @@ export class BridgeSession {
|
|
|
655
780
|
case "Stop":
|
|
656
781
|
phase = "idle";
|
|
657
782
|
if (event.stop_reason) summary = String(event.stop_reason);
|
|
658
|
-
// Clear all pending permissions on stop
|
|
659
783
|
this.permissionStacks.delete(terminalId);
|
|
660
784
|
break;
|
|
661
785
|
case "PermissionRequest":
|
|
662
786
|
phase = "waiting";
|
|
663
|
-
toolName = event.tool_name as string | undefined;
|
|
787
|
+
toolName = (event.tool_name ?? event.toolName) as string | undefined;
|
|
664
788
|
if (event.tool_input && typeof event.tool_input === "object") {
|
|
665
789
|
const input = event.tool_input as Record<string, unknown>;
|
|
666
790
|
permissionRequest = JSON.stringify(input).slice(0, 300);
|
|
791
|
+
} else if (event.toolInput && typeof event.toolInput === "object") {
|
|
792
|
+
permissionRequest = JSON.stringify(event.toolInput).slice(0, 300);
|
|
667
793
|
}
|
|
668
794
|
// Push to permission stack
|
|
669
795
|
{
|
|
@@ -680,23 +806,32 @@ export class BridgeSession {
|
|
|
680
806
|
});
|
|
681
807
|
}
|
|
682
808
|
break;
|
|
809
|
+
case "SessionStart":
|
|
810
|
+
phase = "idle";
|
|
811
|
+
summary = "session started";
|
|
812
|
+
break;
|
|
683
813
|
default:
|
|
684
814
|
return;
|
|
685
815
|
}
|
|
686
816
|
|
|
687
|
-
this.log(`hook event: ${hookName} → phase=${phase} tool=${toolName ?? "none"}`);
|
|
817
|
+
this.log(`hook event [${provider}]: ${rawHookName} → ${hookName} → phase=${phase} tool=${toolName ?? "none"}`);
|
|
688
818
|
|
|
689
819
|
// Build topPermission from stack
|
|
690
820
|
const stack = this.permissionStacks.get(terminalId);
|
|
691
821
|
const topPermission = stack && stack.length > 0 ? stack[stack.length - 1] : undefined;
|
|
692
822
|
const pendingPermissionCount = stack?.length ?? 0;
|
|
693
823
|
|
|
824
|
+
// Increment statusSeq for ordering
|
|
825
|
+
const term = this.terminals.get(terminalId);
|
|
826
|
+
const seq = term ? term.statusSeq++ : 0;
|
|
827
|
+
|
|
694
828
|
this.send(createEnvelope({
|
|
695
829
|
type: "terminal.status",
|
|
696
830
|
sessionId: this.sessionId,
|
|
697
831
|
terminalId,
|
|
698
832
|
payload: {
|
|
699
833
|
phase,
|
|
834
|
+
seq,
|
|
700
835
|
...(toolName && { toolName }),
|
|
701
836
|
...(toolInput && { toolInput }),
|
|
702
837
|
...(permissionRequest && { permissionRequest }),
|
|
@@ -707,6 +842,56 @@ export class BridgeSession {
|
|
|
707
842
|
}));
|
|
708
843
|
}
|
|
709
844
|
|
|
845
|
+
/**
|
|
846
|
+
* Normalize hook event names from different CLI providers to unified internal names.
|
|
847
|
+
* Claude: PascalCase (PreToolUse, PostToolUse, Stop, PermissionRequest)
|
|
848
|
+
* Codex: camelCase (preToolUse, postToolUse, sessionStart)
|
|
849
|
+
* Gemini: PascalCase but different names (BeforeTool, AfterTool, BeforeSubmitPrompt)
|
|
850
|
+
*/
|
|
851
|
+
private normalizeHookName(rawName: string, provider: string): string | undefined {
|
|
852
|
+
// Claude events — already in our canonical format
|
|
853
|
+
if (provider === "claude") {
|
|
854
|
+
return rawName;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// Codex events (PascalCase in nested format)
|
|
858
|
+
if (provider === "codex") {
|
|
859
|
+
switch (rawName) {
|
|
860
|
+
case "PreToolUse": case "preToolUse": return "PreToolUse";
|
|
861
|
+
case "PostToolUse": case "postToolUse": return "PostToolUse";
|
|
862
|
+
case "SessionStart": case "sessionStart": return "SessionStart";
|
|
863
|
+
default: return undefined;
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
// Gemini events
|
|
868
|
+
if (provider === "gemini") {
|
|
869
|
+
switch (rawName) {
|
|
870
|
+
case "BeforeTool": return "PreToolUse";
|
|
871
|
+
case "AfterTool": return "PostToolUse";
|
|
872
|
+
case "BeforeSubmitPrompt": return "SessionStart";
|
|
873
|
+
case "AfterSubmitPrompt": return "Stop";
|
|
874
|
+
case "SessionStart": return "SessionStart";
|
|
875
|
+
case "SessionEnd": return "Stop";
|
|
876
|
+
default: return undefined;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
// Copilot events (camelCase)
|
|
881
|
+
if (provider === "copilot") {
|
|
882
|
+
switch (rawName) {
|
|
883
|
+
case "preToolUse": return "PreToolUse";
|
|
884
|
+
case "postToolUse": return "PostToolUse";
|
|
885
|
+
case "sessionStart": return "SessionStart";
|
|
886
|
+
case "sessionEnd": return "Stop";
|
|
887
|
+
default: return undefined;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
// Unknown provider — try to pass through
|
|
892
|
+
return rawName;
|
|
893
|
+
}
|
|
894
|
+
|
|
710
895
|
private cleanupHookServer(term: TerminalInstance): void {
|
|
711
896
|
if (term.hookServer) {
|
|
712
897
|
term.hookServer.close();
|
|
@@ -714,7 +899,20 @@ export class BridgeSession {
|
|
|
714
899
|
this.log(`hook server closed for ${term.id}`);
|
|
715
900
|
}
|
|
716
901
|
if (term.hookConfigPath) {
|
|
717
|
-
|
|
902
|
+
const configPath = term.hookConfigPath;
|
|
903
|
+
// Claude uses tmp files — safe to delete
|
|
904
|
+
// Codex/Gemini/Copilot use home dir configs — restore backup if exists
|
|
905
|
+
const backupPath = `${configPath}.linkshell-backup`;
|
|
906
|
+
try {
|
|
907
|
+
if (existsSync(backupPath)) {
|
|
908
|
+
const backup = readFileSync(backupPath, "utf8");
|
|
909
|
+
writeFileSync(configPath, backup);
|
|
910
|
+
unlinkSync(backupPath);
|
|
911
|
+
this.log(`restored backup for ${configPath}`);
|
|
912
|
+
} else if (configPath.startsWith(tmpdir())) {
|
|
913
|
+
unlinkSync(configPath);
|
|
914
|
+
}
|
|
915
|
+
} catch { /* ignore */ }
|
|
718
916
|
term.hookConfigPath = undefined;
|
|
719
917
|
}
|
|
720
918
|
}
|