@austinthesing/magic-shell 0.1.0 → 0.1.1

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.
Files changed (3) hide show
  1. package/dist/cli.js +163 -54
  2. package/dist/index.js +219 -63
  3. package/package.json +1 -1
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 { execSync, spawnSync } from "child_process";
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
- try {
19618
- execSync(`security delete-generic-password -s "${SERVICE_NAME}" -a "${key}" 2>/dev/null`, { encoding: "utf-8" });
19619
- } catch {}
19620
- execSync(`security add-generic-password -s "${SERVICE_NAME}" -a "${key}" -w "${value}"`, { encoding: "utf-8" });
19621
- return true;
19622
- } catch {
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 = execSync(`security find-generic-password -s "${SERVICE_NAME}" -a "${key}" -w 2>/dev/null`, { encoding: "utf-8" });
19629
- return result.trim();
19630
- } catch {
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 = execSync(`secret-tool lookup service "${SERVICE_NAME}" account "${key}" 2>/dev/null`, { encoding: "utf-8" });
19656
- return result.trim() || null;
19657
- } catch {
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
- try {
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
- return true;
19676
- } catch {
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
- try {
19750
- execSync("which security", { encoding: "utf-8", stdio: "pipe" });
19751
- return true;
19752
- } catch {
19753
- return false;
19754
- }
19755
- case "linux":
19756
- try {
19757
- execSync("which secret-tool", { encoding: "utf-8", stdio: "pipe" });
19758
- return true;
19759
- } catch {
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 as execSync2 } from "child_process";
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 = execSync2("uname -r", { encoding: "utf-8" }).toLowerCase();
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 = execSync2(`ps -p ${ppid} -o comm=`, { encoding: "utf-8" }).trim();
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(/^`+|`+$/g, "");
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: "center",
21614
+ left: themeContainerLeft,
21509
21615
  top: 4,
21510
- width: 45,
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: "center",
21765
+ left: paletteLeft,
21657
21766
  top: 3,
21658
- width: 55,
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 { execSync, spawnSync } from "child_process";
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
- try {
61
- execSync(`security delete-generic-password -s "${SERVICE_NAME}" -a "${key}" 2>/dev/null`, { encoding: "utf-8" });
62
- } catch {}
63
- execSync(`security add-generic-password -s "${SERVICE_NAME}" -a "${key}" -w "${value}"`, { encoding: "utf-8" });
64
- return true;
65
- } catch {
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 = execSync(`security find-generic-password -s "${SERVICE_NAME}" -a "${key}" -w 2>/dev/null`, { encoding: "utf-8" });
72
- return result.trim();
73
- } catch {
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 = execSync(`secret-tool lookup service "${SERVICE_NAME}" account "${key}" 2>/dev/null`, { encoding: "utf-8" });
99
- return result.trim() || null;
100
- } catch {
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
- try {
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
- return true;
119
- } catch {
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
- try {
193
- execSync("which security", { encoding: "utf-8", stdio: "pipe" });
194
- return true;
195
- } catch {
196
- return false;
197
- }
198
- case "linux":
199
- try {
200
- execSync("which secret-tool", { encoding: "utf-8", stdio: "pipe" });
201
- return true;
202
- } catch {
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 as execSync2 } from "child_process";
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 = execSync2("uname -r", { encoding: "utf-8" }).toLowerCase();
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 = execSync2(`ps -p ${ppid} -o comm=`, { encoding: "utf-8" }).trim();
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(/^`+|`+$/g, "");
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: "center",
21591
+ left: themeContainerLeft,
21486
21592
  top: 4,
21487
- width: 45,
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: "center",
21740
+ left: paletteLeft,
21632
21741
  top: 3,
21633
- width: 55,
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(/^`+|`+$/g, "");
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
- const newKey = await question("Enter API key: ");
22967
- if (newKey.trim()) {
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
- const newKey = await question("Enter API key: ");
22977
- if (newKey.trim()) {
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
- } else {
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.0",
3
+ "version": "0.1.1",
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",