@austinthesing/magic-shell 0.1.3 → 0.2.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.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 Magic Shell Contributors
3
+ Copyright (c) 2026 austin-thesing and Magic Shell Contributors
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
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
@@ -83,7 +67,7 @@ export OPENCODE_ZEN_API_KEY="your-key-here"
83
67
 
84
68
  ## Usage
85
69
 
86
- Magic Shell can be invoked using `magic-shell`, `msh`, or `ms`.
70
+ Magic Shell can be invoked using `magic-shell`, `msh`, or `ms`. For TUI mode directly, use `mshell`.
87
71
 
88
72
  ### Basic Commands
89
73
 
@@ -100,8 +84,8 @@ msh -n "delete all node_modules folders"
100
84
 
101
85
  # Launch interactive TUI mode
102
86
  msh -i
103
- # or just
104
- msh
87
+ # or directly with
88
+ mshell
105
89
  ```
106
90
 
107
91
  ### Command Reference
@@ -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
@@ -147,7 +135,7 @@ msh "compress this folder to a zip file" | pbcopy
147
135
 
148
136
  ## Interactive TUI Mode
149
137
 
150
- Launch with `msh` or `msh -i` for a full interactive experience.
138
+ Launch with `mshell` or `msh -i` for a full interactive experience.
151
139
 
152
140
  ### Keyboard Shortcuts
153
141
 
@@ -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",