@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 +1 -1
- package/README.md +19 -29
- package/dist/cli.js +147 -13
- package/dist/index.js +390 -36
- package/dist/tui.js +23423 -0
- package/package.json +4 -3
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
|
|
524
|
-
import { existsSync as
|
|
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 (!
|
|
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 (!
|
|
638
|
+
if (!existsSync4(CONFIG_FILE2)) {
|
|
533
639
|
return { ...DEFAULT_CONFIG2 };
|
|
534
640
|
}
|
|
535
641
|
try {
|
|
536
|
-
const data =
|
|
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 (!
|
|
691
|
+
if (!existsSync4(HISTORY_FILE2)) {
|
|
586
692
|
return [];
|
|
587
693
|
}
|
|
588
694
|
try {
|
|
589
|
-
const data =
|
|
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 =
|
|
609
|
-
CONFIG_FILE2 =
|
|
610
|
-
HISTORY_FILE2 =
|
|
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: "
|
|
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
|
|
683
|
-
import { basename, join as
|
|
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
|
|
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 (!
|
|
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
|
-
|
|
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
|
|
22771
|
-
|
|
22772
|
-
? This help
|
|
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
|
-
-
|
|
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: "
|
|
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}`);
|