@laabroms/alias-cli 0.1.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.
- package/LICENSE +21 -0
- package/README.md +173 -0
- package/dist/cli.js +492 -0
- package/package.json +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lucas Aabroms
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# Alias CLI
|
|
2
|
+
|
|
3
|
+
```
|
|
4
|
+
█████╗ ██╗ ██╗ █████╗ ███████╗
|
|
5
|
+
██╔══██╗██║ ██║██╔══██╗██╔════╝
|
|
6
|
+
███████║██║ ██║███████║███████╗
|
|
7
|
+
██╔══██║██║ ██║██╔══██║╚════██║
|
|
8
|
+
██║ ██║███████╗██║██║ ██║███████║
|
|
9
|
+
╚═╝ ╚═╝╚══════╝╚═╝╚═╝ ╚═╝╚══════╝
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
> Interactive terminal UI for managing shell aliases
|
|
13
|
+
|
|
14
|
+
[](https://www.npmjs.com/package/@laabroms/alias-cli)
|
|
15
|
+
[](https://opensource.org/licenses/MIT)
|
|
16
|
+
|
|
17
|
+
Built with [Ink](https://github.com/vadimdemedes/ink) — React for CLIs.
|
|
18
|
+
|
|
19
|
+
<!--  -->
|
|
20
|
+
|
|
21
|
+
## Features
|
|
22
|
+
|
|
23
|
+
- ✨ **Interactive TUI** — keyboard-driven, no mouse needed
|
|
24
|
+
- 📝 **Add/Edit/Delete** aliases with clean modal dialogs
|
|
25
|
+
- 🔍 **Real-time search** — filter aliases as you type with arrow key navigation
|
|
26
|
+
- 🔍 **Live preview** — see your alias before saving
|
|
27
|
+
- 💾 **Auto-backup** — creates `.zshrc.backup` before changes
|
|
28
|
+
- 🎯 **Visual focus** — clearly see which field you're editing
|
|
29
|
+
- 🎨 **Color-coded UI** — easy to scan and navigate
|
|
30
|
+
- 📦 **Zero config** — works with `.zshrc` or `.bashrc` out of the box
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
### Quick Install (bash)
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
curl -fsSL https://raw.githubusercontent.com/laabroms/alias-cli/main/install.sh | bash
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### npm (global)
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npm install -g @laabroms/alias-cli
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### npx (no install)
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npx @laabroms/alias-cli
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### From source
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
git clone https://github.com/laabroms/alias-cli.git
|
|
56
|
+
cd alias-cli
|
|
57
|
+
npm install
|
|
58
|
+
npm run dev
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Usage
|
|
62
|
+
|
|
63
|
+
Run the CLI:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
alias-cli
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Keyboard Shortcuts
|
|
70
|
+
|
|
71
|
+
**Main Screen:**
|
|
72
|
+
- `↑/↓` — Navigate aliases
|
|
73
|
+
- `a` — Add new alias
|
|
74
|
+
- `e` — Edit selected alias
|
|
75
|
+
- `d` or `Del` — Delete selected alias
|
|
76
|
+
- `/` — Search/filter aliases
|
|
77
|
+
- `c` — Clear search filter
|
|
78
|
+
- `q` — Quit
|
|
79
|
+
|
|
80
|
+
**Search Mode:**
|
|
81
|
+
- Type to filter aliases in real-time
|
|
82
|
+
- `↑/↓` — Navigate filtered results
|
|
83
|
+
- `Enter` — Edit selected alias
|
|
84
|
+
- `Esc` — Close search
|
|
85
|
+
|
|
86
|
+
**Add/Edit Modal:**
|
|
87
|
+
- `Tab` — Switch between Name and Command fields
|
|
88
|
+
- `Enter` — Save
|
|
89
|
+
- `Esc` — Cancel
|
|
90
|
+
|
|
91
|
+
**Delete Confirmation:**
|
|
92
|
+
- `y` or `Enter` — Confirm delete
|
|
93
|
+
- `n` or `Esc` — Cancel
|
|
94
|
+
|
|
95
|
+
## Example
|
|
96
|
+
|
|
97
|
+
Create a quick commit alias:
|
|
98
|
+
|
|
99
|
+
1. Run `alias-cli`
|
|
100
|
+
2. Press `a` to add
|
|
101
|
+
3. **Name:** `gc`
|
|
102
|
+
4. **Command:** `git add . && git commit -m`
|
|
103
|
+
5. Press `Enter` to save
|
|
104
|
+
6. Press `q` to quit
|
|
105
|
+
7. Paste the command (auto-copied to clipboard!) and press Enter
|
|
106
|
+
8. Use it: `gc "feat: add new feature"`
|
|
107
|
+
|
|
108
|
+
## How It Works
|
|
109
|
+
|
|
110
|
+
1. **Loads** aliases from your `.zshrc` or `.bashrc`
|
|
111
|
+
2. **Displays** them in an interactive list
|
|
112
|
+
3. **Saves** changes back to your shell config
|
|
113
|
+
4. **Backups** the original file before writing
|
|
114
|
+
|
|
115
|
+
All aliases are written to the end of your shell config with a comment:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
# Aliases managed by alias-cli
|
|
119
|
+
alias gs="git status"
|
|
120
|
+
alias gc="git add . && git commit -m"
|
|
121
|
+
alias gp="git push origin main"
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
After making changes, the CLI automatically copies the `source` command to your clipboard — just paste and run!
|
|
125
|
+
|
|
126
|
+
## Requirements
|
|
127
|
+
|
|
128
|
+
- Node.js >= 18.0.0
|
|
129
|
+
- Terminal with ANSI color support
|
|
130
|
+
|
|
131
|
+
## Development
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
# Clone the repo
|
|
135
|
+
git clone https://github.com/laabroms/alias-cli.git
|
|
136
|
+
cd alias-cli
|
|
137
|
+
|
|
138
|
+
# Install dependencies
|
|
139
|
+
npm install
|
|
140
|
+
|
|
141
|
+
# Run in dev mode
|
|
142
|
+
npm run dev
|
|
143
|
+
|
|
144
|
+
# Build for production
|
|
145
|
+
npm run build
|
|
146
|
+
|
|
147
|
+
# Type check
|
|
148
|
+
npm run typecheck
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Tech Stack
|
|
152
|
+
|
|
153
|
+
- **Ink** — React renderer for CLIs
|
|
154
|
+
- **ink-text-input** — Text input component
|
|
155
|
+
- **TypeScript** — Type safety
|
|
156
|
+
- **tsup** — Fast bundler
|
|
157
|
+
- **tsx** — TypeScript execution
|
|
158
|
+
|
|
159
|
+
## Future Ideas
|
|
160
|
+
|
|
161
|
+
- [ ] Import/export alias sets
|
|
162
|
+
- [ ] Syntax highlighting for commands
|
|
163
|
+
- [ ] Multi-select delete
|
|
164
|
+
- [ ] Alias categories/tags
|
|
165
|
+
- [ ] Support for `.bash_aliases` and other config files
|
|
166
|
+
|
|
167
|
+
## Contributing
|
|
168
|
+
|
|
169
|
+
PRs welcome! Please open an issue first to discuss what you'd like to change.
|
|
170
|
+
|
|
171
|
+
## License
|
|
172
|
+
|
|
173
|
+
MIT © [Lucas Aabroms](https://github.com/laabroms)
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
3
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
4
|
+
}) : x)(function(x) {
|
|
5
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
6
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
// src/cli.tsx
|
|
10
|
+
import React9 from "react";
|
|
11
|
+
import { render } from "ink";
|
|
12
|
+
|
|
13
|
+
// src/App.tsx
|
|
14
|
+
import React8, { useState as useState4, useEffect, useCallback } from "react";
|
|
15
|
+
import { Box as Box8, Text as Text8, useInput as useInput5, useApp } from "ink";
|
|
16
|
+
import os2 from "os";
|
|
17
|
+
|
|
18
|
+
// src/aliases.ts
|
|
19
|
+
import fs from "fs";
|
|
20
|
+
import path from "path";
|
|
21
|
+
import os from "os";
|
|
22
|
+
var SHELL_CONFIG_FILES = [".zshrc", ".bashrc"];
|
|
23
|
+
function getShellConfigPath() {
|
|
24
|
+
const homeDir = os.homedir();
|
|
25
|
+
for (const file of SHELL_CONFIG_FILES) {
|
|
26
|
+
const fullPath = path.join(homeDir, file);
|
|
27
|
+
if (fs.existsSync(fullPath)) {
|
|
28
|
+
return fullPath;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return path.join(homeDir, ".zshrc");
|
|
32
|
+
}
|
|
33
|
+
function parseAliases(content) {
|
|
34
|
+
const aliases = [];
|
|
35
|
+
const lines = content.split("\n");
|
|
36
|
+
for (const line of lines) {
|
|
37
|
+
const trimmed = line.trim();
|
|
38
|
+
const match = trimmed.match(/^alias\s+([^=]+)=['"]?(.+?)['"]?$/);
|
|
39
|
+
if (match) {
|
|
40
|
+
const [, name, command] = match;
|
|
41
|
+
aliases.push({
|
|
42
|
+
name: name.trim(),
|
|
43
|
+
command: command.trim().replace(/^['"]|['"]$/g, "")
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return aliases;
|
|
48
|
+
}
|
|
49
|
+
function serializeAliases(aliases) {
|
|
50
|
+
return aliases.map((a) => `alias ${a.name}="${a.command}"`).join("\n");
|
|
51
|
+
}
|
|
52
|
+
function loadAliases() {
|
|
53
|
+
try {
|
|
54
|
+
const configPath = getShellConfigPath();
|
|
55
|
+
const content = fs.readFileSync(configPath, "utf-8");
|
|
56
|
+
return parseAliases(content);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error("Failed to load aliases:", error);
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function saveAliases(aliases) {
|
|
63
|
+
try {
|
|
64
|
+
const configPath = getShellConfigPath();
|
|
65
|
+
let content = "";
|
|
66
|
+
if (fs.existsSync(configPath)) {
|
|
67
|
+
content = fs.readFileSync(configPath, "utf-8");
|
|
68
|
+
}
|
|
69
|
+
const lines = content.split("\n");
|
|
70
|
+
const nonAliasLines = lines.filter((line) => {
|
|
71
|
+
const trimmed = line.trim();
|
|
72
|
+
return !trimmed.startsWith("alias ");
|
|
73
|
+
});
|
|
74
|
+
const newContent = [
|
|
75
|
+
...nonAliasLines,
|
|
76
|
+
"",
|
|
77
|
+
"# Aliases managed by alias-cli",
|
|
78
|
+
serializeAliases(aliases)
|
|
79
|
+
].join("\n");
|
|
80
|
+
const backupPath = `${configPath}.backup`;
|
|
81
|
+
if (fs.existsSync(configPath)) {
|
|
82
|
+
fs.copyFileSync(configPath, backupPath);
|
|
83
|
+
}
|
|
84
|
+
fs.writeFileSync(configPath, newContent, "utf-8");
|
|
85
|
+
} catch (error) {
|
|
86
|
+
console.error("Failed to save aliases:", error);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// src/components/Logo.tsx
|
|
91
|
+
import React from "react";
|
|
92
|
+
import { Box, Text } from "ink";
|
|
93
|
+
function Logo() {
|
|
94
|
+
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", alignItems: "center", marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true, color: "magenta" }, "\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557"), /* @__PURE__ */ React.createElement(Text, { bold: true, color: "magenta" }, "\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D"), /* @__PURE__ */ React.createElement(Text, { bold: true, color: "cyan" }, "\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557"), /* @__PURE__ */ React.createElement(Text, { bold: true, color: "cyan" }, "\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551"), /* @__PURE__ */ React.createElement(Text, { bold: true, color: "blue" }, "\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551"), /* @__PURE__ */ React.createElement(Text, { bold: true, color: "blue" }, "\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D"), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "manage your shell aliases with style"));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/components/LogoCompact.tsx
|
|
98
|
+
import React2 from "react";
|
|
99
|
+
import { Box as Box2, Text as Text2 } from "ink";
|
|
100
|
+
function LogoCompact() {
|
|
101
|
+
return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", alignItems: "center", marginBottom: 1 }, /* @__PURE__ */ React2.createElement(Box2, null, /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "magenta" }, "\u2584\u2580\u2588 "), /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "cyan" }, "\u2588\u2591\u2591 "), /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "blue" }, "\u2588 "), /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "magenta" }, "\u2584\u2580\u2588 "), /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "cyan" }, "\u2584\u2588\u2580")), /* @__PURE__ */ React2.createElement(Box2, null, /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "magenta" }, "\u2588\u2580\u2588 "), /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "cyan" }, "\u2588\u2584\u2584 "), /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "blue" }, "\u2588 "), /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "magenta" }, "\u2588\u2580\u2588 "), /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "cyan" }, "\u2591\u2580\u2588")), /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, "manage your shell aliases"));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// src/components/AliasList.tsx
|
|
105
|
+
import React3 from "react";
|
|
106
|
+
import { Box as Box3, Text as Text3 } from "ink";
|
|
107
|
+
function AliasList({ aliases, selectedIndex, isSearchMode }) {
|
|
108
|
+
if (aliases.length === 0) {
|
|
109
|
+
return /* @__PURE__ */ React3.createElement(
|
|
110
|
+
Box3,
|
|
111
|
+
{
|
|
112
|
+
flexDirection: "column",
|
|
113
|
+
alignItems: "center",
|
|
114
|
+
justifyContent: "center",
|
|
115
|
+
paddingY: 3
|
|
116
|
+
},
|
|
117
|
+
/* @__PURE__ */ React3.createElement(Text3, { color: "gray" }, "No aliases found"),
|
|
118
|
+
isSearchMode ? /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "Try a different search term or press [Esc] to go back") : /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "Press [a] to create your first alias")
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
return /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, aliases.map((alias, index) => {
|
|
122
|
+
const isSelected = index === selectedIndex;
|
|
123
|
+
return /* @__PURE__ */ React3.createElement(Box3, { key: alias.name, paddingY: 0 }, /* @__PURE__ */ React3.createElement(Box3, { width: 2 }, /* @__PURE__ */ React3.createElement(Text3, { bold: true, color: isSelected ? "magenta" : "gray" }, isSelected ? "\u25B6" : " ")), /* @__PURE__ */ React3.createElement(Box3, { width: 15 }, /* @__PURE__ */ React3.createElement(
|
|
124
|
+
Text3,
|
|
125
|
+
{
|
|
126
|
+
bold: isSelected,
|
|
127
|
+
color: isSelected ? "cyan" : "white"
|
|
128
|
+
},
|
|
129
|
+
alias.name
|
|
130
|
+
)), /* @__PURE__ */ React3.createElement(Box3, { width: 2 }, /* @__PURE__ */ React3.createElement(Text3, { color: "gray" }, "=")), /* @__PURE__ */ React3.createElement(Box3, null, /* @__PURE__ */ React3.createElement(
|
|
131
|
+
Text3,
|
|
132
|
+
{
|
|
133
|
+
color: isSelected ? "green" : "gray",
|
|
134
|
+
wrap: "truncate"
|
|
135
|
+
},
|
|
136
|
+
alias.command
|
|
137
|
+
)));
|
|
138
|
+
}));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// src/components/AddAliasModal.tsx
|
|
142
|
+
import React4, { useState } from "react";
|
|
143
|
+
import { Box as Box4, Text as Text4, useInput } from "ink";
|
|
144
|
+
import TextInput from "ink-text-input";
|
|
145
|
+
function AddAliasModal({ onSave, onCancel }) {
|
|
146
|
+
const [name, setName] = useState("");
|
|
147
|
+
const [command, setCommand] = useState("");
|
|
148
|
+
const [focusedField, setFocusedField] = useState("name");
|
|
149
|
+
useInput((input, key) => {
|
|
150
|
+
if (key.tab || key.downArrow) {
|
|
151
|
+
setFocusedField((prev) => prev === "name" ? "command" : "name");
|
|
152
|
+
} else if (key.upArrow) {
|
|
153
|
+
setFocusedField((prev) => prev === "command" ? "name" : "command");
|
|
154
|
+
} else if (key.escape) {
|
|
155
|
+
onCancel();
|
|
156
|
+
} else if (key.return && name && command) {
|
|
157
|
+
onSave(name, command);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
const isFocusedName = focusedField === "name";
|
|
161
|
+
const isFocusedCommand = focusedField === "command";
|
|
162
|
+
return /* @__PURE__ */ React4.createElement(Box4, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React4.createElement(Box4, { marginBottom: 1 }, /* @__PURE__ */ React4.createElement(Text4, { bold: true, color: "green" }, "\u2795 Add New Alias")), /* @__PURE__ */ React4.createElement(Box4, { flexDirection: "column" }, /* @__PURE__ */ React4.createElement(Box4, { marginBottom: 0 }, /* @__PURE__ */ React4.createElement(Text4, { bold: true, color: isFocusedName ? "cyan" : "gray" }, isFocusedName && "\u25B6 ", "Name:")), /* @__PURE__ */ React4.createElement(
|
|
163
|
+
Box4,
|
|
164
|
+
{
|
|
165
|
+
borderStyle: "round",
|
|
166
|
+
borderColor: isFocusedName ? "cyan" : "gray",
|
|
167
|
+
paddingX: 1,
|
|
168
|
+
width: 50
|
|
169
|
+
},
|
|
170
|
+
isFocusedName ? /* @__PURE__ */ React4.createElement(TextInput, { value: name, onChange: setName, placeholder: "e.g., gc" }) : /* @__PURE__ */ React4.createElement(Text4, { color: name ? "white" : "gray" }, name || "e.g., gc")
|
|
171
|
+
)), /* @__PURE__ */ React4.createElement(Box4, { flexDirection: "column" }, /* @__PURE__ */ React4.createElement(Box4, { marginBottom: 0 }, /* @__PURE__ */ React4.createElement(Text4, { bold: true, color: isFocusedCommand ? "cyan" : "gray" }, isFocusedCommand && "\u25B6 ", "Command:")), /* @__PURE__ */ React4.createElement(
|
|
172
|
+
Box4,
|
|
173
|
+
{
|
|
174
|
+
borderStyle: "round",
|
|
175
|
+
borderColor: isFocusedCommand ? "cyan" : "gray",
|
|
176
|
+
paddingX: 1,
|
|
177
|
+
width: 50
|
|
178
|
+
},
|
|
179
|
+
isFocusedCommand ? /* @__PURE__ */ React4.createElement(
|
|
180
|
+
TextInput,
|
|
181
|
+
{
|
|
182
|
+
value: command,
|
|
183
|
+
onChange: setCommand,
|
|
184
|
+
placeholder: "e.g., git add . && git commit -m"
|
|
185
|
+
}
|
|
186
|
+
) : /* @__PURE__ */ React4.createElement(Text4, { color: command ? "white" : "gray" }, command || "e.g., git add . && git commit -m")
|
|
187
|
+
)), name && command && /* @__PURE__ */ React4.createElement(Box4, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, "Preview:"), /* @__PURE__ */ React4.createElement(Box4, { paddingX: 2 }, /* @__PURE__ */ React4.createElement(Text4, { color: "cyan" }, name), /* @__PURE__ */ React4.createElement(Text4, { color: "gray" }, " = "), /* @__PURE__ */ React4.createElement(Text4, { color: "green" }, '"', command, '"'))), /* @__PURE__ */ React4.createElement(Box4, { marginTop: 1, justifyContent: "center", gap: 2 }, /* @__PURE__ */ React4.createElement(Text4, null, /* @__PURE__ */ React4.createElement(Text4, { bold: true, color: "cyan" }, "[\u2191/\u2193]"), /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, " switch")), /* @__PURE__ */ React4.createElement(Text4, null, /* @__PURE__ */ React4.createElement(Text4, { bold: true, color: "green" }, "[Enter]"), /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, " save")), /* @__PURE__ */ React4.createElement(Text4, null, /* @__PURE__ */ React4.createElement(Text4, { bold: true, color: "gray" }, "[Esc]"), /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, " cancel"))));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// src/components/EditAliasModal.tsx
|
|
191
|
+
import React5, { useState as useState2 } from "react";
|
|
192
|
+
import { Box as Box5, Text as Text5, useInput as useInput2 } from "ink";
|
|
193
|
+
import TextInput2 from "ink-text-input";
|
|
194
|
+
function EditAliasModal({ alias, onSave, onCancel }) {
|
|
195
|
+
const [name, setName] = useState2(alias.name);
|
|
196
|
+
const [command, setCommand] = useState2(alias.command);
|
|
197
|
+
const [focusedField, setFocusedField] = useState2("name");
|
|
198
|
+
useInput2((input, key) => {
|
|
199
|
+
if (key.tab || key.downArrow) {
|
|
200
|
+
setFocusedField((prev) => prev === "name" ? "command" : "name");
|
|
201
|
+
} else if (key.upArrow) {
|
|
202
|
+
setFocusedField((prev) => prev === "command" ? "name" : "command");
|
|
203
|
+
} else if (key.escape) {
|
|
204
|
+
onCancel();
|
|
205
|
+
} else if (key.return && name && command) {
|
|
206
|
+
onSave(name, command);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
const isFocusedName = focusedField === "name";
|
|
210
|
+
const isFocusedCommand = focusedField === "command";
|
|
211
|
+
return /* @__PURE__ */ React5.createElement(Box5, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React5.createElement(Box5, { marginBottom: 1 }, /* @__PURE__ */ React5.createElement(Text5, { bold: true, color: "blue" }, "\u270F\uFE0F Edit Alias")), /* @__PURE__ */ React5.createElement(Box5, { flexDirection: "column" }, /* @__PURE__ */ React5.createElement(Box5, { marginBottom: 0 }, /* @__PURE__ */ React5.createElement(Text5, { bold: true, color: isFocusedName ? "cyan" : "gray" }, isFocusedName && "\u25B6 ", "Name:")), /* @__PURE__ */ React5.createElement(
|
|
212
|
+
Box5,
|
|
213
|
+
{
|
|
214
|
+
borderStyle: "round",
|
|
215
|
+
borderColor: isFocusedName ? "cyan" : "gray",
|
|
216
|
+
paddingX: 1,
|
|
217
|
+
width: 50
|
|
218
|
+
},
|
|
219
|
+
isFocusedName ? /* @__PURE__ */ React5.createElement(TextInput2, { value: name, onChange: setName }) : /* @__PURE__ */ React5.createElement(Text5, { color: "white" }, name)
|
|
220
|
+
)), /* @__PURE__ */ React5.createElement(Box5, { flexDirection: "column" }, /* @__PURE__ */ React5.createElement(Box5, { marginBottom: 0 }, /* @__PURE__ */ React5.createElement(Text5, { bold: true, color: isFocusedCommand ? "cyan" : "gray" }, isFocusedCommand && "\u25B6 ", "Command:")), /* @__PURE__ */ React5.createElement(
|
|
221
|
+
Box5,
|
|
222
|
+
{
|
|
223
|
+
borderStyle: "round",
|
|
224
|
+
borderColor: isFocusedCommand ? "cyan" : "gray",
|
|
225
|
+
paddingX: 1,
|
|
226
|
+
width: 50
|
|
227
|
+
},
|
|
228
|
+
isFocusedCommand ? /* @__PURE__ */ React5.createElement(TextInput2, { value: command, onChange: setCommand }) : /* @__PURE__ */ React5.createElement(Text5, { color: "white" }, command)
|
|
229
|
+
)), /* @__PURE__ */ React5.createElement(Box5, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, "Preview:"), /* @__PURE__ */ React5.createElement(Box5, { paddingX: 2 }, /* @__PURE__ */ React5.createElement(Text5, { color: "cyan" }, name), /* @__PURE__ */ React5.createElement(Text5, { color: "gray" }, " = "), /* @__PURE__ */ React5.createElement(Text5, { color: "green" }, '"', command, '"'))), /* @__PURE__ */ React5.createElement(Box5, { marginTop: 1, justifyContent: "center", gap: 2 }, /* @__PURE__ */ React5.createElement(Text5, null, /* @__PURE__ */ React5.createElement(Text5, { bold: true, color: "cyan" }, "[\u2191/\u2193]"), /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, " switch")), /* @__PURE__ */ React5.createElement(Text5, null, /* @__PURE__ */ React5.createElement(Text5, { bold: true, color: "green" }, "[Enter]"), /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, " save")), /* @__PURE__ */ React5.createElement(Text5, null, /* @__PURE__ */ React5.createElement(Text5, { bold: true, color: "gray" }, "[Esc]"), /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, " cancel"))));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// src/components/DeleteConfirmModal.tsx
|
|
233
|
+
import React6 from "react";
|
|
234
|
+
import { Box as Box6, Text as Text6, useInput as useInput3 } from "ink";
|
|
235
|
+
function DeleteConfirmModal({
|
|
236
|
+
alias,
|
|
237
|
+
onConfirm,
|
|
238
|
+
onCancel
|
|
239
|
+
}) {
|
|
240
|
+
useInput3((input, key) => {
|
|
241
|
+
if (input === "y" || key.return) {
|
|
242
|
+
onConfirm();
|
|
243
|
+
} else if (input === "n" || key.escape) {
|
|
244
|
+
onCancel();
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
return /* @__PURE__ */ React6.createElement(Box6, { flexDirection: "column", gap: 1, alignItems: "center" }, /* @__PURE__ */ React6.createElement(Box6, { marginBottom: 1 }, /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: "red" }, "\u26A0\uFE0F Delete Alias")), /* @__PURE__ */ React6.createElement(
|
|
248
|
+
Box6,
|
|
249
|
+
{
|
|
250
|
+
borderStyle: "round",
|
|
251
|
+
borderColor: "red",
|
|
252
|
+
paddingX: 2,
|
|
253
|
+
paddingY: 1,
|
|
254
|
+
flexDirection: "column",
|
|
255
|
+
alignItems: "center"
|
|
256
|
+
},
|
|
257
|
+
/* @__PURE__ */ React6.createElement(Text6, null, "Are you sure you want to delete", " ", /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: "cyan" }, alias.name), "?"),
|
|
258
|
+
/* @__PURE__ */ React6.createElement(Box6, { marginTop: 1, flexDirection: "column", alignItems: "center" }, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "Command:"), /* @__PURE__ */ React6.createElement(Text6, { color: "gray" }, '"', alias.command, '"'))
|
|
259
|
+
), /* @__PURE__ */ React6.createElement(Box6, { marginTop: 1 }, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true, italic: true }, "This action cannot be undone")), /* @__PURE__ */ React6.createElement(Box6, { marginTop: 1, gap: 3 }, /* @__PURE__ */ React6.createElement(Text6, null, /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: "red" }, "[y]"), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " yes, delete")), /* @__PURE__ */ React6.createElement(Text6, null, /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: "green" }, "[n]"), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " no, cancel")), /* @__PURE__ */ React6.createElement(Text6, null, /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: "gray" }, "[Esc]"), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " cancel"))));
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// src/components/SearchModal.tsx
|
|
263
|
+
import React7, { useState as useState3 } from "react";
|
|
264
|
+
import { Box as Box7, Text as Text7, useInput as useInput4 } from "ink";
|
|
265
|
+
import TextInput3 from "ink-text-input";
|
|
266
|
+
function SearchModal({ onSearch, onCancel, matchCount, onNavigate, onSelect }) {
|
|
267
|
+
const [query, setQuery] = useState3("");
|
|
268
|
+
const handleChange = (value) => {
|
|
269
|
+
setQuery(value);
|
|
270
|
+
onSearch(value);
|
|
271
|
+
};
|
|
272
|
+
useInput4((input, key) => {
|
|
273
|
+
if (key.escape) {
|
|
274
|
+
onCancel();
|
|
275
|
+
} else if (key.return) {
|
|
276
|
+
if (matchCount && matchCount > 0 && onSelect) {
|
|
277
|
+
onSelect();
|
|
278
|
+
} else {
|
|
279
|
+
onCancel();
|
|
280
|
+
}
|
|
281
|
+
} else if (key.upArrow && onNavigate) {
|
|
282
|
+
onNavigate("up");
|
|
283
|
+
} else if (key.downArrow && onNavigate) {
|
|
284
|
+
onNavigate("down");
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
return /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React7.createElement(Box7, { marginBottom: 1 }, /* @__PURE__ */ React7.createElement(Text7, { bold: true, color: "cyan" }, "\u{1F50D} Search Aliases")), /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column" }, /* @__PURE__ */ React7.createElement(Box7, { marginBottom: 0 }, /* @__PURE__ */ React7.createElement(Text7, { bold: true, color: "cyan" }, "Search:")), /* @__PURE__ */ React7.createElement(
|
|
288
|
+
Box7,
|
|
289
|
+
{
|
|
290
|
+
borderStyle: "round",
|
|
291
|
+
borderColor: "cyan",
|
|
292
|
+
paddingX: 1,
|
|
293
|
+
width: 50
|
|
294
|
+
},
|
|
295
|
+
/* @__PURE__ */ React7.createElement(
|
|
296
|
+
TextInput3,
|
|
297
|
+
{
|
|
298
|
+
value: query,
|
|
299
|
+
onChange: handleChange,
|
|
300
|
+
placeholder: "Type to filter aliases..."
|
|
301
|
+
}
|
|
302
|
+
)
|
|
303
|
+
)), query && matchCount !== void 0 && /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1, justifyContent: "center" }, /* @__PURE__ */ React7.createElement(Text7, { color: "cyan" }, matchCount), /* @__PURE__ */ React7.createElement(Text7, { dimColor: true }, " ", matchCount === 1 ? "match" : "matches")), /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1, justifyContent: "center", gap: 2 }, /* @__PURE__ */ React7.createElement(Text7, null, /* @__PURE__ */ React7.createElement(Text7, { bold: true, color: "green" }, "[Enter]"), /* @__PURE__ */ React7.createElement(Text7, { dimColor: true }, " select")), /* @__PURE__ */ React7.createElement(Text7, { dimColor: true }, "\u2022"), /* @__PURE__ */ React7.createElement(Text7, null, /* @__PURE__ */ React7.createElement(Text7, { bold: true, color: "yellow" }, "[Esc]"), /* @__PURE__ */ React7.createElement(Text7, { dimColor: true }, " close")), /* @__PURE__ */ React7.createElement(Text7, { dimColor: true }, "\u2022"), /* @__PURE__ */ React7.createElement(Text7, null, /* @__PURE__ */ React7.createElement(Text7, { bold: true, color: "cyan" }, "[\u2191/\u2193]"), /* @__PURE__ */ React7.createElement(Text7, { dimColor: true }, " navigate")), /* @__PURE__ */ React7.createElement(Text7, { dimColor: true }, "\u2022"), /* @__PURE__ */ React7.createElement(Text7, null, /* @__PURE__ */ React7.createElement(Text7, { bold: true, color: "gray" }, "[q]"), /* @__PURE__ */ React7.createElement(Text7, { dimColor: true }, " quit"))));
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// src/App.tsx
|
|
307
|
+
function App() {
|
|
308
|
+
const { exit } = useApp();
|
|
309
|
+
const [aliases, setAliases] = useState4([]);
|
|
310
|
+
const [selectedIndex, setSelectedIndex] = useState4(0);
|
|
311
|
+
const [mode, setMode] = useState4("list");
|
|
312
|
+
const [searchQuery, setSearchQuery] = useState4("");
|
|
313
|
+
const [hasChanges, setHasChanges] = useState4(false);
|
|
314
|
+
useEffect(() => {
|
|
315
|
+
const loaded = loadAliases();
|
|
316
|
+
setAliases(loaded);
|
|
317
|
+
}, []);
|
|
318
|
+
const filteredAliases = searchQuery ? aliases.filter(
|
|
319
|
+
(a) => a.name.toLowerCase().includes(searchQuery.toLowerCase()) || a.command.toLowerCase().includes(searchQuery.toLowerCase())
|
|
320
|
+
) : aliases;
|
|
321
|
+
useInput5((input, key) => {
|
|
322
|
+
if (mode !== "list") return;
|
|
323
|
+
if (key.upArrow) {
|
|
324
|
+
setSelectedIndex((prev) => Math.max(0, prev - 1));
|
|
325
|
+
} else if (key.downArrow) {
|
|
326
|
+
setSelectedIndex(
|
|
327
|
+
(prev) => Math.min(filteredAliases.length - 1, prev + 1)
|
|
328
|
+
);
|
|
329
|
+
} else if (input === "a") {
|
|
330
|
+
setMode("add");
|
|
331
|
+
} else if (input === "e" && filteredAliases.length > 0) {
|
|
332
|
+
setMode("edit");
|
|
333
|
+
} else if ((input === "d" || key.delete) && filteredAliases.length > 0) {
|
|
334
|
+
setMode("delete");
|
|
335
|
+
} else if (input === "/") {
|
|
336
|
+
setMode("search");
|
|
337
|
+
} else if (input === "c" && searchQuery) {
|
|
338
|
+
handleClearSearch();
|
|
339
|
+
} else if (input === "q") {
|
|
340
|
+
if (hasChanges) {
|
|
341
|
+
const configPath = getShellConfigPath();
|
|
342
|
+
const fileName = configPath.replace(os2.homedir(), "~");
|
|
343
|
+
const sourceCommand = `source ${fileName}`;
|
|
344
|
+
exit();
|
|
345
|
+
console.log("\n\x1B[32m\u2728 Changes saved!\x1B[0m\n");
|
|
346
|
+
console.log("\x1B[33m\u{1F4CB} To apply your aliases, run:\x1B[0m");
|
|
347
|
+
console.log(` \x1B[36;1m${sourceCommand}\x1B[0m
|
|
348
|
+
`);
|
|
349
|
+
try {
|
|
350
|
+
const { execSync } = __require("child_process");
|
|
351
|
+
try {
|
|
352
|
+
execSync(`echo '${sourceCommand}' | pbcopy`, { stdio: "ignore" });
|
|
353
|
+
console.log("\x1B[2m\u2713 Copied to clipboard! Just paste and run.\x1B[0m\n");
|
|
354
|
+
} catch {
|
|
355
|
+
execSync(`echo '${sourceCommand}' | xclip -selection clipboard`, { stdio: "ignore" });
|
|
356
|
+
console.log("\x1B[2m\u2713 Copied to clipboard! Just paste and run.\x1B[0m\n");
|
|
357
|
+
}
|
|
358
|
+
} catch {
|
|
359
|
+
}
|
|
360
|
+
console.log("\x1B[33m\u26A1 Want auto-reload on quit?\x1B[0m");
|
|
361
|
+
console.log(`\x1B[2m Add this wrapper to your ${fileName}:\x1B[0m`);
|
|
362
|
+
console.log(`\x1B[2m alias-cli-reload() { command alias-cli && [ -f ~/.alias-cli-reload ] && source "$(cat ~/.alias-cli-reload)" && rm ~/.alias-cli-reload; }\x1B[0m`);
|
|
363
|
+
console.log(`\x1B[2m alias alias-cli='alias-cli-reload'\x1B[0m`);
|
|
364
|
+
console.log(`\x1B[2m Then run: source ${fileName}\x1B[0m
|
|
365
|
+
`);
|
|
366
|
+
} else {
|
|
367
|
+
exit();
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
const handleAdd = useCallback(
|
|
372
|
+
(name, command) => {
|
|
373
|
+
const newAlias = { name, command };
|
|
374
|
+
const updated = [...aliases, newAlias];
|
|
375
|
+
setAliases(updated);
|
|
376
|
+
saveAliases(updated);
|
|
377
|
+
setHasChanges(true);
|
|
378
|
+
setMode("list");
|
|
379
|
+
},
|
|
380
|
+
[aliases, setAliases, saveAliases, setHasChanges, setMode]
|
|
381
|
+
);
|
|
382
|
+
const handleEdit = useCallback(
|
|
383
|
+
(name, command) => {
|
|
384
|
+
const selected = filteredAliases[selectedIndex];
|
|
385
|
+
const updated = aliases.map(
|
|
386
|
+
(a) => a.name === selected.name ? { name, command } : a
|
|
387
|
+
);
|
|
388
|
+
setAliases(updated);
|
|
389
|
+
saveAliases(updated);
|
|
390
|
+
setHasChanges(true);
|
|
391
|
+
setMode("list");
|
|
392
|
+
},
|
|
393
|
+
[aliases, selectedIndex, setAliases, saveAliases, setHasChanges, setMode]
|
|
394
|
+
);
|
|
395
|
+
const handleDelete = useCallback(() => {
|
|
396
|
+
const selected = filteredAliases[selectedIndex];
|
|
397
|
+
const updated = aliases.filter((a) => a.name !== selected.name);
|
|
398
|
+
setAliases(updated);
|
|
399
|
+
saveAliases(updated);
|
|
400
|
+
setHasChanges(true);
|
|
401
|
+
setSelectedIndex(Math.max(0, selectedIndex - 1));
|
|
402
|
+
setMode("list");
|
|
403
|
+
}, [aliases, selectedIndex, setAliases, saveAliases, setHasChanges, setMode]);
|
|
404
|
+
const handleCancel = useCallback(() => {
|
|
405
|
+
setMode("list");
|
|
406
|
+
}, [setMode]);
|
|
407
|
+
const handleSearch = useCallback(
|
|
408
|
+
(query) => {
|
|
409
|
+
setSearchQuery(query);
|
|
410
|
+
setSelectedIndex(0);
|
|
411
|
+
},
|
|
412
|
+
[setSearchQuery, setSelectedIndex]
|
|
413
|
+
);
|
|
414
|
+
const handleClearSearch = useCallback(() => {
|
|
415
|
+
setSearchQuery("");
|
|
416
|
+
setMode("list");
|
|
417
|
+
}, [setSearchQuery, setMode]);
|
|
418
|
+
const handleNavigate = useCallback(
|
|
419
|
+
(direction) => {
|
|
420
|
+
if (direction === "up") {
|
|
421
|
+
setSelectedIndex((prev) => Math.max(0, prev - 1));
|
|
422
|
+
} else {
|
|
423
|
+
setSelectedIndex((prev) => Math.min(filteredAliases.length - 1, prev + 1));
|
|
424
|
+
}
|
|
425
|
+
},
|
|
426
|
+
[filteredAliases.length]
|
|
427
|
+
);
|
|
428
|
+
const handleSelectFromSearch = useCallback(() => {
|
|
429
|
+
if (filteredAliases.length > 0) {
|
|
430
|
+
setMode("edit");
|
|
431
|
+
}
|
|
432
|
+
}, [filteredAliases.length]);
|
|
433
|
+
return /* @__PURE__ */ React8.createElement(Box8, { flexDirection: "column", paddingX: 2, paddingY: 1 }, mode === "list" ? /* @__PURE__ */ React8.createElement(Logo, null) : /* @__PURE__ */ React8.createElement(LogoCompact, null), /* @__PURE__ */ React8.createElement(Box8, { marginBottom: 1 }, /* @__PURE__ */ React8.createElement(Box8, { gap: 1 }, /* @__PURE__ */ React8.createElement(Text8, { bold: true, color: "magenta" }, "\u26A1"), /* @__PURE__ */ React8.createElement(Text8, { bold: true, color: "white" }, "Alias Manager"), /* @__PURE__ */ React8.createElement(Text8, { dimColor: true }, "(", aliases.length, " aliases)"))), /* @__PURE__ */ React8.createElement(Box8, { marginBottom: 1 }, /* @__PURE__ */ React8.createElement(Text8, { color: "gray" }, "\u2500".repeat(80))), mode === "search" && /* @__PURE__ */ React8.createElement(Box8, { marginBottom: 1 }, /* @__PURE__ */ React8.createElement(
|
|
434
|
+
SearchModal,
|
|
435
|
+
{
|
|
436
|
+
onSearch: handleSearch,
|
|
437
|
+
onCancel: handleCancel,
|
|
438
|
+
matchCount: filteredAliases.length,
|
|
439
|
+
onNavigate: handleNavigate,
|
|
440
|
+
onSelect: handleSelectFromSearch
|
|
441
|
+
}
|
|
442
|
+
)), /* @__PURE__ */ React8.createElement(
|
|
443
|
+
Box8,
|
|
444
|
+
{
|
|
445
|
+
borderStyle: "round",
|
|
446
|
+
borderColor: "gray",
|
|
447
|
+
flexDirection: "column",
|
|
448
|
+
paddingX: 2,
|
|
449
|
+
paddingY: 1,
|
|
450
|
+
minHeight: 12
|
|
451
|
+
},
|
|
452
|
+
(() => {
|
|
453
|
+
switch (mode) {
|
|
454
|
+
case "list":
|
|
455
|
+
case "search":
|
|
456
|
+
return /* @__PURE__ */ React8.createElement(
|
|
457
|
+
AliasList,
|
|
458
|
+
{
|
|
459
|
+
aliases: filteredAliases,
|
|
460
|
+
selectedIndex,
|
|
461
|
+
isSearchMode: mode === "search"
|
|
462
|
+
}
|
|
463
|
+
);
|
|
464
|
+
case "add":
|
|
465
|
+
return /* @__PURE__ */ React8.createElement(AddAliasModal, { onSave: handleAdd, onCancel: handleCancel });
|
|
466
|
+
case "edit":
|
|
467
|
+
return filteredAliases[selectedIndex] ? /* @__PURE__ */ React8.createElement(
|
|
468
|
+
EditAliasModal,
|
|
469
|
+
{
|
|
470
|
+
alias: filteredAliases[selectedIndex],
|
|
471
|
+
onSave: handleEdit,
|
|
472
|
+
onCancel: handleCancel
|
|
473
|
+
}
|
|
474
|
+
) : null;
|
|
475
|
+
case "delete":
|
|
476
|
+
return filteredAliases[selectedIndex] ? /* @__PURE__ */ React8.createElement(
|
|
477
|
+
DeleteConfirmModal,
|
|
478
|
+
{
|
|
479
|
+
alias: filteredAliases[selectedIndex],
|
|
480
|
+
onConfirm: handleDelete,
|
|
481
|
+
onCancel: handleCancel
|
|
482
|
+
}
|
|
483
|
+
) : null;
|
|
484
|
+
default:
|
|
485
|
+
return null;
|
|
486
|
+
}
|
|
487
|
+
})()
|
|
488
|
+
), mode === "list" && /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(Box8, { marginTop: 1 }, /* @__PURE__ */ React8.createElement(Text8, { color: "gray" }, "\u2500".repeat(80))), /* @__PURE__ */ React8.createElement(Box8, { marginTop: 1, justifyContent: "center", gap: 3 }, /* @__PURE__ */ React8.createElement(Text8, null, /* @__PURE__ */ React8.createElement(Text8, { bold: true, color: "green" }, "[a]"), /* @__PURE__ */ React8.createElement(Text8, { dimColor: true }, " add")), /* @__PURE__ */ React8.createElement(Text8, null, /* @__PURE__ */ React8.createElement(Text8, { bold: true, color: "blue" }, "[e]"), /* @__PURE__ */ React8.createElement(Text8, { dimColor: true }, " edit")), /* @__PURE__ */ React8.createElement(Text8, null, /* @__PURE__ */ React8.createElement(Text8, { bold: true, color: "red" }, "[d/Del]"), /* @__PURE__ */ React8.createElement(Text8, { dimColor: true }, " delete")), /* @__PURE__ */ React8.createElement(Text8, null, /* @__PURE__ */ React8.createElement(Text8, { bold: true, color: "cyan" }, "[/]"), /* @__PURE__ */ React8.createElement(Text8, { dimColor: true }, " search")), searchQuery && /* @__PURE__ */ React8.createElement(Text8, null, /* @__PURE__ */ React8.createElement(Text8, { bold: true, color: "yellow" }, "[c]"), /* @__PURE__ */ React8.createElement(Text8, { dimColor: true }, " clear")), /* @__PURE__ */ React8.createElement(Text8, { dimColor: true }, "\u2022"), /* @__PURE__ */ React8.createElement(Text8, null, /* @__PURE__ */ React8.createElement(Text8, { bold: true, color: "gray" }, "[\u2191/\u2193]"), /* @__PURE__ */ React8.createElement(Text8, { dimColor: true }, " navigate")), /* @__PURE__ */ React8.createElement(Text8, { dimColor: true }, "\u2022"), /* @__PURE__ */ React8.createElement(Text8, null, /* @__PURE__ */ React8.createElement(Text8, { bold: true, color: "gray" }, "[q]"), /* @__PURE__ */ React8.createElement(Text8, { dimColor: true }, " quit"))), searchQuery && /* @__PURE__ */ React8.createElement(Box8, { marginTop: 1, justifyContent: "center" }, /* @__PURE__ */ React8.createElement(Text8, { dimColor: true }, "Filtering: "), /* @__PURE__ */ React8.createElement(Text8, { color: "cyan" }, '"', searchQuery, '"'), /* @__PURE__ */ React8.createElement(Text8, { dimColor: true }, " (", filteredAliases.length, " matches)"))));
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// src/cli.tsx
|
|
492
|
+
render(/* @__PURE__ */ React9.createElement(App, null));
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@laabroms/alias-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Interactive TUI for managing shell aliases",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/cli.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"alias-cli": "dist/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsup",
|
|
15
|
+
"dev": "tsx src/cli.tsx",
|
|
16
|
+
"prepublishOnly": "npm run build",
|
|
17
|
+
"typecheck": "tsc --noEmit"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"alias",
|
|
21
|
+
"cli",
|
|
22
|
+
"tui",
|
|
23
|
+
"shell",
|
|
24
|
+
"zsh",
|
|
25
|
+
"bash",
|
|
26
|
+
"terminal",
|
|
27
|
+
"interactive"
|
|
28
|
+
],
|
|
29
|
+
"author": "Lucas Aabroms",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/laabroms/alias-cli.git"
|
|
34
|
+
},
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/laabroms/alias-cli/issues"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://github.com/laabroms/alias-cli#readme",
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"ink": "^6.8.0",
|
|
41
|
+
"ink-select-input": "^6.2.0",
|
|
42
|
+
"ink-text-input": "^6.0.0",
|
|
43
|
+
"react": "^19.2.4"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/node": "^25.3.0",
|
|
47
|
+
"@types/react": "^19.2.14",
|
|
48
|
+
"esbuild": "^0.27.3",
|
|
49
|
+
"tsup": "^8.5.1",
|
|
50
|
+
"tsx": "^4.21.0",
|
|
51
|
+
"typescript": "^5.9.3"
|
|
52
|
+
},
|
|
53
|
+
"engines": {
|
|
54
|
+
"node": ">=18.0.0"
|
|
55
|
+
}
|
|
56
|
+
}
|