@austinthesing/magic-shell 0.1.0 → 0.1.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/LICENSE +1 -1
- package/dist/cli.js +163 -54
- package/dist/index.js +219 -63
- package/package.json +1 -1
package/LICENSE
CHANGED
package/dist/cli.js
CHANGED
|
@@ -19587,7 +19587,7 @@ var init_types = __esm(() => {
|
|
|
19587
19587
|
});
|
|
19588
19588
|
|
|
19589
19589
|
// src/lib/keychain.ts
|
|
19590
|
-
import {
|
|
19590
|
+
import { spawnSync } from "child_process";
|
|
19591
19591
|
async function setSecret(key, value) {
|
|
19592
19592
|
switch (process.platform) {
|
|
19593
19593
|
case "darwin":
|
|
@@ -19614,20 +19614,57 @@ async function getSecret(key) {
|
|
|
19614
19614
|
}
|
|
19615
19615
|
function setSecretMacOS(key, value) {
|
|
19616
19616
|
try {
|
|
19617
|
-
|
|
19618
|
-
|
|
19619
|
-
|
|
19620
|
-
|
|
19621
|
-
|
|
19622
|
-
|
|
19617
|
+
spawnSync("security", [
|
|
19618
|
+
"delete-generic-password",
|
|
19619
|
+
"-s",
|
|
19620
|
+
SERVICE_NAME,
|
|
19621
|
+
"-a",
|
|
19622
|
+
key
|
|
19623
|
+
], {
|
|
19624
|
+
encoding: "utf-8",
|
|
19625
|
+
stdio: "pipe"
|
|
19626
|
+
});
|
|
19627
|
+
const result = spawnSync("security", [
|
|
19628
|
+
"add-generic-password",
|
|
19629
|
+
"-s",
|
|
19630
|
+
SERVICE_NAME,
|
|
19631
|
+
"-a",
|
|
19632
|
+
key,
|
|
19633
|
+
"-w",
|
|
19634
|
+
value
|
|
19635
|
+
], {
|
|
19636
|
+
encoding: "utf-8",
|
|
19637
|
+
stdio: "pipe"
|
|
19638
|
+
});
|
|
19639
|
+
return result.status === 0;
|
|
19640
|
+
} catch (error) {
|
|
19641
|
+
if (process.env.DEBUG_API === "1") {
|
|
19642
|
+
console.error("[DEBUG] macOS keychain set error:", error instanceof Error ? error.message : String(error));
|
|
19643
|
+
}
|
|
19623
19644
|
return false;
|
|
19624
19645
|
}
|
|
19625
19646
|
}
|
|
19626
19647
|
function getSecretMacOS(key) {
|
|
19627
19648
|
try {
|
|
19628
|
-
const result =
|
|
19629
|
-
|
|
19630
|
-
|
|
19649
|
+
const result = spawnSync("security", [
|
|
19650
|
+
"find-generic-password",
|
|
19651
|
+
"-s",
|
|
19652
|
+
SERVICE_NAME,
|
|
19653
|
+
"-a",
|
|
19654
|
+
key,
|
|
19655
|
+
"-w"
|
|
19656
|
+
], {
|
|
19657
|
+
encoding: "utf-8",
|
|
19658
|
+
stdio: "pipe"
|
|
19659
|
+
});
|
|
19660
|
+
if (result.status !== 0) {
|
|
19661
|
+
return null;
|
|
19662
|
+
}
|
|
19663
|
+
return result.stdout?.trim() || null;
|
|
19664
|
+
} catch (error) {
|
|
19665
|
+
if (process.env.DEBUG_API === "1") {
|
|
19666
|
+
console.error("[DEBUG] macOS keychain get error:", error instanceof Error ? error.message : String(error));
|
|
19667
|
+
}
|
|
19631
19668
|
return null;
|
|
19632
19669
|
}
|
|
19633
19670
|
}
|
|
@@ -19646,34 +19683,105 @@ function setSecretLinux(key, value) {
|
|
|
19646
19683
|
encoding: "utf-8"
|
|
19647
19684
|
});
|
|
19648
19685
|
return result.status === 0;
|
|
19649
|
-
} catch {
|
|
19686
|
+
} catch (error) {
|
|
19687
|
+
if (process.env.DEBUG_API === "1") {
|
|
19688
|
+
console.error("[DEBUG] Linux secret-tool set error:", error instanceof Error ? error.message : String(error));
|
|
19689
|
+
}
|
|
19650
19690
|
return false;
|
|
19651
19691
|
}
|
|
19652
19692
|
}
|
|
19653
19693
|
function getSecretLinux(key) {
|
|
19654
19694
|
try {
|
|
19655
|
-
const result =
|
|
19656
|
-
|
|
19657
|
-
|
|
19695
|
+
const result = spawnSync("secret-tool", [
|
|
19696
|
+
"lookup",
|
|
19697
|
+
"service",
|
|
19698
|
+
SERVICE_NAME,
|
|
19699
|
+
"account",
|
|
19700
|
+
key
|
|
19701
|
+
], {
|
|
19702
|
+
encoding: "utf-8",
|
|
19703
|
+
stdio: "pipe"
|
|
19704
|
+
});
|
|
19705
|
+
if (result.status !== 0) {
|
|
19706
|
+
return null;
|
|
19707
|
+
}
|
|
19708
|
+
return result.stdout?.trim() || null;
|
|
19709
|
+
} catch (error) {
|
|
19710
|
+
if (process.env.DEBUG_API === "1") {
|
|
19711
|
+
console.error("[DEBUG] Linux secret-tool get error:", error instanceof Error ? error.message : String(error));
|
|
19712
|
+
}
|
|
19658
19713
|
return null;
|
|
19659
19714
|
}
|
|
19660
19715
|
}
|
|
19661
19716
|
function setSecretWindows(key, value) {
|
|
19662
19717
|
try {
|
|
19663
19718
|
const targetName = `${SERVICE_NAME}:${key}`;
|
|
19664
|
-
|
|
19665
|
-
execSync(`cmdkey /delete:${targetName}`, {
|
|
19666
|
-
encoding: "utf-8",
|
|
19667
|
-
stdio: "pipe"
|
|
19668
|
-
});
|
|
19669
|
-
} catch {}
|
|
19670
|
-
const escapedValue = value.replace(/"/g, '""');
|
|
19671
|
-
execSync(`cmdkey /generic:${targetName} /user:${targetName} /pass:"${escapedValue}"`, {
|
|
19719
|
+
spawnSync("cmdkey", [`/delete:${targetName}`], {
|
|
19672
19720
|
encoding: "utf-8",
|
|
19673
19721
|
stdio: "pipe"
|
|
19674
19722
|
});
|
|
19675
|
-
|
|
19676
|
-
|
|
19723
|
+
const psScript = `
|
|
19724
|
+
$targetName = $input | Select-Object -First 1
|
|
19725
|
+
$password = $input | Select-Object -Skip 1 -First 1
|
|
19726
|
+
|
|
19727
|
+
Add-Type -TypeDefinition @"
|
|
19728
|
+
using System;
|
|
19729
|
+
using System.Runtime.InteropServices;
|
|
19730
|
+
|
|
19731
|
+
public class CredentialManager {
|
|
19732
|
+
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
|
19733
|
+
public static extern bool CredWrite(ref CREDENTIAL credential, int flags);
|
|
19734
|
+
|
|
19735
|
+
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
|
19736
|
+
public struct CREDENTIAL {
|
|
19737
|
+
public int Flags;
|
|
19738
|
+
public int Type;
|
|
19739
|
+
public string TargetName;
|
|
19740
|
+
public string Comment;
|
|
19741
|
+
public long LastWritten;
|
|
19742
|
+
public int CredentialBlobSize;
|
|
19743
|
+
public IntPtr CredentialBlob;
|
|
19744
|
+
public int Persist;
|
|
19745
|
+
public int AttributeCount;
|
|
19746
|
+
public IntPtr Attributes;
|
|
19747
|
+
public string TargetAlias;
|
|
19748
|
+
public string UserName;
|
|
19749
|
+
}
|
|
19750
|
+
|
|
19751
|
+
public static bool SaveCredential(string target, string password) {
|
|
19752
|
+
var passwordBytes = System.Text.Encoding.Unicode.GetBytes(password);
|
|
19753
|
+
var credentialBlob = Marshal.AllocHGlobal(passwordBytes.Length);
|
|
19754
|
+
Marshal.Copy(passwordBytes, 0, credentialBlob, passwordBytes.Length);
|
|
19755
|
+
|
|
19756
|
+
var credential = new CREDENTIAL {
|
|
19757
|
+
Type = 1, // CRED_TYPE_GENERIC
|
|
19758
|
+
TargetName = target,
|
|
19759
|
+
CredentialBlobSize = passwordBytes.Length,
|
|
19760
|
+
CredentialBlob = credentialBlob,
|
|
19761
|
+
Persist = 2, // CRED_PERSIST_LOCAL_MACHINE
|
|
19762
|
+
UserName = target
|
|
19763
|
+
};
|
|
19764
|
+
|
|
19765
|
+
var result = CredWrite(ref credential, 0);
|
|
19766
|
+
Marshal.FreeHGlobal(credentialBlob);
|
|
19767
|
+
return result;
|
|
19768
|
+
}
|
|
19769
|
+
}
|
|
19770
|
+
"@
|
|
19771
|
+
|
|
19772
|
+
[CredentialManager]::SaveCredential($targetName, $password)
|
|
19773
|
+
`.trim();
|
|
19774
|
+
const result = spawnSync("powershell", ["-NoProfile", "-Command", psScript], {
|
|
19775
|
+
input: `${targetName}
|
|
19776
|
+
${value}`,
|
|
19777
|
+
encoding: "utf-8",
|
|
19778
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
19779
|
+
});
|
|
19780
|
+
return result.stdout?.trim() === "True";
|
|
19781
|
+
} catch (error) {
|
|
19782
|
+
if (process.env.DEBUG_API === "1") {
|
|
19783
|
+
console.error("[DEBUG] Windows credential set error:", error instanceof Error ? error.message : String(error));
|
|
19784
|
+
}
|
|
19677
19785
|
return false;
|
|
19678
19786
|
}
|
|
19679
19787
|
}
|
|
@@ -19739,33 +19847,27 @@ public class CredentialManager {
|
|
|
19739
19847
|
});
|
|
19740
19848
|
const password = result.stdout?.trim();
|
|
19741
19849
|
return password || null;
|
|
19742
|
-
} catch {
|
|
19850
|
+
} catch (error) {
|
|
19851
|
+
if (process.env.DEBUG_API === "1") {
|
|
19852
|
+
console.error("[DEBUG] Windows credential get error:", error instanceof Error ? error.message : String(error));
|
|
19853
|
+
}
|
|
19743
19854
|
return null;
|
|
19744
19855
|
}
|
|
19745
19856
|
}
|
|
19746
19857
|
function isSecureStorageAvailable() {
|
|
19747
19858
|
switch (process.platform) {
|
|
19748
|
-
case "darwin":
|
|
19749
|
-
|
|
19750
|
-
|
|
19751
|
-
|
|
19752
|
-
|
|
19753
|
-
|
|
19754
|
-
|
|
19755
|
-
|
|
19756
|
-
|
|
19757
|
-
|
|
19758
|
-
|
|
19759
|
-
|
|
19760
|
-
return false;
|
|
19761
|
-
}
|
|
19762
|
-
case "win32":
|
|
19763
|
-
try {
|
|
19764
|
-
execSync("where cmdkey", { encoding: "utf-8", stdio: "pipe" });
|
|
19765
|
-
return true;
|
|
19766
|
-
} catch {
|
|
19767
|
-
return false;
|
|
19768
|
-
}
|
|
19859
|
+
case "darwin": {
|
|
19860
|
+
const result = spawnSync("which", ["security"], { encoding: "utf-8", stdio: "pipe" });
|
|
19861
|
+
return result.status === 0;
|
|
19862
|
+
}
|
|
19863
|
+
case "linux": {
|
|
19864
|
+
const result = spawnSync("which", ["secret-tool"], { encoding: "utf-8", stdio: "pipe" });
|
|
19865
|
+
return result.status === 0;
|
|
19866
|
+
}
|
|
19867
|
+
case "win32": {
|
|
19868
|
+
const result = spawnSync("where", ["cmdkey"], { encoding: "utf-8", stdio: "pipe" });
|
|
19869
|
+
return result.status === 0;
|
|
19870
|
+
}
|
|
19769
19871
|
default:
|
|
19770
19872
|
return false;
|
|
19771
19873
|
}
|
|
@@ -20024,7 +20126,7 @@ var init_safety = __esm(() => {
|
|
|
20024
20126
|
});
|
|
20025
20127
|
|
|
20026
20128
|
// src/lib/shell.ts
|
|
20027
|
-
import { execSync
|
|
20129
|
+
import { execSync } from "child_process";
|
|
20028
20130
|
import { existsSync as existsSync5 } from "fs";
|
|
20029
20131
|
import { homedir as homedir2 } from "os";
|
|
20030
20132
|
function detectShell() {
|
|
@@ -20059,7 +20161,7 @@ function detectWSL() {
|
|
|
20059
20161
|
if (process.platform !== "linux")
|
|
20060
20162
|
return false;
|
|
20061
20163
|
try {
|
|
20062
|
-
const release =
|
|
20164
|
+
const release = execSync("uname -r", { encoding: "utf-8" }).toLowerCase();
|
|
20063
20165
|
if (release.includes("microsoft") || release.includes("wsl")) {
|
|
20064
20166
|
return true;
|
|
20065
20167
|
}
|
|
@@ -20087,7 +20189,7 @@ function getShellPath() {
|
|
|
20087
20189
|
if (process.platform !== "win32") {
|
|
20088
20190
|
try {
|
|
20089
20191
|
const ppid = process.ppid;
|
|
20090
|
-
const cmdline =
|
|
20192
|
+
const cmdline = execSync(`ps -p ${ppid} -o comm=`, { encoding: "utf-8" }).trim();
|
|
20091
20193
|
if (cmdline)
|
|
20092
20194
|
return cmdline;
|
|
20093
20195
|
} catch {}
|
|
@@ -20291,7 +20393,8 @@ function cleanCommand(command) {
|
|
|
20291
20393
|
let cleaned = command;
|
|
20292
20394
|
cleaned = cleaned.replace(/^```[\w]*\n?/gm, "");
|
|
20293
20395
|
cleaned = cleaned.replace(/\n?```$/gm, "");
|
|
20294
|
-
cleaned = cleaned.replace(
|
|
20396
|
+
cleaned = cleaned.replace(/^`+/, "");
|
|
20397
|
+
cleaned = cleaned.replace(/`+$/, "");
|
|
20295
20398
|
cleaned = cleaned.replace(/^(command:|shell:|bash:|zsh:|sh:)\s*/i, "");
|
|
20296
20399
|
const lines = cleaned.split(`
|
|
20297
20400
|
`);
|
|
@@ -21502,12 +21605,15 @@ function showThemeSelector() {
|
|
|
21502
21605
|
themeSelector = null;
|
|
21503
21606
|
}
|
|
21504
21607
|
const currentTheme2 = getTheme();
|
|
21608
|
+
const themeContainerWidth = 45;
|
|
21609
|
+
const terminalWidth = process.stdout.columns || 80;
|
|
21610
|
+
const themeContainerLeft = Math.max(2, Math.floor((terminalWidth - themeContainerWidth) / 2));
|
|
21505
21611
|
const container = new BoxRenderable(renderer, {
|
|
21506
21612
|
id: "theme-selector-container",
|
|
21507
21613
|
position: "absolute",
|
|
21508
|
-
left:
|
|
21614
|
+
left: themeContainerLeft,
|
|
21509
21615
|
top: 4,
|
|
21510
|
-
width:
|
|
21616
|
+
width: themeContainerWidth,
|
|
21511
21617
|
height: themeNames.length + 4,
|
|
21512
21618
|
backgroundColor: currentTheme2.colors.backgroundPanel,
|
|
21513
21619
|
border: true,
|
|
@@ -21650,12 +21756,15 @@ function showCommandPalette() {
|
|
|
21650
21756
|
return;
|
|
21651
21757
|
}
|
|
21652
21758
|
const commands = getCommandPaletteOptions();
|
|
21759
|
+
const paletteWidth = 55;
|
|
21760
|
+
const termWidth = process.stdout.columns || 80;
|
|
21761
|
+
const paletteLeft = Math.max(2, Math.floor((termWidth - paletteWidth) / 2));
|
|
21653
21762
|
const container = new BoxRenderable(renderer, {
|
|
21654
21763
|
id: "command-palette-container",
|
|
21655
21764
|
position: "absolute",
|
|
21656
|
-
left:
|
|
21765
|
+
left: paletteLeft,
|
|
21657
21766
|
top: 3,
|
|
21658
|
-
width:
|
|
21767
|
+
width: paletteWidth,
|
|
21659
21768
|
height: Math.min(commands.length + 4, 16),
|
|
21660
21769
|
backgroundColor: "#1e293b",
|
|
21661
21770
|
border: true,
|
package/dist/index.js
CHANGED
|
@@ -30,7 +30,7 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
|
30
30
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
31
31
|
|
|
32
32
|
// src/lib/keychain.ts
|
|
33
|
-
import {
|
|
33
|
+
import { spawnSync } from "child_process";
|
|
34
34
|
async function setSecret(key, value) {
|
|
35
35
|
switch (process.platform) {
|
|
36
36
|
case "darwin":
|
|
@@ -57,20 +57,57 @@ async function getSecret(key) {
|
|
|
57
57
|
}
|
|
58
58
|
function setSecretMacOS(key, value) {
|
|
59
59
|
try {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
60
|
+
spawnSync("security", [
|
|
61
|
+
"delete-generic-password",
|
|
62
|
+
"-s",
|
|
63
|
+
SERVICE_NAME,
|
|
64
|
+
"-a",
|
|
65
|
+
key
|
|
66
|
+
], {
|
|
67
|
+
encoding: "utf-8",
|
|
68
|
+
stdio: "pipe"
|
|
69
|
+
});
|
|
70
|
+
const result = spawnSync("security", [
|
|
71
|
+
"add-generic-password",
|
|
72
|
+
"-s",
|
|
73
|
+
SERVICE_NAME,
|
|
74
|
+
"-a",
|
|
75
|
+
key,
|
|
76
|
+
"-w",
|
|
77
|
+
value
|
|
78
|
+
], {
|
|
79
|
+
encoding: "utf-8",
|
|
80
|
+
stdio: "pipe"
|
|
81
|
+
});
|
|
82
|
+
return result.status === 0;
|
|
83
|
+
} catch (error) {
|
|
84
|
+
if (process.env.DEBUG_API === "1") {
|
|
85
|
+
console.error("[DEBUG] macOS keychain set error:", error instanceof Error ? error.message : String(error));
|
|
86
|
+
}
|
|
66
87
|
return false;
|
|
67
88
|
}
|
|
68
89
|
}
|
|
69
90
|
function getSecretMacOS(key) {
|
|
70
91
|
try {
|
|
71
|
-
const result =
|
|
72
|
-
|
|
73
|
-
|
|
92
|
+
const result = spawnSync("security", [
|
|
93
|
+
"find-generic-password",
|
|
94
|
+
"-s",
|
|
95
|
+
SERVICE_NAME,
|
|
96
|
+
"-a",
|
|
97
|
+
key,
|
|
98
|
+
"-w"
|
|
99
|
+
], {
|
|
100
|
+
encoding: "utf-8",
|
|
101
|
+
stdio: "pipe"
|
|
102
|
+
});
|
|
103
|
+
if (result.status !== 0) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
return result.stdout?.trim() || null;
|
|
107
|
+
} catch (error) {
|
|
108
|
+
if (process.env.DEBUG_API === "1") {
|
|
109
|
+
console.error("[DEBUG] macOS keychain get error:", error instanceof Error ? error.message : String(error));
|
|
110
|
+
}
|
|
74
111
|
return null;
|
|
75
112
|
}
|
|
76
113
|
}
|
|
@@ -89,34 +126,105 @@ function setSecretLinux(key, value) {
|
|
|
89
126
|
encoding: "utf-8"
|
|
90
127
|
});
|
|
91
128
|
return result.status === 0;
|
|
92
|
-
} catch {
|
|
129
|
+
} catch (error) {
|
|
130
|
+
if (process.env.DEBUG_API === "1") {
|
|
131
|
+
console.error("[DEBUG] Linux secret-tool set error:", error instanceof Error ? error.message : String(error));
|
|
132
|
+
}
|
|
93
133
|
return false;
|
|
94
134
|
}
|
|
95
135
|
}
|
|
96
136
|
function getSecretLinux(key) {
|
|
97
137
|
try {
|
|
98
|
-
const result =
|
|
99
|
-
|
|
100
|
-
|
|
138
|
+
const result = spawnSync("secret-tool", [
|
|
139
|
+
"lookup",
|
|
140
|
+
"service",
|
|
141
|
+
SERVICE_NAME,
|
|
142
|
+
"account",
|
|
143
|
+
key
|
|
144
|
+
], {
|
|
145
|
+
encoding: "utf-8",
|
|
146
|
+
stdio: "pipe"
|
|
147
|
+
});
|
|
148
|
+
if (result.status !== 0) {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
return result.stdout?.trim() || null;
|
|
152
|
+
} catch (error) {
|
|
153
|
+
if (process.env.DEBUG_API === "1") {
|
|
154
|
+
console.error("[DEBUG] Linux secret-tool get error:", error instanceof Error ? error.message : String(error));
|
|
155
|
+
}
|
|
101
156
|
return null;
|
|
102
157
|
}
|
|
103
158
|
}
|
|
104
159
|
function setSecretWindows(key, value) {
|
|
105
160
|
try {
|
|
106
161
|
const targetName = `${SERVICE_NAME}:${key}`;
|
|
107
|
-
|
|
108
|
-
execSync(`cmdkey /delete:${targetName}`, {
|
|
109
|
-
encoding: "utf-8",
|
|
110
|
-
stdio: "pipe"
|
|
111
|
-
});
|
|
112
|
-
} catch {}
|
|
113
|
-
const escapedValue = value.replace(/"/g, '""');
|
|
114
|
-
execSync(`cmdkey /generic:${targetName} /user:${targetName} /pass:"${escapedValue}"`, {
|
|
162
|
+
spawnSync("cmdkey", [`/delete:${targetName}`], {
|
|
115
163
|
encoding: "utf-8",
|
|
116
164
|
stdio: "pipe"
|
|
117
165
|
});
|
|
118
|
-
|
|
119
|
-
|
|
166
|
+
const psScript = `
|
|
167
|
+
$targetName = $input | Select-Object -First 1
|
|
168
|
+
$password = $input | Select-Object -Skip 1 -First 1
|
|
169
|
+
|
|
170
|
+
Add-Type -TypeDefinition @"
|
|
171
|
+
using System;
|
|
172
|
+
using System.Runtime.InteropServices;
|
|
173
|
+
|
|
174
|
+
public class CredentialManager {
|
|
175
|
+
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
|
176
|
+
public static extern bool CredWrite(ref CREDENTIAL credential, int flags);
|
|
177
|
+
|
|
178
|
+
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
|
179
|
+
public struct CREDENTIAL {
|
|
180
|
+
public int Flags;
|
|
181
|
+
public int Type;
|
|
182
|
+
public string TargetName;
|
|
183
|
+
public string Comment;
|
|
184
|
+
public long LastWritten;
|
|
185
|
+
public int CredentialBlobSize;
|
|
186
|
+
public IntPtr CredentialBlob;
|
|
187
|
+
public int Persist;
|
|
188
|
+
public int AttributeCount;
|
|
189
|
+
public IntPtr Attributes;
|
|
190
|
+
public string TargetAlias;
|
|
191
|
+
public string UserName;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
public static bool SaveCredential(string target, string password) {
|
|
195
|
+
var passwordBytes = System.Text.Encoding.Unicode.GetBytes(password);
|
|
196
|
+
var credentialBlob = Marshal.AllocHGlobal(passwordBytes.Length);
|
|
197
|
+
Marshal.Copy(passwordBytes, 0, credentialBlob, passwordBytes.Length);
|
|
198
|
+
|
|
199
|
+
var credential = new CREDENTIAL {
|
|
200
|
+
Type = 1, // CRED_TYPE_GENERIC
|
|
201
|
+
TargetName = target,
|
|
202
|
+
CredentialBlobSize = passwordBytes.Length,
|
|
203
|
+
CredentialBlob = credentialBlob,
|
|
204
|
+
Persist = 2, // CRED_PERSIST_LOCAL_MACHINE
|
|
205
|
+
UserName = target
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
var result = CredWrite(ref credential, 0);
|
|
209
|
+
Marshal.FreeHGlobal(credentialBlob);
|
|
210
|
+
return result;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
"@
|
|
214
|
+
|
|
215
|
+
[CredentialManager]::SaveCredential($targetName, $password)
|
|
216
|
+
`.trim();
|
|
217
|
+
const result = spawnSync("powershell", ["-NoProfile", "-Command", psScript], {
|
|
218
|
+
input: `${targetName}
|
|
219
|
+
${value}`,
|
|
220
|
+
encoding: "utf-8",
|
|
221
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
222
|
+
});
|
|
223
|
+
return result.stdout?.trim() === "True";
|
|
224
|
+
} catch (error) {
|
|
225
|
+
if (process.env.DEBUG_API === "1") {
|
|
226
|
+
console.error("[DEBUG] Windows credential set error:", error instanceof Error ? error.message : String(error));
|
|
227
|
+
}
|
|
120
228
|
return false;
|
|
121
229
|
}
|
|
122
230
|
}
|
|
@@ -182,33 +290,27 @@ public class CredentialManager {
|
|
|
182
290
|
});
|
|
183
291
|
const password = result.stdout?.trim();
|
|
184
292
|
return password || null;
|
|
185
|
-
} catch {
|
|
293
|
+
} catch (error) {
|
|
294
|
+
if (process.env.DEBUG_API === "1") {
|
|
295
|
+
console.error("[DEBUG] Windows credential get error:", error instanceof Error ? error.message : String(error));
|
|
296
|
+
}
|
|
186
297
|
return null;
|
|
187
298
|
}
|
|
188
299
|
}
|
|
189
300
|
function isSecureStorageAvailable() {
|
|
190
301
|
switch (process.platform) {
|
|
191
|
-
case "darwin":
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
return false;
|
|
204
|
-
}
|
|
205
|
-
case "win32":
|
|
206
|
-
try {
|
|
207
|
-
execSync("where cmdkey", { encoding: "utf-8", stdio: "pipe" });
|
|
208
|
-
return true;
|
|
209
|
-
} catch {
|
|
210
|
-
return false;
|
|
211
|
-
}
|
|
302
|
+
case "darwin": {
|
|
303
|
+
const result = spawnSync("which", ["security"], { encoding: "utf-8", stdio: "pipe" });
|
|
304
|
+
return result.status === 0;
|
|
305
|
+
}
|
|
306
|
+
case "linux": {
|
|
307
|
+
const result = spawnSync("which", ["secret-tool"], { encoding: "utf-8", stdio: "pipe" });
|
|
308
|
+
return result.status === 0;
|
|
309
|
+
}
|
|
310
|
+
case "win32": {
|
|
311
|
+
const result = spawnSync("where", ["cmdkey"], { encoding: "utf-8", stdio: "pipe" });
|
|
312
|
+
return result.status === 0;
|
|
313
|
+
}
|
|
212
314
|
default:
|
|
213
315
|
return false;
|
|
214
316
|
}
|
|
@@ -217,7 +319,7 @@ var SERVICE_NAME = "magic-shell";
|
|
|
217
319
|
var init_keychain = () => {};
|
|
218
320
|
|
|
219
321
|
// src/lib/shell.ts
|
|
220
|
-
import { execSync
|
|
322
|
+
import { execSync } from "child_process";
|
|
221
323
|
import { existsSync as existsSync2 } from "fs";
|
|
222
324
|
import { homedir as homedir2 } from "os";
|
|
223
325
|
function detectShell() {
|
|
@@ -252,7 +354,7 @@ function detectWSL() {
|
|
|
252
354
|
if (process.platform !== "linux")
|
|
253
355
|
return false;
|
|
254
356
|
try {
|
|
255
|
-
const release =
|
|
357
|
+
const release = execSync("uname -r", { encoding: "utf-8" }).toLowerCase();
|
|
256
358
|
if (release.includes("microsoft") || release.includes("wsl")) {
|
|
257
359
|
return true;
|
|
258
360
|
}
|
|
@@ -280,7 +382,7 @@ function getShellPath() {
|
|
|
280
382
|
if (process.platform !== "win32") {
|
|
281
383
|
try {
|
|
282
384
|
const ppid = process.ppid;
|
|
283
|
-
const cmdline =
|
|
385
|
+
const cmdline = execSync(`ps -p ${ppid} -o comm=`, { encoding: "utf-8" }).trim();
|
|
284
386
|
if (cmdline)
|
|
285
387
|
return cmdline;
|
|
286
388
|
} catch {}
|
|
@@ -20292,7 +20394,8 @@ function cleanCommand2(command) {
|
|
|
20292
20394
|
let cleaned = command;
|
|
20293
20395
|
cleaned = cleaned.replace(/^```[\w]*\n?/gm, "");
|
|
20294
20396
|
cleaned = cleaned.replace(/\n?```$/gm, "");
|
|
20295
|
-
cleaned = cleaned.replace(
|
|
20397
|
+
cleaned = cleaned.replace(/^`+/, "");
|
|
20398
|
+
cleaned = cleaned.replace(/`+$/, "");
|
|
20296
20399
|
cleaned = cleaned.replace(/^(command:|shell:|bash:|zsh:|sh:)\s*/i, "");
|
|
20297
20400
|
const lines = cleaned.split(`
|
|
20298
20401
|
`);
|
|
@@ -21479,12 +21582,15 @@ function showThemeSelector() {
|
|
|
21479
21582
|
themeSelector = null;
|
|
21480
21583
|
}
|
|
21481
21584
|
const currentTheme3 = getTheme2();
|
|
21585
|
+
const themeContainerWidth = 45;
|
|
21586
|
+
const terminalWidth = process.stdout.columns || 80;
|
|
21587
|
+
const themeContainerLeft = Math.max(2, Math.floor((terminalWidth - themeContainerWidth) / 2));
|
|
21482
21588
|
const container = new BoxRenderable(renderer, {
|
|
21483
21589
|
id: "theme-selector-container",
|
|
21484
21590
|
position: "absolute",
|
|
21485
|
-
left:
|
|
21591
|
+
left: themeContainerLeft,
|
|
21486
21592
|
top: 4,
|
|
21487
|
-
width:
|
|
21593
|
+
width: themeContainerWidth,
|
|
21488
21594
|
height: themeNames2.length + 4,
|
|
21489
21595
|
backgroundColor: currentTheme3.colors.backgroundPanel,
|
|
21490
21596
|
border: true,
|
|
@@ -21625,12 +21731,15 @@ function showCommandPalette() {
|
|
|
21625
21731
|
return;
|
|
21626
21732
|
}
|
|
21627
21733
|
const commands = getCommandPaletteOptions();
|
|
21734
|
+
const paletteWidth = 55;
|
|
21735
|
+
const termWidth = process.stdout.columns || 80;
|
|
21736
|
+
const paletteLeft = Math.max(2, Math.floor((termWidth - paletteWidth) / 2));
|
|
21628
21737
|
const container = new BoxRenderable(renderer, {
|
|
21629
21738
|
id: "command-palette-container",
|
|
21630
21739
|
position: "absolute",
|
|
21631
|
-
left:
|
|
21740
|
+
left: paletteLeft,
|
|
21632
21741
|
top: 3,
|
|
21633
|
-
width:
|
|
21742
|
+
width: paletteWidth,
|
|
21634
21743
|
height: Math.min(commands.length + 4, 16),
|
|
21635
21744
|
backgroundColor: "#1e293b",
|
|
21636
21745
|
border: true,
|
|
@@ -22352,7 +22461,8 @@ function cleanCommand(command) {
|
|
|
22352
22461
|
let cleaned = command;
|
|
22353
22462
|
cleaned = cleaned.replace(/^```[\w]*\n?/gm, "");
|
|
22354
22463
|
cleaned = cleaned.replace(/\n?```$/gm, "");
|
|
22355
|
-
cleaned = cleaned.replace(
|
|
22464
|
+
cleaned = cleaned.replace(/^`+/, "");
|
|
22465
|
+
cleaned = cleaned.replace(/`+$/, "");
|
|
22356
22466
|
cleaned = cleaned.replace(/^(command:|shell:|bash:|zsh:|sh:)\s*/i, "");
|
|
22357
22467
|
const lines = cleaned.split(`
|
|
22358
22468
|
`);
|
|
@@ -22933,6 +23043,27 @@ ${colors.bold}OpenRouter Models${colors.reset}
|
|
|
22933
23043
|
}
|
|
22934
23044
|
console.log();
|
|
22935
23045
|
}
|
|
23046
|
+
function validateApiKey(key, provider) {
|
|
23047
|
+
const trimmed = key.trim();
|
|
23048
|
+
if (trimmed.length === 0) {
|
|
23049
|
+
return "API key cannot be empty";
|
|
23050
|
+
}
|
|
23051
|
+
if (trimmed.length < 20) {
|
|
23052
|
+
return "API key seems too short (expected at least 20 characters)";
|
|
23053
|
+
}
|
|
23054
|
+
if (!trimmed.startsWith("sk-")) {
|
|
23055
|
+
const providerName = provider === "opencode-zen" ? "OpenCode Zen" : "OpenRouter";
|
|
23056
|
+
return `${providerName} API keys typically start with 'sk-'`;
|
|
23057
|
+
}
|
|
23058
|
+
if (trimmed.includes(" ")) {
|
|
23059
|
+
return "API key contains spaces - check for copy-paste errors";
|
|
23060
|
+
}
|
|
23061
|
+
if (trimmed.includes(`
|
|
23062
|
+
`) || trimmed.includes("\r")) {
|
|
23063
|
+
return "API key contains newlines - check for copy-paste errors";
|
|
23064
|
+
}
|
|
23065
|
+
return null;
|
|
23066
|
+
}
|
|
22936
23067
|
async function setup() {
|
|
22937
23068
|
const readline = await import("readline");
|
|
22938
23069
|
const rl = readline.createInterface({
|
|
@@ -22963,24 +23094,49 @@ API key already configured. Keep it? [Y/n]: `);
|
|
|
22963
23094
|
const url = provider === "opencode-zen" ? "https://opencode.ai/auth" : "https://openrouter.ai/keys";
|
|
22964
23095
|
console.log(`
|
|
22965
23096
|
Get your API key from: ${colors.cyan}${url}${colors.reset}`);
|
|
22966
|
-
|
|
22967
|
-
|
|
23097
|
+
let validKey = false;
|
|
23098
|
+
while (!validKey) {
|
|
23099
|
+
const newKey = await question("Enter API key: ");
|
|
23100
|
+
if (!newKey.trim()) {
|
|
23101
|
+
console.log(`${colors.yellow}Keeping existing API key${colors.reset}`);
|
|
23102
|
+
break;
|
|
23103
|
+
}
|
|
23104
|
+
const validationError = validateApiKey(newKey, provider);
|
|
23105
|
+
if (validationError) {
|
|
23106
|
+
console.log(`${colors.yellow}Warning: ${validationError}${colors.reset}`);
|
|
23107
|
+
const proceed = await question("Continue anyway? [y/N]: ");
|
|
23108
|
+
if (proceed.toLowerCase() !== "y") {
|
|
23109
|
+
continue;
|
|
23110
|
+
}
|
|
23111
|
+
}
|
|
22968
23112
|
await setApiKey(provider, newKey.trim());
|
|
22969
23113
|
console.log(`${colors.green}\u2713 API key saved${colors.reset}`);
|
|
23114
|
+
validKey = true;
|
|
22970
23115
|
}
|
|
22971
23116
|
}
|
|
22972
23117
|
} else {
|
|
22973
23118
|
const url = provider === "opencode-zen" ? "https://opencode.ai/auth" : "https://openrouter.ai/keys";
|
|
22974
23119
|
console.log(`
|
|
22975
23120
|
Get your API key from: ${colors.cyan}${url}${colors.reset}`);
|
|
22976
|
-
|
|
22977
|
-
|
|
23121
|
+
let validKey = false;
|
|
23122
|
+
while (!validKey) {
|
|
23123
|
+
const newKey = await question("Enter API key: ");
|
|
23124
|
+
if (!newKey.trim()) {
|
|
23125
|
+
console.log(`${colors.red}No API key provided. Exiting.${colors.reset}`);
|
|
23126
|
+
rl.close();
|
|
23127
|
+
process.exit(1);
|
|
23128
|
+
}
|
|
23129
|
+
const validationError = validateApiKey(newKey, provider);
|
|
23130
|
+
if (validationError) {
|
|
23131
|
+
console.log(`${colors.yellow}Warning: ${validationError}${colors.reset}`);
|
|
23132
|
+
const proceed = await question("Continue anyway? [y/N]: ");
|
|
23133
|
+
if (proceed.toLowerCase() !== "y") {
|
|
23134
|
+
continue;
|
|
23135
|
+
}
|
|
23136
|
+
}
|
|
22978
23137
|
await setApiKey(provider, newKey.trim());
|
|
22979
23138
|
console.log(`${colors.green}\u2713 API key saved${colors.reset}`);
|
|
22980
|
-
|
|
22981
|
-
console.log(`${colors.red}No API key provided. Exiting.${colors.reset}`);
|
|
22982
|
-
rl.close();
|
|
22983
|
-
process.exit(1);
|
|
23139
|
+
validKey = true;
|
|
22984
23140
|
}
|
|
22985
23141
|
}
|
|
22986
23142
|
const models = provider === "opencode-zen" ? OPENCODE_ZEN_MODELS : OPENROUTER_MODELS;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@austinthesing/magic-shell",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Natural language to terminal commands with safety features. Supports OpenCode Zen (with free models) and OpenRouter.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|