@austinthesing/magic-shell 0.1.3 → 0.2.0

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 (4) hide show
  1. package/README.md +15 -25
  2. package/dist/cli.js +147 -13
  3. package/dist/index.js +390 -36
  4. package/package.json +1 -1
package/README.md CHANGED
@@ -8,8 +8,10 @@ Magic Shell is an open-source CLI tool that translates plain English (or any nat
8
8
 
9
9
  - **Natural Language Translation**: Describe what you want to do in plain English
10
10
  - **Multiple AI Providers**: OpenCode Zen (with free models!) and OpenRouter
11
+ - **Project Context Aware**: Opt-in detection of package.json scripts, Makefile targets, etc.
11
12
  - **Interactive TUI Mode**: Full-featured terminal interface with themes
12
13
  - **Command Safety Analysis**: Multi-level safety checks before executing commands
14
+ - **Auto Updates**: Automatic update checking with one-command upgrades
13
15
  - **Cross-Platform**: macOS, Linux, and Windows support
14
16
  - **Shell-Aware**: Automatically detects and adapts to your shell (bash, zsh, fish, PowerShell, etc.)
15
17
  - **Secure Credential Storage**: Uses system keychain (macOS Keychain, Linux secret-tool, Windows Credential Manager)
@@ -18,38 +20,20 @@ Magic Shell is an open-source CLI tool that translates plain English (or any nat
18
20
 
19
21
  ## Installation
20
22
 
21
- ### Quick Install (Recommended)
23
+ ### Via Package Manager (Recommended)
22
24
 
23
- **macOS / Linux:**
24
25
  ```bash
25
- curl -fsSL https://raw.githubusercontent.com/austin-thesing/magic-shell/main/install.sh | bash
26
- ```
27
-
28
- **Windows (PowerShell):**
29
- ```powershell
30
- irm https://raw.githubusercontent.com/austin-thesing/magic-shell/main/install.ps1 | iex
31
- ```
32
-
33
- ### Via Package Manager
34
-
35
- ```bash
36
- # Install globally with bun (recommended)
26
+ # bun (recommended)
37
27
  bun add -g @austinthesing/magic-shell
38
28
 
39
- # Or with npm
29
+ # npm
40
30
  npm install -g @austinthesing/magic-shell
41
31
 
42
- # Or with yarn
43
- yarn global add @austinthesing/magic-shell
44
-
45
- # Or with pnpm
32
+ # pnpm
46
33
  pnpm add -g @austinthesing/magic-shell
47
- ```
48
34
 
49
- ### Via Homebrew (macOS/Linux)
50
-
51
- ```bash
52
- brew install austin-thesing/tap/magic-shell
35
+ # yarn
36
+ yarn global add @austinthesing/magic-shell
53
37
  ```
54
38
 
55
39
  ### From Source
@@ -118,6 +102,10 @@ msh
118
102
  | `msh --provider <name>` | Set provider (opencode-zen or openrouter) |
119
103
  | `msh --themes` | List available themes |
120
104
  | `msh --theme <name>` | Set color theme |
105
+ | `msh --repo-context` | Enable project context detection |
106
+ | `msh -r <query>` | Use project context for single query |
107
+ | `msh --version` | Show version |
108
+ | `msh --check-update` | Check for updates |
121
109
  | `msh --help` | Show help |
122
110
 
123
111
  ### Examples
@@ -160,6 +148,7 @@ All shortcuts use the `Ctrl+X` chord (press Ctrl+X, then the key):
160
148
  | `Ctrl+X S` | Switch provider |
161
149
  | `Ctrl+X D` | Toggle dry-run mode |
162
150
  | `Ctrl+X T` | Change theme |
151
+ | `Ctrl+X R` | Toggle project context |
163
152
  | `Ctrl+X H` | Show history |
164
153
  | `Ctrl+X C` | Show config |
165
154
  | `Ctrl+X L` | Clear output |
@@ -257,9 +246,10 @@ Configuration is stored in `~/.magic-shell/config.json`.
257
246
  ```json
258
247
  {
259
248
  "provider": "opencode-zen",
260
- "defaultModel": "grok-code",
249
+ "defaultModel": "gemini-3-flash",
261
250
  "safetyLevel": "moderate",
262
251
  "dryRunByDefault": false,
252
+ "repoContext": false,
263
253
  "theme": "opencode",
264
254
  "blockedCommands": [...],
265
255
  "confirmedDangerousPatterns": [...]
package/dist/cli.js CHANGED
@@ -21116,7 +21116,7 @@ var init_config = __esm(() => {
21116
21116
  provider: "opencode-zen",
21117
21117
  openrouterApiKey: "",
21118
21118
  opencodeZenApiKey: "",
21119
- defaultModel: "gpt-5-nano",
21119
+ defaultModel: "gemini-3-flash",
21120
21120
  safetyLevel: "moderate",
21121
21121
  dryRunByDefault: false,
21122
21122
  blockedCommands: [
@@ -21127,7 +21127,8 @@ var init_config = __esm(() => {
21127
21127
  "chmod -R 777 /",
21128
21128
  "chown -R"
21129
21129
  ],
21130
- confirmedDangerousPatterns: []
21130
+ confirmedDangerousPatterns: [],
21131
+ repoContext: false
21131
21132
  };
21132
21133
  });
21133
21134
 
@@ -21472,6 +21473,112 @@ function getPlatformPaths(platform) {
21472
21473
  }
21473
21474
  var init_shell = () => {};
21474
21475
 
21476
+ // src/lib/repo-context.ts
21477
+ import { existsSync as existsSync6, readFileSync as readFileSync2 } from "fs";
21478
+ import { join as join3 } from "path";
21479
+ function detectRepoContext(cwd) {
21480
+ const context = {
21481
+ type: "unknown"
21482
+ };
21483
+ let detected = false;
21484
+ if (existsSync6(join3(cwd, ".git"))) {
21485
+ context.hasGit = true;
21486
+ detected = true;
21487
+ }
21488
+ if (existsSync6(join3(cwd, "Dockerfile")) || existsSync6(join3(cwd, "docker-compose.yml")) || existsSync6(join3(cwd, "docker-compose.yaml"))) {
21489
+ context.hasDocker = true;
21490
+ detected = true;
21491
+ }
21492
+ const packageJsonPath = join3(cwd, "package.json");
21493
+ if (existsSync6(packageJsonPath)) {
21494
+ detected = true;
21495
+ context.type = "node";
21496
+ try {
21497
+ const packageJson = JSON.parse(readFileSync2(packageJsonPath, "utf-8"));
21498
+ if (existsSync6(join3(cwd, "bun.lockb")) || existsSync6(join3(cwd, "bun.lock"))) {
21499
+ context.packageManager = "bun";
21500
+ } else if (existsSync6(join3(cwd, "pnpm-lock.yaml"))) {
21501
+ context.packageManager = "pnpm";
21502
+ } else if (existsSync6(join3(cwd, "yarn.lock"))) {
21503
+ context.packageManager = "yarn";
21504
+ } else if (existsSync6(join3(cwd, "package-lock.json"))) {
21505
+ context.packageManager = "npm";
21506
+ } else if (packageJson.packageManager) {
21507
+ const pm = packageJson.packageManager.split("@")[0];
21508
+ context.packageManager = pm;
21509
+ }
21510
+ if (packageJson.scripts && typeof packageJson.scripts === "object") {
21511
+ context.scripts = Object.keys(packageJson.scripts);
21512
+ }
21513
+ } catch {}
21514
+ }
21515
+ const makefilePath = join3(cwd, "Makefile");
21516
+ if (existsSync6(makefilePath)) {
21517
+ detected = true;
21518
+ if (context.type === "unknown")
21519
+ context.type = "make";
21520
+ try {
21521
+ const makefile = readFileSync2(makefilePath, "utf-8");
21522
+ const targetRegex = /^([a-zA-Z_][a-zA-Z0-9_-]*)\s*:/gm;
21523
+ const targets = [];
21524
+ let match;
21525
+ while ((match = targetRegex.exec(makefile)) !== null) {
21526
+ if (!match[1].startsWith(".") && !match[1].startsWith("_")) {
21527
+ targets.push(match[1]);
21528
+ }
21529
+ }
21530
+ if (targets.length > 0) {
21531
+ context.makeTargets = [...new Set(targets)];
21532
+ }
21533
+ } catch {}
21534
+ }
21535
+ if (existsSync6(join3(cwd, "Cargo.toml"))) {
21536
+ detected = true;
21537
+ context.type = "rust";
21538
+ context.cargoCommands = ["build", "run", "test", "check", "clippy", "fmt", "doc"];
21539
+ }
21540
+ if (existsSync6(join3(cwd, "pyproject.toml")) || existsSync6(join3(cwd, "setup.py")) || existsSync6(join3(cwd, "requirements.txt"))) {
21541
+ detected = true;
21542
+ if (context.type === "unknown")
21543
+ context.type = "python";
21544
+ }
21545
+ if (existsSync6(join3(cwd, "go.mod"))) {
21546
+ detected = true;
21547
+ if (context.type === "unknown")
21548
+ context.type = "go";
21549
+ }
21550
+ return detected ? context : null;
21551
+ }
21552
+ function formatRepoContext(context) {
21553
+ const lines = [];
21554
+ lines.push(`Project type: ${context.type}`);
21555
+ if (context.packageManager) {
21556
+ lines.push(`Package manager: ${context.packageManager}`);
21557
+ }
21558
+ if (context.scripts && context.scripts.length > 0) {
21559
+ const displayScripts = context.scripts.slice(0, 15);
21560
+ const suffix = context.scripts.length > 15 ? ` (+${context.scripts.length - 15} more)` : "";
21561
+ lines.push(`Available scripts: ${displayScripts.join(", ")}${suffix}`);
21562
+ }
21563
+ if (context.makeTargets && context.makeTargets.length > 0) {
21564
+ const displayTargets = context.makeTargets.slice(0, 15);
21565
+ const suffix = context.makeTargets.length > 15 ? ` (+${context.makeTargets.length - 15} more)` : "";
21566
+ lines.push(`Make targets: ${displayTargets.join(", ")}${suffix}`);
21567
+ }
21568
+ if (context.cargoCommands) {
21569
+ lines.push(`Cargo commands: ${context.cargoCommands.join(", ")}`);
21570
+ }
21571
+ if (context.hasDocker) {
21572
+ lines.push(`Docker: available`);
21573
+ }
21574
+ if (context.hasGit) {
21575
+ lines.push(`Git: initialized`);
21576
+ }
21577
+ return lines.join(`
21578
+ `);
21579
+ }
21580
+ var init_repo_context = () => {};
21581
+
21475
21582
  // src/lib/api.ts
21476
21583
  function getZenApiType(modelId) {
21477
21584
  if (modelId.startsWith("gpt-")) {
@@ -21485,11 +21592,21 @@ function getZenApiType(modelId) {
21485
21592
  }
21486
21593
  return "openai-compatible";
21487
21594
  }
21488
- function buildSystemPrompt(cwd, history, shellInfo) {
21595
+ function buildSystemPrompt(cwd, history, shellInfo, repoContextEnabled) {
21489
21596
  const historyContext = formatHistory(history);
21490
21597
  const platformPaths = getPlatformPaths(shellInfo.platform);
21491
21598
  const shellHints = getShellSyntaxHints(shellInfo.shell);
21492
21599
  const platformName = shellInfo.platform === "macos" ? "macOS" : shellInfo.platform === "windows" ? "Windows" : shellInfo.platform === "linux" ? shellInfo.isWSL ? "Linux (WSL)" : "Linux" : "Unknown";
21600
+ let projectContextSection = "";
21601
+ if (repoContextEnabled) {
21602
+ const repoContext = detectRepoContext(cwd);
21603
+ if (repoContext) {
21604
+ projectContextSection = `
21605
+ Project context:
21606
+ ${formatRepoContext(repoContext)}
21607
+ `;
21608
+ }
21609
+ }
21493
21610
  return `You are a shell command translator. Convert the user's natural language request into a shell command.
21494
21611
 
21495
21612
  Current environment:
@@ -21498,7 +21615,7 @@ Current environment:
21498
21615
  - Working directory: ${cwd}
21499
21616
  - Home directory: ${shellInfo.homeDir}
21500
21617
  ${shellInfo.terminalEmulator ? `- Terminal: ${shellInfo.terminalEmulator}` : ""}
21501
-
21618
+ ${projectContextSection}
21502
21619
  ${shellHints}
21503
21620
 
21504
21621
  Recent command history:
@@ -21509,7 +21626,8 @@ Rules:
21509
21626
  - No explanations, no markdown, no backticks, no code blocks
21510
21627
  - Use the correct syntax for the detected shell (${shellInfo.shell})
21511
21628
  - If the request is unclear, make a reasonable assumption
21512
- - Prefer simple, common commands over complex one-liners
21629
+ - Prefer simple, common commands over complex one-liners${repoContextEnabled ? `
21630
+ - Use project-specific commands when relevant (e.g., use the detected package manager and available scripts)` : ""}
21513
21631
  - Use the command history for context (e.g., "do that again", "undo", "delete the file I just created")
21514
21632
  - If the user asks something that can't be done with a shell command, output a command that prints a helpful message
21515
21633
  - For file operations, prefer safer alternatives when possible
@@ -21781,9 +21899,9 @@ function getShellInfo() {
21781
21899
  }
21782
21900
  return cachedShellInfo;
21783
21901
  }
21784
- async function translateToCommand(apiKey, model, userInput, cwd, history = []) {
21902
+ async function translateToCommand(apiKey, model, userInput, cwd, history = [], repoContextEnabled) {
21785
21903
  const shellInfo = getShellInfo();
21786
- const systemPrompt = buildSystemPrompt(cwd, history, shellInfo);
21904
+ const systemPrompt = buildSystemPrompt(cwd, history, shellInfo, repoContextEnabled);
21787
21905
  let rawCommand;
21788
21906
  if (model.provider === "openrouter") {
21789
21907
  rawCommand = await callOpenRouter(apiKey, model.id, systemPrompt, userInput);
@@ -21809,6 +21927,7 @@ async function translateToCommand(apiKey, model, userInput, cwd, history = []) {
21809
21927
  var DEBUG_API, cachedShellInfo = null;
21810
21928
  var init_api = __esm(() => {
21811
21929
  init_shell();
21930
+ init_repo_context();
21812
21931
  DEBUG_API = process.env.DEBUG_API === "1";
21813
21932
  });
21814
21933
 
@@ -22306,7 +22425,8 @@ function getStatusBarContent() {
22306
22425
  const theme = getTheme();
22307
22426
  const providerName = config.provider === "opencode-zen" ? "OpenCode Zen" : "OpenRouter";
22308
22427
  const safeModeIndicator = dryRunMode ? fg(theme.colors.warning)("[DRY RUN]") : fg(theme.colors.success)("Safe");
22309
- return t`${fg(theme.colors.textMuted)("Provider:")} ${fg(theme.colors.text)(providerName)} ${fg(theme.colors.textMuted)("Model:")} ${fg(theme.colors.text)(currentModel.name)} ${safeModeIndicator}`;
22428
+ const repoContextIndicator = config.repoContext ? fg(theme.colors.info)("[Repo]") : "";
22429
+ return t`${fg(theme.colors.textMuted)("Provider:")} ${fg(theme.colors.text)(providerName)} ${fg(theme.colors.textMuted)("Model:")} ${fg(theme.colors.text)(currentModel.name)} ${safeModeIndicator}${repoContextIndicator ? " " : ""}${repoContextIndicator}`;
22310
22430
  }
22311
22431
  function getHelpBarContent() {
22312
22432
  const theme = getTheme();
@@ -22636,7 +22756,7 @@ async function translateAndProcess(input) {
22636
22756
  }
22637
22757
  const loadingMsg = addSystemMessage("Translating...");
22638
22758
  try {
22639
- const command = await translateToCommand(apiKey, currentModel, input, currentCwd, history);
22759
+ const command = await translateToCommand(apiKey, currentModel, input, currentCwd, history, config.repoContext);
22640
22760
  chatScrollBox.remove(`msg-${loadingMsg.id}`);
22641
22761
  chatMessages = chatMessages.filter((m) => m.id !== loadingMsg.id);
22642
22762
  const safety = analyzeCommand(command, config);
@@ -22786,9 +22906,10 @@ function showHelp() {
22786
22906
  const helpText = `Keyboard Shortcuts (Ctrl+X then...):
22787
22907
  P Command palette M Change model
22788
22908
  S Switch provider D Toggle dry-run
22789
- T Change theme H Show history
22790
- C Show config L Clear chat
22791
- ? This help Q Exit
22909
+ T Change theme R Toggle repo context
22910
+ H Show history L Clear chat
22911
+ C Show config ? This help
22912
+ Q Exit
22792
22913
 
22793
22914
  Other:
22794
22915
  Ctrl+C Exit / Cancel Esc Close palette
@@ -22796,7 +22917,7 @@ Ctrl+C Exit / Cancel Esc Close palette
22796
22917
  Tips:
22797
22918
  - Type naturally: "list all files" -> ls -la
22798
22919
  - Reference history: "do that again", "undo"
22799
- - Free models: gpt-5-nano, grok-code, glm-4.7-free`;
22920
+ - Enable repo context to use project scripts (Ctrl+X R)`;
22800
22921
  addSystemMessage(helpText);
22801
22922
  }
22802
22923
  async function showConfig() {
@@ -22817,6 +22938,7 @@ Shell: ${shellInfo.shell} (${shellInfo.shellPath})
22817
22938
  Platform: ${shellInfo.platform}${shellInfo.isWSL ? " (WSL)" : ""}
22818
22939
  Safety: ${config.safetyLevel}
22819
22940
  Dry-run: ${dryRunMode ? "ON" : "OFF"}
22941
+ Repo context: ${config.repoContext ? "ON" : "OFF"}
22820
22942
  API Key: ${apiKeyStatus}
22821
22943
  History: ${history.length} commands`;
22822
22944
  addSystemMessage(configText);
@@ -23079,6 +23201,18 @@ function getCommandPaletteOptions() {
23079
23201
  addSystemMessage(`Dry-run mode: ${dryRunMode ? "ON" : "OFF"}`);
23080
23202
  }
23081
23203
  },
23204
+ {
23205
+ name: "Toggle Project Context",
23206
+ description: config.repoContext ? "Currently ON (sends script names to AI)" : "Currently OFF",
23207
+ key: "r",
23208
+ chord: "r",
23209
+ action: () => {
23210
+ config.repoContext = !config.repoContext;
23211
+ saveConfig(config);
23212
+ statusBarText.content = getStatusBarContent();
23213
+ addSystemMessage(`Project context: ${config.repoContext ? "ON - AI can see your package.json scripts, Makefile targets, etc." : "OFF"}`);
23214
+ }
23215
+ },
23082
23216
  {
23083
23217
  name: "Show Config",
23084
23218
  description: "View current configuration",
package/dist/index.js CHANGED
@@ -518,22 +518,128 @@ function getPlatformPaths(platform) {
518
518
  }
519
519
  var init_shell = () => {};
520
520
 
521
+ // src/lib/repo-context.ts
522
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
523
+ import { join as join2 } from "path";
524
+ function detectRepoContext(cwd) {
525
+ const context = {
526
+ type: "unknown"
527
+ };
528
+ let detected = false;
529
+ if (existsSync3(join2(cwd, ".git"))) {
530
+ context.hasGit = true;
531
+ detected = true;
532
+ }
533
+ if (existsSync3(join2(cwd, "Dockerfile")) || existsSync3(join2(cwd, "docker-compose.yml")) || existsSync3(join2(cwd, "docker-compose.yaml"))) {
534
+ context.hasDocker = true;
535
+ detected = true;
536
+ }
537
+ const packageJsonPath = join2(cwd, "package.json");
538
+ if (existsSync3(packageJsonPath)) {
539
+ detected = true;
540
+ context.type = "node";
541
+ try {
542
+ const packageJson = JSON.parse(readFileSync2(packageJsonPath, "utf-8"));
543
+ if (existsSync3(join2(cwd, "bun.lockb")) || existsSync3(join2(cwd, "bun.lock"))) {
544
+ context.packageManager = "bun";
545
+ } else if (existsSync3(join2(cwd, "pnpm-lock.yaml"))) {
546
+ context.packageManager = "pnpm";
547
+ } else if (existsSync3(join2(cwd, "yarn.lock"))) {
548
+ context.packageManager = "yarn";
549
+ } else if (existsSync3(join2(cwd, "package-lock.json"))) {
550
+ context.packageManager = "npm";
551
+ } else if (packageJson.packageManager) {
552
+ const pm = packageJson.packageManager.split("@")[0];
553
+ context.packageManager = pm;
554
+ }
555
+ if (packageJson.scripts && typeof packageJson.scripts === "object") {
556
+ context.scripts = Object.keys(packageJson.scripts);
557
+ }
558
+ } catch {}
559
+ }
560
+ const makefilePath = join2(cwd, "Makefile");
561
+ if (existsSync3(makefilePath)) {
562
+ detected = true;
563
+ if (context.type === "unknown")
564
+ context.type = "make";
565
+ try {
566
+ const makefile = readFileSync2(makefilePath, "utf-8");
567
+ const targetRegex = /^([a-zA-Z_][a-zA-Z0-9_-]*)\s*:/gm;
568
+ const targets = [];
569
+ let match;
570
+ while ((match = targetRegex.exec(makefile)) !== null) {
571
+ if (!match[1].startsWith(".") && !match[1].startsWith("_")) {
572
+ targets.push(match[1]);
573
+ }
574
+ }
575
+ if (targets.length > 0) {
576
+ context.makeTargets = [...new Set(targets)];
577
+ }
578
+ } catch {}
579
+ }
580
+ if (existsSync3(join2(cwd, "Cargo.toml"))) {
581
+ detected = true;
582
+ context.type = "rust";
583
+ context.cargoCommands = ["build", "run", "test", "check", "clippy", "fmt", "doc"];
584
+ }
585
+ if (existsSync3(join2(cwd, "pyproject.toml")) || existsSync3(join2(cwd, "setup.py")) || existsSync3(join2(cwd, "requirements.txt"))) {
586
+ detected = true;
587
+ if (context.type === "unknown")
588
+ context.type = "python";
589
+ }
590
+ if (existsSync3(join2(cwd, "go.mod"))) {
591
+ detected = true;
592
+ if (context.type === "unknown")
593
+ context.type = "go";
594
+ }
595
+ return detected ? context : null;
596
+ }
597
+ function formatRepoContext(context) {
598
+ const lines = [];
599
+ lines.push(`Project type: ${context.type}`);
600
+ if (context.packageManager) {
601
+ lines.push(`Package manager: ${context.packageManager}`);
602
+ }
603
+ if (context.scripts && context.scripts.length > 0) {
604
+ const displayScripts = context.scripts.slice(0, 15);
605
+ const suffix = context.scripts.length > 15 ? ` (+${context.scripts.length - 15} more)` : "";
606
+ lines.push(`Available scripts: ${displayScripts.join(", ")}${suffix}`);
607
+ }
608
+ if (context.makeTargets && context.makeTargets.length > 0) {
609
+ const displayTargets = context.makeTargets.slice(0, 15);
610
+ const suffix = context.makeTargets.length > 15 ? ` (+${context.makeTargets.length - 15} more)` : "";
611
+ lines.push(`Make targets: ${displayTargets.join(", ")}${suffix}`);
612
+ }
613
+ if (context.cargoCommands) {
614
+ lines.push(`Cargo commands: ${context.cargoCommands.join(", ")}`);
615
+ }
616
+ if (context.hasDocker) {
617
+ lines.push(`Docker: available`);
618
+ }
619
+ if (context.hasGit) {
620
+ lines.push(`Git: initialized`);
621
+ }
622
+ return lines.join(`
623
+ `);
624
+ }
625
+ var init_repo_context = () => {};
626
+
521
627
  // src/lib/config.ts
522
628
  import { homedir as homedir3 } from "os";
523
- import { join as join2 } from "path";
524
- import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
629
+ import { join as join3 } from "path";
630
+ import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
525
631
  function ensureConfigDir2() {
526
- if (!existsSync3(CONFIG_DIR2)) {
632
+ if (!existsSync4(CONFIG_DIR2)) {
527
633
  mkdirSync2(CONFIG_DIR2, { recursive: true });
528
634
  }
529
635
  }
530
636
  function loadConfig2() {
531
637
  ensureConfigDir2();
532
- if (!existsSync3(CONFIG_FILE2)) {
638
+ if (!existsSync4(CONFIG_FILE2)) {
533
639
  return { ...DEFAULT_CONFIG2 };
534
640
  }
535
641
  try {
536
- const data = readFileSync2(CONFIG_FILE2, "utf-8");
642
+ const data = readFileSync3(CONFIG_FILE2, "utf-8");
537
643
  const loaded = JSON.parse(data);
538
644
  return { ...DEFAULT_CONFIG2, ...loaded };
539
645
  } catch {
@@ -582,11 +688,11 @@ async function setApiKey2(provider, key) {
582
688
  }
583
689
  function loadHistory2() {
584
690
  ensureConfigDir2();
585
- if (!existsSync3(HISTORY_FILE2)) {
691
+ if (!existsSync4(HISTORY_FILE2)) {
586
692
  return [];
587
693
  }
588
694
  try {
589
- const data = readFileSync2(HISTORY_FILE2, "utf-8");
695
+ const data = readFileSync3(HISTORY_FILE2, "utf-8");
590
696
  return JSON.parse(data);
591
697
  } catch {
592
698
  return [];
@@ -605,14 +711,14 @@ function addToHistory(entry) {
605
711
  var CONFIG_DIR2, CONFIG_FILE2, HISTORY_FILE2, KEYCHAIN_OPENROUTER2 = "openrouter-api-key", KEYCHAIN_OPENCODE_ZEN2 = "opencode-zen-api-key", DEFAULT_CONFIG2;
606
712
  var init_config = __esm(() => {
607
713
  init_keychain();
608
- CONFIG_DIR2 = join2(homedir3(), ".magic-shell");
609
- CONFIG_FILE2 = join2(CONFIG_DIR2, "config.json");
610
- HISTORY_FILE2 = join2(CONFIG_DIR2, "history.json");
714
+ CONFIG_DIR2 = join3(homedir3(), ".magic-shell");
715
+ CONFIG_FILE2 = join3(CONFIG_DIR2, "config.json");
716
+ HISTORY_FILE2 = join3(CONFIG_DIR2, "history.json");
611
717
  DEFAULT_CONFIG2 = {
612
718
  provider: "opencode-zen",
613
719
  openrouterApiKey: "",
614
720
  opencodeZenApiKey: "",
615
- defaultModel: "gpt-5-nano",
721
+ defaultModel: "gemini-3-flash",
616
722
  safetyLevel: "moderate",
617
723
  dryRunByDefault: false,
618
724
  blockedCommands: [
@@ -623,7 +729,8 @@ var init_config = __esm(() => {
623
729
  "chmod -R 777 /",
624
730
  "chown -R"
625
731
  ],
626
- confirmedDangerousPatterns: []
732
+ confirmedDangerousPatterns: [],
733
+ repoContext: false
627
734
  };
628
735
  });
629
736
 
@@ -679,8 +786,8 @@ import { EventEmitter as EventEmitter3 } from "events";
679
786
  import { resolve, dirname } from "path";
680
787
  import { fileURLToPath } from "url";
681
788
  import { resolve as resolve2, isAbsolute, parse } from "path";
682
- import { existsSync as existsSync4 } from "fs";
683
- import { basename, join as join3 } from "path";
789
+ import { existsSync as existsSync6 } from "fs";
790
+ import { basename, join as join5 } from "path";
684
791
  import os from "os";
685
792
  import path from "path";
686
793
  import { EventEmitter as EventEmitter4 } from "events";
@@ -2042,7 +2149,7 @@ function getBunfsRootPath() {
2042
2149
  return process.platform === "win32" ? "B:\\~BUN\\root" : "/$bunfs/root";
2043
2150
  }
2044
2151
  function normalizeBunfsPath(fileName) {
2045
- return join3(getBunfsRootPath(), basename(fileName));
2152
+ return join5(getBunfsRootPath(), basename(fileName));
2046
2153
  }
2047
2154
  function isValidDirectoryName(name) {
2048
2155
  if (!name || typeof name !== "string") {
@@ -11901,7 +12008,7 @@ var init_index_93qf6w1k = __esm(async () => {
11901
12008
  worker_path = this.options.workerPath;
11902
12009
  } else {
11903
12010
  worker_path = new URL("./parser.worker.js", import.meta.url).href;
11904
- if (!existsSync4(resolve2(import.meta.dirname, "parser.worker.js"))) {
12011
+ if (!existsSync6(resolve2(import.meta.dirname, "parser.worker.js"))) {
11905
12012
  worker_path = new URL("./parser.worker.ts", import.meta.url).href;
11906
12013
  }
11907
12014
  }
@@ -21486,11 +21593,21 @@ function getZenApiType2(modelId) {
21486
21593
  }
21487
21594
  return "openai-compatible";
21488
21595
  }
21489
- function buildSystemPrompt2(cwd, history, shellInfo) {
21596
+ function buildSystemPrompt2(cwd, history, shellInfo, repoContextEnabled) {
21490
21597
  const historyContext = formatHistory2(history);
21491
21598
  const platformPaths = getPlatformPaths(shellInfo.platform);
21492
21599
  const shellHints = getShellSyntaxHints(shellInfo.shell);
21493
21600
  const platformName = shellInfo.platform === "macos" ? "macOS" : shellInfo.platform === "windows" ? "Windows" : shellInfo.platform === "linux" ? shellInfo.isWSL ? "Linux (WSL)" : "Linux" : "Unknown";
21601
+ let projectContextSection = "";
21602
+ if (repoContextEnabled) {
21603
+ const repoContext = detectRepoContext(cwd);
21604
+ if (repoContext) {
21605
+ projectContextSection = `
21606
+ Project context:
21607
+ ${formatRepoContext(repoContext)}
21608
+ `;
21609
+ }
21610
+ }
21494
21611
  return `You are a shell command translator. Convert the user's natural language request into a shell command.
21495
21612
 
21496
21613
  Current environment:
@@ -21499,7 +21616,7 @@ Current environment:
21499
21616
  - Working directory: ${cwd}
21500
21617
  - Home directory: ${shellInfo.homeDir}
21501
21618
  ${shellInfo.terminalEmulator ? `- Terminal: ${shellInfo.terminalEmulator}` : ""}
21502
-
21619
+ ${projectContextSection}
21503
21620
  ${shellHints}
21504
21621
 
21505
21622
  Recent command history:
@@ -21510,7 +21627,8 @@ Rules:
21510
21627
  - No explanations, no markdown, no backticks, no code blocks
21511
21628
  - Use the correct syntax for the detected shell (${shellInfo.shell})
21512
21629
  - If the request is unclear, make a reasonable assumption
21513
- - Prefer simple, common commands over complex one-liners
21630
+ - Prefer simple, common commands over complex one-liners${repoContextEnabled ? `
21631
+ - Use project-specific commands when relevant (e.g., use the detected package manager and available scripts)` : ""}
21514
21632
  - Use the command history for context (e.g., "do that again", "undo", "delete the file I just created")
21515
21633
  - If the user asks something that can't be done with a shell command, output a command that prints a helpful message
21516
21634
  - For file operations, prefer safer alternatives when possible
@@ -21782,9 +21900,9 @@ function getShellInfo2() {
21782
21900
  }
21783
21901
  return cachedShellInfo2;
21784
21902
  }
21785
- async function translateToCommand2(apiKey, model, userInput, cwd, history = []) {
21903
+ async function translateToCommand2(apiKey, model, userInput, cwd, history = [], repoContextEnabled) {
21786
21904
  const shellInfo = getShellInfo2();
21787
- const systemPrompt = buildSystemPrompt2(cwd, history, shellInfo);
21905
+ const systemPrompt = buildSystemPrompt2(cwd, history, shellInfo, repoContextEnabled);
21788
21906
  let rawCommand;
21789
21907
  if (model.provider === "openrouter") {
21790
21908
  rawCommand = await callOpenRouter2(apiKey, model.id, systemPrompt, userInput);
@@ -21810,6 +21928,7 @@ async function translateToCommand2(apiKey, model, userInput, cwd, history = [])
21810
21928
  var DEBUG_API2, cachedShellInfo2 = null;
21811
21929
  var init_api = __esm(() => {
21812
21930
  init_shell();
21931
+ init_repo_context();
21813
21932
  DEBUG_API2 = process.env.DEBUG_API === "1";
21814
21933
  });
21815
21934
 
@@ -22287,7 +22406,8 @@ function getStatusBarContent() {
22287
22406
  const theme = getTheme2();
22288
22407
  const providerName = config.provider === "opencode-zen" ? "OpenCode Zen" : "OpenRouter";
22289
22408
  const safeModeIndicator = dryRunMode ? fg(theme.colors.warning)("[DRY RUN]") : fg(theme.colors.success)("Safe");
22290
- return t`${fg(theme.colors.textMuted)("Provider:")} ${fg(theme.colors.text)(providerName)} ${fg(theme.colors.textMuted)("Model:")} ${fg(theme.colors.text)(currentModel.name)} ${safeModeIndicator}`;
22409
+ const repoContextIndicator = config.repoContext ? fg(theme.colors.info)("[Repo]") : "";
22410
+ return t`${fg(theme.colors.textMuted)("Provider:")} ${fg(theme.colors.text)(providerName)} ${fg(theme.colors.textMuted)("Model:")} ${fg(theme.colors.text)(currentModel.name)} ${safeModeIndicator}${repoContextIndicator ? " " : ""}${repoContextIndicator}`;
22291
22411
  }
22292
22412
  function getHelpBarContent() {
22293
22413
  const theme = getTheme2();
@@ -22617,7 +22737,7 @@ async function translateAndProcess(input) {
22617
22737
  }
22618
22738
  const loadingMsg = addSystemMessage("Translating...");
22619
22739
  try {
22620
- const command = await translateToCommand2(apiKey, currentModel, input, currentCwd, history);
22740
+ const command = await translateToCommand2(apiKey, currentModel, input, currentCwd, history, config.repoContext);
22621
22741
  chatScrollBox.remove(`msg-${loadingMsg.id}`);
22622
22742
  chatMessages = chatMessages.filter((m) => m.id !== loadingMsg.id);
22623
22743
  const safety = analyzeCommand2(command, config);
@@ -22767,9 +22887,10 @@ function showHelp() {
22767
22887
  const helpText = `Keyboard Shortcuts (Ctrl+X then...):
22768
22888
  P Command palette M Change model
22769
22889
  S Switch provider D Toggle dry-run
22770
- T Change theme H Show history
22771
- C Show config L Clear chat
22772
- ? This help Q Exit
22890
+ T Change theme R Toggle repo context
22891
+ H Show history L Clear chat
22892
+ C Show config ? This help
22893
+ Q Exit
22773
22894
 
22774
22895
  Other:
22775
22896
  Ctrl+C Exit / Cancel Esc Close palette
@@ -22777,7 +22898,7 @@ Ctrl+C Exit / Cancel Esc Close palette
22777
22898
  Tips:
22778
22899
  - Type naturally: "list all files" -> ls -la
22779
22900
  - Reference history: "do that again", "undo"
22780
- - Free models: gpt-5-nano, grok-code, glm-4.7-free`;
22901
+ - Enable repo context to use project scripts (Ctrl+X R)`;
22781
22902
  addSystemMessage(helpText);
22782
22903
  }
22783
22904
  async function showConfig() {
@@ -22798,6 +22919,7 @@ Shell: ${shellInfo.shell} (${shellInfo.shellPath})
22798
22919
  Platform: ${shellInfo.platform}${shellInfo.isWSL ? " (WSL)" : ""}
22799
22920
  Safety: ${config.safetyLevel}
22800
22921
  Dry-run: ${dryRunMode ? "ON" : "OFF"}
22922
+ Repo context: ${config.repoContext ? "ON" : "OFF"}
22801
22923
  API Key: ${apiKeyStatus}
22802
22924
  History: ${history.length} commands`;
22803
22925
  addSystemMessage(configText);
@@ -23057,6 +23179,18 @@ function getCommandPaletteOptions() {
23057
23179
  addSystemMessage(`Dry-run mode: ${dryRunMode ? "ON" : "OFF"}`);
23058
23180
  }
23059
23181
  },
23182
+ {
23183
+ name: "Toggle Project Context",
23184
+ description: config.repoContext ? "Currently ON (sends script names to AI)" : "Currently OFF",
23185
+ key: "r",
23186
+ chord: "r",
23187
+ action: () => {
23188
+ config.repoContext = !config.repoContext;
23189
+ saveConfig2(config);
23190
+ statusBarText.content = getStatusBarContent();
23191
+ addSystemMessage(`Project context: ${config.repoContext ? "ON - AI can see your package.json scripts, Makefile targets, etc." : "OFF"}`);
23192
+ }
23193
+ },
23060
23194
  {
23061
23195
  name: "Show Config",
23062
23196
  description: "View current configuration",
@@ -23582,7 +23716,7 @@ var DEFAULT_CONFIG = {
23582
23716
  provider: "opencode-zen",
23583
23717
  openrouterApiKey: "",
23584
23718
  opencodeZenApiKey: "",
23585
- defaultModel: "gpt-5-nano",
23719
+ defaultModel: "gemini-3-flash",
23586
23720
  safetyLevel: "moderate",
23587
23721
  dryRunByDefault: false,
23588
23722
  blockedCommands: [
@@ -23593,7 +23727,8 @@ var DEFAULT_CONFIG = {
23593
23727
  "chmod -R 777 /",
23594
23728
  "chown -R"
23595
23729
  ],
23596
- confirmedDangerousPatterns: []
23730
+ confirmedDangerousPatterns: [],
23731
+ repoContext: false
23597
23732
  };
23598
23733
  function ensureConfigDir() {
23599
23734
  if (!existsSync(CONFIG_DIR)) {
@@ -23794,6 +23929,7 @@ function getSeverityMessage(severity) {
23794
23929
 
23795
23930
  // src/lib/api.ts
23796
23931
  init_shell();
23932
+ init_repo_context();
23797
23933
  function getZenApiType(modelId) {
23798
23934
  if (modelId.startsWith("gpt-")) {
23799
23935
  return "openai-responses";
@@ -23806,11 +23942,21 @@ function getZenApiType(modelId) {
23806
23942
  }
23807
23943
  return "openai-compatible";
23808
23944
  }
23809
- function buildSystemPrompt(cwd, history, shellInfo) {
23945
+ function buildSystemPrompt(cwd, history, shellInfo, repoContextEnabled) {
23810
23946
  const historyContext = formatHistory(history);
23811
23947
  const platformPaths = getPlatformPaths(shellInfo.platform);
23812
23948
  const shellHints = getShellSyntaxHints(shellInfo.shell);
23813
23949
  const platformName = shellInfo.platform === "macos" ? "macOS" : shellInfo.platform === "windows" ? "Windows" : shellInfo.platform === "linux" ? shellInfo.isWSL ? "Linux (WSL)" : "Linux" : "Unknown";
23950
+ let projectContextSection = "";
23951
+ if (repoContextEnabled) {
23952
+ const repoContext = detectRepoContext(cwd);
23953
+ if (repoContext) {
23954
+ projectContextSection = `
23955
+ Project context:
23956
+ ${formatRepoContext(repoContext)}
23957
+ `;
23958
+ }
23959
+ }
23814
23960
  return `You are a shell command translator. Convert the user's natural language request into a shell command.
23815
23961
 
23816
23962
  Current environment:
@@ -23819,7 +23965,7 @@ Current environment:
23819
23965
  - Working directory: ${cwd}
23820
23966
  - Home directory: ${shellInfo.homeDir}
23821
23967
  ${shellInfo.terminalEmulator ? `- Terminal: ${shellInfo.terminalEmulator}` : ""}
23822
-
23968
+ ${projectContextSection}
23823
23969
  ${shellHints}
23824
23970
 
23825
23971
  Recent command history:
@@ -23830,7 +23976,8 @@ Rules:
23830
23976
  - No explanations, no markdown, no backticks, no code blocks
23831
23977
  - Use the correct syntax for the detected shell (${shellInfo.shell})
23832
23978
  - If the request is unclear, make a reasonable assumption
23833
- - Prefer simple, common commands over complex one-liners
23979
+ - Prefer simple, common commands over complex one-liners${repoContextEnabled ? `
23980
+ - Use project-specific commands when relevant (e.g., use the detected package manager and available scripts)` : ""}
23834
23981
  - Use the command history for context (e.g., "do that again", "undo", "delete the file I just created")
23835
23982
  - If the user asks something that can't be done with a shell command, output a command that prints a helpful message
23836
23983
  - For file operations, prefer safer alternatives when possible
@@ -24104,9 +24251,9 @@ function getShellInfo() {
24104
24251
  }
24105
24252
  return cachedShellInfo;
24106
24253
  }
24107
- async function translateToCommand(apiKey, model, userInput, cwd, history = []) {
24254
+ async function translateToCommand(apiKey, model, userInput, cwd, history = [], repoContextEnabled) {
24108
24255
  const shellInfo = getShellInfo();
24109
- const systemPrompt = buildSystemPrompt(cwd, history, shellInfo);
24256
+ const systemPrompt = buildSystemPrompt(cwd, history, shellInfo, repoContextEnabled);
24110
24257
  let rawCommand;
24111
24258
  if (model.provider === "openrouter") {
24112
24259
  rawCommand = await callOpenRouter(apiKey, model.id, systemPrompt, userInput);
@@ -24347,6 +24494,128 @@ function getAnsiColors() {
24347
24494
  }
24348
24495
  loadTheme();
24349
24496
 
24497
+ // src/lib/update-checker.ts
24498
+ import { homedir as homedir4 } from "os";
24499
+ import { join as join4 } from "path";
24500
+ import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
24501
+ var __dirname = "/Users/austin/code/dxd/magic-shell/src/lib";
24502
+ var PACKAGE_NAME = "@austinthesing/magic-shell";
24503
+ var NPM_REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
24504
+ var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
24505
+ var CONFIG_DIR3 = join4(homedir4(), ".magic-shell");
24506
+ var UPDATE_CHECK_FILE = join4(CONFIG_DIR3, ".update-check");
24507
+ function ensureConfigDir3() {
24508
+ if (!existsSync5(CONFIG_DIR3)) {
24509
+ mkdirSync3(CONFIG_DIR3, { recursive: true });
24510
+ }
24511
+ }
24512
+ function loadUpdateState() {
24513
+ ensureConfigDir3();
24514
+ try {
24515
+ if (existsSync5(UPDATE_CHECK_FILE)) {
24516
+ return JSON.parse(readFileSync4(UPDATE_CHECK_FILE, "utf-8"));
24517
+ }
24518
+ } catch {}
24519
+ return { lastCheck: 0, latestVersion: null, dismissed: null };
24520
+ }
24521
+ function saveUpdateState(state) {
24522
+ ensureConfigDir3();
24523
+ try {
24524
+ writeFileSync3(UPDATE_CHECK_FILE, JSON.stringify(state));
24525
+ } catch {}
24526
+ }
24527
+ function getCurrentVersion() {
24528
+ try {
24529
+ const packagePaths = [
24530
+ join4(__dirname, "../../package.json"),
24531
+ join4(__dirname, "../../../package.json"),
24532
+ join4(process.cwd(), "package.json")
24533
+ ];
24534
+ for (const path of packagePaths) {
24535
+ if (existsSync5(path)) {
24536
+ const pkg = JSON.parse(readFileSync4(path, "utf-8"));
24537
+ if (pkg.name === PACKAGE_NAME || pkg.name === "magic-shell") {
24538
+ return pkg.version;
24539
+ }
24540
+ }
24541
+ }
24542
+ } catch {}
24543
+ return "0.0.0";
24544
+ }
24545
+ function compareVersions(a, b) {
24546
+ const partsA = a.split(".").map(Number);
24547
+ const partsB = b.split(".").map(Number);
24548
+ for (let i = 0;i < Math.max(partsA.length, partsB.length); i++) {
24549
+ const numA = partsA[i] || 0;
24550
+ const numB = partsB[i] || 0;
24551
+ if (numA > numB)
24552
+ return 1;
24553
+ if (numA < numB)
24554
+ return -1;
24555
+ }
24556
+ return 0;
24557
+ }
24558
+ async function fetchLatestVersion() {
24559
+ try {
24560
+ const controller = new AbortController;
24561
+ const timeout = setTimeout(() => controller.abort(), 3000);
24562
+ const response = await fetch(NPM_REGISTRY_URL, {
24563
+ signal: controller.signal,
24564
+ headers: { Accept: "application/json" }
24565
+ });
24566
+ clearTimeout(timeout);
24567
+ if (!response.ok)
24568
+ return null;
24569
+ const data = await response.json();
24570
+ return data.version || null;
24571
+ } catch {
24572
+ return null;
24573
+ }
24574
+ }
24575
+ async function checkForUpdates() {
24576
+ const state = loadUpdateState();
24577
+ const currentVersion = getCurrentVersion();
24578
+ const now = Date.now();
24579
+ if (now - state.lastCheck < CHECK_INTERVAL_MS) {
24580
+ if (state.latestVersion && compareVersions(state.latestVersion, currentVersion) > 0) {
24581
+ if (state.dismissed === state.latestVersion) {
24582
+ return null;
24583
+ }
24584
+ return {
24585
+ hasUpdate: true,
24586
+ currentVersion,
24587
+ latestVersion: state.latestVersion,
24588
+ updateCommand: `bun update -g ${PACKAGE_NAME}`
24589
+ };
24590
+ }
24591
+ return null;
24592
+ }
24593
+ const latestVersion = await fetchLatestVersion();
24594
+ state.lastCheck = now;
24595
+ if (latestVersion) {
24596
+ state.latestVersion = latestVersion;
24597
+ }
24598
+ saveUpdateState(state);
24599
+ if (latestVersion && compareVersions(latestVersion, currentVersion) > 0) {
24600
+ if (state.dismissed === latestVersion) {
24601
+ return null;
24602
+ }
24603
+ return {
24604
+ hasUpdate: true,
24605
+ currentVersion,
24606
+ latestVersion,
24607
+ updateCommand: `bun update -g ${PACKAGE_NAME}`
24608
+ };
24609
+ }
24610
+ return null;
24611
+ }
24612
+ async function forceCheckForUpdates() {
24613
+ const state = loadUpdateState();
24614
+ state.lastCheck = 0;
24615
+ saveUpdateState(state);
24616
+ return checkForUpdates();
24617
+ }
24618
+
24350
24619
  // src/index.ts
24351
24620
  loadTheme();
24352
24621
  var getColors = () => {
@@ -24378,6 +24647,10 @@ ${colors.bold}USAGE${colors.reset}
24378
24647
  msh --provider <name> Set provider (opencode-zen or openrouter)
24379
24648
  msh --themes List available themes
24380
24649
  msh --theme <name> Set color theme
24650
+ msh --repo-context Enable project context detection
24651
+ msh --no-repo-context Disable project context detection
24652
+ msh --version Show version
24653
+ msh --check-update Check for updates
24381
24654
  msh --help Show this help
24382
24655
 
24383
24656
  ${colors.bold}EXAMPLES${colors.reset}
@@ -24390,6 +24663,9 @@ ${colors.bold}EXAMPLES${colors.reset}
24390
24663
  ${colors.dim}# Check what would run${colors.reset}
24391
24664
  msh -n "delete all log files"
24392
24665
 
24666
+ ${colors.dim}# Use project context (knows your npm scripts, etc)${colors.reset}
24667
+ msh --repo-context "run the dev server"
24668
+
24393
24669
  ${colors.dim}# Pipe to clipboard (macOS)${colors.reset}
24394
24670
  msh "find large files" | pbcopy
24395
24671
 
@@ -24593,6 +24869,27 @@ function executeCommand(command) {
24593
24869
  });
24594
24870
  });
24595
24871
  }
24872
+ function createSpinner(message) {
24873
+ const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
24874
+ let i = 0;
24875
+ const isTTY = process.stderr.isTTY;
24876
+ const interval = isTTY ? setInterval(() => {
24877
+ process.stderr.write(`\r${colors.primary}${frames[i]}${colors.reset} ${colors.dim}${message}${colors.reset}`);
24878
+ i = (i + 1) % frames.length;
24879
+ }, 80) : null;
24880
+ if (!isTTY) {
24881
+ process.stderr.write(`${colors.dim}${message}...${colors.reset}
24882
+ `);
24883
+ }
24884
+ return {
24885
+ stop: () => {
24886
+ if (interval) {
24887
+ clearInterval(interval);
24888
+ process.stderr.write("\r\x1B[K");
24889
+ }
24890
+ }
24891
+ };
24892
+ }
24596
24893
  async function translate(query, options) {
24597
24894
  const config2 = loadConfig();
24598
24895
  const apiKey = await getApiKey(config2.provider);
@@ -24604,12 +24901,18 @@ async function translate(query, options) {
24604
24901
  const model = ALL_MODELS.find((m) => m.id === config2.defaultModel) || (config2.provider === "opencode-zen" ? OPENCODE_ZEN_MODELS[0] : OPENROUTER_MODELS[0]);
24605
24902
  const history2 = loadHistory();
24606
24903
  const cwd = getCwd2();
24904
+ const useRepoContext = options.repoContext ?? config2.repoContext ?? false;
24905
+ const spinner = createSpinner(`Translating with ${model.name}`);
24607
24906
  try {
24608
- const command = await translateToCommand(apiKey, model, query, cwd, history2);
24907
+ const command = await translateToCommand(apiKey, model, query, cwd, history2, useRepoContext);
24908
+ spinner.stop();
24609
24909
  if (options.dryRun) {
24610
24910
  const safety = analyzeCommand(command, config2);
24611
24911
  console.log(`${colors.dim}Query:${colors.reset} ${query}`);
24612
24912
  console.log(`${colors.dim}Model:${colors.reset} ${model.name}`);
24913
+ if (useRepoContext) {
24914
+ console.log(`${colors.dim}Project context:${colors.reset} enabled`);
24915
+ }
24613
24916
  console.log();
24614
24917
  console.log(`${colors.bold}Command:${colors.reset} ${command}`);
24615
24918
  if (safety.isDangerous) {
@@ -24634,27 +24937,58 @@ async function translate(query, options) {
24634
24937
  console.log(command);
24635
24938
  }
24636
24939
  } catch (error) {
24940
+ spinner.stop();
24637
24941
  const message = error instanceof Error ? error.message : String(error);
24638
24942
  console.error(`${colors.red}Error: ${message}${colors.reset}`);
24639
24943
  process.exit(1);
24640
24944
  }
24641
24945
  }
24946
+ async function showUpdateNotification() {
24947
+ try {
24948
+ const update = await checkForUpdates();
24949
+ if (update?.hasUpdate) {
24950
+ console.error(`${colors.cyan}\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510${colors.reset}`);
24951
+ console.error(`${colors.cyan}\u2502${colors.reset} ${colors.bold}Update available!${colors.reset} ${colors.dim}${update.currentVersion}${colors.reset} \u2192 ${colors.green}${update.latestVersion}${colors.reset} ${colors.cyan}\u2502${colors.reset}`);
24952
+ console.error(`${colors.cyan}\u2502${colors.reset} Run: ${colors.yellow}${update.updateCommand}${colors.reset} ${colors.cyan}\u2502${colors.reset}`);
24953
+ console.error(`${colors.cyan}\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518${colors.reset}`);
24954
+ console.error();
24955
+ }
24956
+ } catch {}
24957
+ }
24642
24958
  async function main3() {
24643
24959
  const args = process.argv.slice(2);
24960
+ const updatePromise = args.length > 0 && !args[0].startsWith("-i") ? showUpdateNotification() : Promise.resolve();
24644
24961
  if (args.length === 0 || args[0] === "-i" || args[0] === "--interactive") {
24645
24962
  const { default: runTui } = await init_cli().then(() => exports_cli);
24646
24963
  await runTui();
24647
24964
  return;
24648
24965
  }
24649
24966
  if (args[0] === "--help" || args[0] === "-h") {
24967
+ await updatePromise;
24650
24968
  printHelp();
24651
24969
  return;
24652
24970
  }
24971
+ if (args[0] === "--version" || args[0] === "-v") {
24972
+ console.log(`magic-shell v${getCurrentVersion()}`);
24973
+ return;
24974
+ }
24975
+ if (args[0] === "--check-update") {
24976
+ const update = await forceCheckForUpdates();
24977
+ if (update?.hasUpdate) {
24978
+ console.log(`${colors.green}Update available!${colors.reset} ${update.currentVersion} \u2192 ${update.latestVersion}`);
24979
+ console.log(`Run: ${colors.cyan}${update.updateCommand}${colors.reset}`);
24980
+ } else {
24981
+ console.log(`${colors.green}\u2713 You're running the latest version (${getCurrentVersion()})${colors.reset}`);
24982
+ }
24983
+ return;
24984
+ }
24653
24985
  if (args[0] === "--setup") {
24986
+ await updatePromise;
24654
24987
  await setup();
24655
24988
  return;
24656
24989
  }
24657
24990
  if (args[0] === "--models") {
24991
+ await updatePromise;
24658
24992
  printModels();
24659
24993
  return;
24660
24994
  }
@@ -24720,8 +25054,24 @@ ${colors.bold}Available Themes${colors.reset}
24720
25054
  console.log(`${colors.success}\u2713 Theme set to ${themeName}${colors.reset}`);
24721
25055
  return;
24722
25056
  }
25057
+ if (args[0] === "--repo-context") {
25058
+ const config2 = loadConfig();
25059
+ config2.repoContext = true;
25060
+ saveConfig(config2);
25061
+ console.log(`${colors.success}\u2713 Project context enabled${colors.reset}`);
25062
+ console.log(`${colors.dim}Magic Shell will now detect package.json scripts, Makefile targets, etc.${colors.reset}`);
25063
+ return;
25064
+ }
25065
+ if (args[0] === "--no-repo-context") {
25066
+ const config2 = loadConfig();
25067
+ config2.repoContext = false;
25068
+ saveConfig(config2);
25069
+ console.log(`${colors.success}\u2713 Project context disabled${colors.reset}`);
25070
+ return;
25071
+ }
24723
25072
  let execute = false;
24724
25073
  let dryRun = false;
25074
+ let repoContext = undefined;
24725
25075
  let queryParts = [];
24726
25076
  for (let i = 0;i < args.length; i++) {
24727
25077
  const arg = args[i];
@@ -24729,6 +25079,10 @@ ${colors.bold}Available Themes${colors.reset}
24729
25079
  execute = true;
24730
25080
  } else if (arg === "-n" || arg === "--dry-run") {
24731
25081
  dryRun = true;
25082
+ } else if (arg === "-r" || arg === "--repo-context") {
25083
+ repoContext = true;
25084
+ } else if (arg === "--no-repo-context") {
25085
+ repoContext = false;
24732
25086
  } else if (!arg.startsWith("-")) {
24733
25087
  queryParts.push(arg);
24734
25088
  }
@@ -24739,7 +25093,7 @@ ${colors.bold}Available Themes${colors.reset}
24739
25093
  console.error(`Usage: msh "your query here"`);
24740
25094
  process.exit(1);
24741
25095
  }
24742
- await translate(query, { execute, dryRun });
25096
+ await translate(query, { execute, dryRun, repoContext });
24743
25097
  }
24744
25098
  main3().catch((error) => {
24745
25099
  console.error(`${colors.red}Error: ${error.message}${colors.reset}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@austinthesing/magic-shell",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
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",