start-ai-cli 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +116 -19
- package/bin/start-ai-cli.js +633 -135
- package/package.json +78 -45
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026
|
|
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.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
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
CHANGED
|
@@ -1,49 +1,144 @@
|
|
|
1
|
-
# start-ai-cli
|
|
1
|
+
# start-ai-cli - AI coding CLI launcher
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/start-ai-cli)
|
|
4
|
+
[](https://www.npmjs.com/package/start-ai-cli)
|
|
5
|
+
[](LICENSE)
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
`start-ai-cli` is an npm package and command line tool for launching selected AI coding CLIs from the same project directory. It can open Codex CLI, Claude Code, and Cursor CLI in separate terminal tabs or windows so every assistant starts in the current workspace.
|
|
8
|
+
|
|
9
|
+
Use it when you want one command to choose and start your AI coding agents:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx start-ai-cli
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Features
|
|
6
16
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
- Windows Terminal
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
- Cursor CLI available as `agent`
|
|
17
|
+
- Interactively chooses which Codex CLI, Claude Code CLI, and Cursor CLI tools to launch.
|
|
18
|
+
- Remembers your enabled tools in a global config file for the next run.
|
|
19
|
+
- Opens each available tool in its own Windows Terminal tab or macOS Terminal window.
|
|
20
|
+
- Skips missing CLIs instead of failing when at least one supported tool is available.
|
|
21
|
+
- Works as a global npm command or one-off `npx` command.
|
|
13
22
|
|
|
14
23
|
## Install
|
|
15
24
|
|
|
25
|
+
Run without installing:
|
|
26
|
+
|
|
16
27
|
```bash
|
|
17
|
-
|
|
28
|
+
npx start-ai-cli
|
|
18
29
|
```
|
|
19
30
|
|
|
20
|
-
|
|
31
|
+
Install globally:
|
|
21
32
|
|
|
22
33
|
```bash
|
|
23
|
-
npm
|
|
34
|
+
npm install -g start-ai-cli
|
|
24
35
|
```
|
|
25
36
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
Run the command from the project directory you want both tools to use:
|
|
37
|
+
Then run it from the project directory you want the AI tools to use:
|
|
29
38
|
|
|
30
39
|
```bash
|
|
31
40
|
start-ai-cli
|
|
32
41
|
```
|
|
33
42
|
|
|
34
|
-
|
|
43
|
+
## Supported Tools
|
|
44
|
+
|
|
45
|
+
| Tool | Required command | Terminal title |
|
|
46
|
+
| --- | --- | --- |
|
|
47
|
+
| Codex CLI | `codex` | `Codex` |
|
|
48
|
+
| Claude Code | `claude` | `Claude` |
|
|
49
|
+
| Cursor CLI | `agent` | `Cursor` |
|
|
50
|
+
|
|
51
|
+
On Windows, `start-ai-cli` uses Windows Terminal (`wt.exe`) with PowerShell. On macOS, it uses Terminal.app through `osascript`.
|
|
35
52
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
-
|
|
53
|
+
## Requirements
|
|
54
|
+
|
|
55
|
+
- Windows or macOS
|
|
56
|
+
- Node.js 22 or newer
|
|
57
|
+
- Windows Terminal available as `wt.exe` on Windows
|
|
58
|
+
- PowerShell 7 available as `pwsh.exe`, or Windows PowerShell, on Windows
|
|
59
|
+
- Terminal.app and `osascript` available on macOS
|
|
60
|
+
- At least one supported AI coding CLI available in `PATH`
|
|
39
61
|
|
|
40
62
|
## Options
|
|
41
63
|
|
|
42
64
|
```bash
|
|
65
|
+
start-ai-cli
|
|
66
|
+
start-ai-cli --all
|
|
67
|
+
start-ai-cli --no-interactive
|
|
43
68
|
start-ai-cli --help
|
|
44
69
|
start-ai-cli --version
|
|
45
70
|
```
|
|
46
71
|
|
|
72
|
+
By default, `start-ai-cli` opens an interactive selector. Use Up/Down to move, Space to enable or disable an available CLI, Enter to open the selected tools, or `q`/Esc to cancel. Missing CLIs are shown as unavailable and cannot be selected.
|
|
73
|
+
|
|
74
|
+
Use `--all` or `--no-interactive` in scripts and automation to launch every available CLI without prompting.
|
|
75
|
+
|
|
76
|
+
## Global Config
|
|
77
|
+
|
|
78
|
+
Interactive selections are saved to:
|
|
79
|
+
|
|
80
|
+
```text
|
|
81
|
+
~/.start-ai-cli/config.json
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The config stores enabled tool ids:
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
{
|
|
88
|
+
"enabledClis": ["codex", "claude", "cursor"]
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Delete this file to reset the saved defaults.
|
|
93
|
+
|
|
94
|
+
## Troubleshooting
|
|
95
|
+
|
|
96
|
+
### `start-ai-cli cannot start: no CLI tools found in PATH`
|
|
97
|
+
|
|
98
|
+
Install or expose at least one supported command in your shell:
|
|
99
|
+
|
|
100
|
+
- `codex` for Codex CLI
|
|
101
|
+
- `claude` for Claude Code
|
|
102
|
+
- `agent` for Cursor CLI
|
|
103
|
+
|
|
104
|
+
### A CLI does not appear as available in the prompt
|
|
105
|
+
|
|
106
|
+
Make sure its command is installed and available in `PATH`:
|
|
107
|
+
|
|
108
|
+
- `codex` for Codex CLI
|
|
109
|
+
- `claude` for Claude Code
|
|
110
|
+
- `agent` for Cursor CLI
|
|
111
|
+
|
|
112
|
+
### `start-ai-cli cannot prompt in a non-interactive terminal`
|
|
113
|
+
|
|
114
|
+
Run with `--all` or `--no-interactive` when using CI, scripts, or shell pipelines:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
start-ai-cli --all
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### `start-ai-cli cannot start: no selected CLI tools are available`
|
|
121
|
+
|
|
122
|
+
Choose at least one installed CLI in the prompt, or run `start-ai-cli --all` to launch every available tool.
|
|
123
|
+
|
|
124
|
+
### `start-ai-cli cancelled`
|
|
125
|
+
|
|
126
|
+
The interactive selector was closed with `q` or Esc. Run `start-ai-cli` again and press Enter after selecting at least one available CLI.
|
|
127
|
+
|
|
128
|
+
### Windows Terminal was not found
|
|
129
|
+
|
|
130
|
+
Install Windows Terminal and make sure `wt.exe` is available in `PATH`.
|
|
131
|
+
|
|
132
|
+
### PowerShell was not found
|
|
133
|
+
|
|
134
|
+
Install PowerShell 7 (`pwsh.exe`) or make sure Windows PowerShell (`powershell.exe`) is available.
|
|
135
|
+
|
|
136
|
+
## Package Links
|
|
137
|
+
|
|
138
|
+
- npm package: https://www.npmjs.com/package/start-ai-cli
|
|
139
|
+
- GitHub repository: https://github.com/guoxiao0521/open-ai-cli
|
|
140
|
+
- Issues: https://github.com/guoxiao0521/open-ai-cli/issues
|
|
141
|
+
|
|
47
142
|
## Development
|
|
48
143
|
|
|
49
144
|
```bash
|
|
@@ -54,6 +149,8 @@ npm run publish:dry-run
|
|
|
54
149
|
|
|
55
150
|
## Publish
|
|
56
151
|
|
|
152
|
+
`README.md` and npm metadata changes appear on npm after publishing a new package version.
|
|
153
|
+
|
|
57
154
|
```bash
|
|
58
155
|
npm login --registry=https://registry.npmjs.org/
|
|
59
156
|
npm publish
|
package/bin/start-ai-cli.js
CHANGED
|
@@ -1,170 +1,668 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
3
|
import { spawn, spawnSync } from 'node:child_process';
|
|
4
|
-
import { readFileSync, realpathSync } from 'node:fs';
|
|
5
|
-
import {
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';
|
|
5
|
+
import { homedir } from 'node:os';
|
|
6
|
+
import { dirname, resolve, win32 as pathWin32 } from 'node:path';
|
|
6
7
|
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import React, { useState } from 'react';
|
|
9
|
+
import { Box, Text, render, useApp, useInput } from 'ink';
|
|
10
|
+
|
|
11
|
+
const COMMAND = 'start-ai-cli';
|
|
12
|
+
|
|
13
|
+
const HARD_REQUIREMENTS_BY_PLATFORM = {
|
|
14
|
+
win32: [{ command: 'wt.exe', label: 'Windows Terminal (wt.exe)' }],
|
|
15
|
+
darwin: [{ command: 'osascript', label: 'AppleScript runner (osascript)' }]
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const CLI_COMMANDS = [
|
|
19
|
+
{ id: 'codex', command: 'codex', label: 'Codex CLI (codex)', title: 'Codex' },
|
|
20
|
+
{ id: 'claude', command: 'claude', label: 'Claude Code CLI (claude)', title: 'Claude' },
|
|
21
|
+
{ id: 'cursor', command: 'agent', label: 'Cursor CLI (agent)', title: 'Cursor' }
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
const DEFAULT_WINDOWS_SHELL_COMMAND = 'pwsh.exe';
|
|
25
|
+
const DEFAULT_ENABLED_CLI_IDS = CLI_COMMANDS.map(({ id }) => id);
|
|
26
|
+
const EMPTY_SELECTION_MESSAGE = 'Select at least one available CLI.';
|
|
27
|
+
|
|
28
|
+
const HELP_TEXT = `Usage:
|
|
29
|
+
${COMMAND}
|
|
30
|
+
${COMMAND} --all
|
|
31
|
+
${COMMAND} --help
|
|
32
|
+
${COMMAND} --version
|
|
33
|
+
|
|
34
|
+
Interactively choose which CLI tools to open in the current directory:
|
|
35
|
+
- Codex: runs "codex"
|
|
36
|
+
- Claude: runs "claude"
|
|
37
|
+
- Cursor: runs "agent"
|
|
38
|
+
|
|
39
|
+
Options:
|
|
40
|
+
--all, --no-interactive Open every available CLI without prompting
|
|
41
|
+
--help, -h Show this help
|
|
42
|
+
--version, -v Show the package version
|
|
43
|
+
|
|
44
|
+
Requirements:
|
|
45
|
+
- Windows with Windows Terminal (wt.exe) and PowerShell, or macOS with Terminal.app
|
|
46
|
+
- Codex CLI available as "codex"
|
|
47
|
+
- Claude Code CLI available as "claude"
|
|
48
|
+
- Cursor CLI available as "agent"
|
|
49
|
+
`;
|
|
50
|
+
|
|
51
|
+
export function parseArgs(args) {
|
|
52
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
53
|
+
return { action: 'help' };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (args.includes('--version') || args.includes('-v')) {
|
|
57
|
+
return { action: 'version' };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const nonInteractive = args.includes('--all') || args.includes('--no-interactive');
|
|
61
|
+
const known = new Set(['--all', '--no-interactive']);
|
|
62
|
+
const unknown = args.filter((arg) => arg.startsWith('-') && !known.has(arg));
|
|
63
|
+
if (unknown.length > 0) {
|
|
64
|
+
return { action: 'error', message: `Unknown option: ${unknown.join(', ')}` };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return { action: 'open', interactive: !nonInteractive };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function getPackageVersion() {
|
|
71
|
+
const packageJsonPath = resolve(dirname(fileURLToPath(import.meta.url)), '..', 'package.json');
|
|
72
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
73
|
+
return packageJson.version;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function getConfigPath({ homeDirectory = homedir() } = {}) {
|
|
77
|
+
return resolve(homeDirectory, '.start-ai-cli', 'config.json');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function normalizeEnabledCliIds(enabledCliIds, cliCommands = CLI_COMMANDS) {
|
|
81
|
+
const validIds = new Set(cliCommands.map(({ id }) => id));
|
|
82
|
+
if (!Array.isArray(enabledCliIds)) {
|
|
83
|
+
return cliCommands.map(({ id }) => id);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const normalized = [];
|
|
87
|
+
for (const id of enabledCliIds) {
|
|
88
|
+
if (typeof id === 'string' && validIds.has(id) && !normalized.includes(id)) {
|
|
89
|
+
normalized.push(id);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return normalized;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function readConfig({
|
|
97
|
+
configPath = getConfigPath(),
|
|
98
|
+
readFileFn = readFileSync
|
|
99
|
+
} = {}) {
|
|
100
|
+
try {
|
|
101
|
+
const config = JSON.parse(readFileFn(configPath, 'utf8'));
|
|
102
|
+
return {
|
|
103
|
+
enabledClis: normalizeEnabledCliIds(config.enabledClis)
|
|
104
|
+
};
|
|
105
|
+
} catch {
|
|
106
|
+
return {
|
|
107
|
+
enabledClis: [...DEFAULT_ENABLED_CLI_IDS]
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function writeConfig({
|
|
113
|
+
configPath = getConfigPath(),
|
|
114
|
+
enabledClis,
|
|
115
|
+
mkdirFn = mkdirSync,
|
|
116
|
+
writeFileFn = writeFileSync
|
|
117
|
+
} = {}) {
|
|
118
|
+
const config = {
|
|
119
|
+
enabledClis: normalizeEnabledCliIds(enabledClis)
|
|
120
|
+
};
|
|
121
|
+
mkdirFn(dirname(configPath), { recursive: true });
|
|
122
|
+
writeFileFn(configPath, `${JSON.stringify(config, null, 2)}\n`, 'utf8');
|
|
123
|
+
return config;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function inspectCliCommands({
|
|
127
|
+
env = process.env,
|
|
128
|
+
platform = process.platform,
|
|
129
|
+
commandExistsFn = commandExists,
|
|
130
|
+
cliCommands = CLI_COMMANDS
|
|
131
|
+
} = {}) {
|
|
132
|
+
return cliCommands.map((cli) => ({
|
|
133
|
+
...cli,
|
|
134
|
+
available: commandExistsFn(cli.command, { env, platform })
|
|
135
|
+
}));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function getAvailableTabsFromInspection(inspectedClis) {
|
|
139
|
+
return inspectedClis.filter(({ available }) => available);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function getMissingLabelsFromInspection(inspectedClis) {
|
|
143
|
+
return inspectedClis
|
|
144
|
+
.filter(({ available }) => !available)
|
|
145
|
+
.map(({ label }) => label);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function filterTabsByEnabledCliIds(tabs, enabledCliIds) {
|
|
149
|
+
const enabled = new Set(normalizeEnabledCliIds(enabledCliIds));
|
|
150
|
+
return tabs.filter(({ id }) => enabled.has(id));
|
|
151
|
+
}
|
|
7
152
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
{ command: 'agent', label: 'Cursor CLI (agent)' }
|
|
15
|
-
];
|
|
16
|
-
|
|
17
|
-
const HELP_TEXT = `Usage:
|
|
18
|
-
${COMMAND}
|
|
19
|
-
${COMMAND} --help
|
|
20
|
-
${COMMAND} --version
|
|
21
|
-
|
|
22
|
-
Opens Windows Terminal with three tabs in the current directory:
|
|
23
|
-
- Codex: runs "codex"
|
|
24
|
-
- Claude: runs "claude"
|
|
25
|
-
- Cursor: runs "agent"
|
|
26
|
-
|
|
27
|
-
Requirements:
|
|
28
|
-
- Windows
|
|
29
|
-
- Windows Terminal (wt.exe)
|
|
30
|
-
- Codex CLI available as "codex"
|
|
31
|
-
- Claude Code CLI available as "claude"
|
|
32
|
-
- Cursor CLI available as "agent"
|
|
33
|
-
`;
|
|
34
|
-
|
|
35
|
-
export function parseArgs(args) {
|
|
36
|
-
if (args.includes('--help') || args.includes('-h')) {
|
|
37
|
-
return { action: 'help' };
|
|
38
|
-
}
|
|
153
|
+
export function isInteractiveTerminal({
|
|
154
|
+
input = process.stdin,
|
|
155
|
+
output = process.stdout
|
|
156
|
+
} = {}) {
|
|
157
|
+
return Boolean(input.isTTY && output.isTTY);
|
|
158
|
+
}
|
|
39
159
|
|
|
40
|
-
|
|
41
|
-
|
|
160
|
+
export class CliSelectionCancelledError extends Error {
|
|
161
|
+
constructor() {
|
|
162
|
+
super('CLI selection cancelled.');
|
|
163
|
+
this.name = 'CliSelectionCancelledError';
|
|
42
164
|
}
|
|
165
|
+
}
|
|
43
166
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
167
|
+
function getFirstAvailableCliIndex(inspectedClis) {
|
|
168
|
+
const index = inspectedClis.findIndex(({ available }) => available);
|
|
169
|
+
return index === -1 ? 0 : index;
|
|
170
|
+
}
|
|
48
171
|
|
|
49
|
-
|
|
172
|
+
function getAvailableCliIds(inspectedClis) {
|
|
173
|
+
return new Set(inspectedClis.filter(({ available }) => available).map(({ id }) => id));
|
|
50
174
|
}
|
|
51
175
|
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
return packageJson.version;
|
|
176
|
+
function orderSelectedIds(selectedIds, inspectedClis) {
|
|
177
|
+
const selected = new Set(selectedIds);
|
|
178
|
+
return inspectedClis.filter(({ id }) => selected.has(id)).map(({ id }) => id);
|
|
56
179
|
}
|
|
57
180
|
|
|
58
|
-
export function
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
';',
|
|
70
|
-
'new-tab',
|
|
71
|
-
'--title',
|
|
72
|
-
'Claude',
|
|
73
|
-
'-d',
|
|
74
|
-
cwd,
|
|
75
|
-
'powershell.exe',
|
|
76
|
-
'-NoExit',
|
|
77
|
-
'-Command',
|
|
78
|
-
claudeCommand,
|
|
79
|
-
';',
|
|
80
|
-
'new-tab',
|
|
81
|
-
'--title',
|
|
82
|
-
'Cursor',
|
|
83
|
-
'-d',
|
|
84
|
-
cwd,
|
|
85
|
-
'powershell.exe',
|
|
86
|
-
'-NoExit',
|
|
87
|
-
'-Command',
|
|
88
|
-
cursorCommand
|
|
89
|
-
];
|
|
181
|
+
export function createCliSelectionState({
|
|
182
|
+
inspectedClis,
|
|
183
|
+
defaultEnabledCliIds
|
|
184
|
+
}) {
|
|
185
|
+
const availableIds = getAvailableCliIds(inspectedClis);
|
|
186
|
+
return {
|
|
187
|
+
cursorIndex: getFirstAvailableCliIndex(inspectedClis),
|
|
188
|
+
selectedIds: normalizeEnabledCliIds(defaultEnabledCliIds, inspectedClis)
|
|
189
|
+
.filter((id) => availableIds.has(id)),
|
|
190
|
+
errorMessage: null
|
|
191
|
+
};
|
|
90
192
|
}
|
|
91
193
|
|
|
92
|
-
export function
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
})
|
|
194
|
+
export function moveCliSelectionCursor({
|
|
195
|
+
state,
|
|
196
|
+
inspectedClis,
|
|
197
|
+
direction
|
|
198
|
+
}) {
|
|
199
|
+
if (inspectedClis.every(({ available }) => !available)) {
|
|
200
|
+
return state;
|
|
201
|
+
}
|
|
98
202
|
|
|
99
|
-
|
|
203
|
+
let cursorIndex = state.cursorIndex;
|
|
204
|
+
for (let i = 0; i < inspectedClis.length; i++) {
|
|
205
|
+
cursorIndex = (cursorIndex + direction + inspectedClis.length) % inspectedClis.length;
|
|
206
|
+
if (inspectedClis[cursorIndex].available) {
|
|
207
|
+
return {
|
|
208
|
+
...state,
|
|
209
|
+
cursorIndex,
|
|
210
|
+
errorMessage: null
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return state;
|
|
100
216
|
}
|
|
101
217
|
|
|
102
|
-
export function
|
|
103
|
-
|
|
104
|
-
|
|
218
|
+
export function toggleCliSelectionAtCursor({
|
|
219
|
+
state,
|
|
220
|
+
inspectedClis
|
|
221
|
+
}) {
|
|
222
|
+
const cli = inspectedClis[state.cursorIndex];
|
|
223
|
+
if (!cli?.available) {
|
|
224
|
+
return state;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const selected = new Set(state.selectedIds);
|
|
228
|
+
if (selected.has(cli.id)) {
|
|
229
|
+
selected.delete(cli.id);
|
|
230
|
+
} else {
|
|
231
|
+
selected.add(cli.id);
|
|
105
232
|
}
|
|
106
233
|
|
|
107
|
-
return
|
|
108
|
-
|
|
109
|
-
|
|
234
|
+
return {
|
|
235
|
+
...state,
|
|
236
|
+
selectedIds: orderSelectedIds(selected, inspectedClis),
|
|
237
|
+
errorMessage: null
|
|
238
|
+
};
|
|
110
239
|
}
|
|
111
240
|
|
|
112
|
-
export function
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
241
|
+
export function confirmCliSelection({
|
|
242
|
+
state,
|
|
243
|
+
inspectedClis
|
|
244
|
+
}) {
|
|
245
|
+
const availableIds = getAvailableCliIds(inspectedClis);
|
|
246
|
+
const selectedIds = orderSelectedIds(
|
|
247
|
+
state.selectedIds.filter((id) => availableIds.has(id)),
|
|
248
|
+
inspectedClis
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
if (selectedIds.length === 0) {
|
|
252
|
+
return {
|
|
253
|
+
confirmed: false,
|
|
254
|
+
state: {
|
|
255
|
+
...state,
|
|
256
|
+
selectedIds,
|
|
257
|
+
errorMessage: EMPTY_SELECTION_MESSAGE
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
}
|
|
120
261
|
|
|
121
|
-
|
|
262
|
+
return {
|
|
263
|
+
confirmed: true,
|
|
264
|
+
selectedIds
|
|
265
|
+
};
|
|
122
266
|
}
|
|
123
267
|
|
|
124
|
-
export function
|
|
125
|
-
|
|
268
|
+
export function CliSelectionPrompt({
|
|
269
|
+
inspectedClis,
|
|
270
|
+
defaultEnabledCliIds,
|
|
271
|
+
onSubmit,
|
|
272
|
+
onCancel
|
|
273
|
+
}) {
|
|
274
|
+
const { exit } = useApp();
|
|
275
|
+
const [state, setState] = useState(() => createCliSelectionState({
|
|
276
|
+
inspectedClis,
|
|
277
|
+
defaultEnabledCliIds
|
|
278
|
+
}));
|
|
126
279
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
280
|
+
useInput((input, key) => {
|
|
281
|
+
if (key.upArrow) {
|
|
282
|
+
setState((current) => moveCliSelectionCursor({
|
|
283
|
+
state: current,
|
|
284
|
+
inspectedClis,
|
|
285
|
+
direction: -1
|
|
286
|
+
}));
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
131
289
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
290
|
+
if (key.downArrow) {
|
|
291
|
+
setState((current) => moveCliSelectionCursor({
|
|
292
|
+
state: current,
|
|
293
|
+
inspectedClis,
|
|
294
|
+
direction: 1
|
|
295
|
+
}));
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
136
298
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
299
|
+
if (input === ' ') {
|
|
300
|
+
setState((current) => toggleCliSelectionAtCursor({
|
|
301
|
+
state: current,
|
|
302
|
+
inspectedClis
|
|
303
|
+
}));
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (key.return) {
|
|
308
|
+
const result = confirmCliSelection({
|
|
309
|
+
state,
|
|
310
|
+
inspectedClis
|
|
311
|
+
});
|
|
142
312
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
313
|
+
if (result.confirmed) {
|
|
314
|
+
onSubmit(result.selectedIds);
|
|
315
|
+
exit();
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
setState(result.state);
|
|
320
|
+
return;
|
|
148
321
|
}
|
|
149
|
-
return 1;
|
|
150
|
-
}
|
|
151
322
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
323
|
+
if (input === 'q' || key.escape) {
|
|
324
|
+
onCancel();
|
|
325
|
+
exit();
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
const selected = new Set(state.selectedIds);
|
|
330
|
+
return React.createElement(
|
|
331
|
+
Box,
|
|
332
|
+
{ flexDirection: 'column' },
|
|
333
|
+
React.createElement(Text, { bold: true }, 'Select CLI tools to launch'),
|
|
334
|
+
...inspectedClis.map((cli, index) => {
|
|
335
|
+
const focused = index === state.cursorIndex;
|
|
336
|
+
const marker = focused ? '>' : ' ';
|
|
337
|
+
const checked = selected.has(cli.id) ? '[x]' : '[ ]';
|
|
338
|
+
const availability = cli.available ? 'available' : 'not found in PATH';
|
|
339
|
+
const color = cli.available && focused ? 'cyan' : undefined;
|
|
340
|
+
|
|
341
|
+
return React.createElement(
|
|
342
|
+
Text,
|
|
343
|
+
{
|
|
344
|
+
key: cli.id,
|
|
345
|
+
color,
|
|
346
|
+
dimColor: !cli.available
|
|
347
|
+
},
|
|
348
|
+
`${marker} ${checked} ${cli.label} - ${availability}`
|
|
349
|
+
);
|
|
350
|
+
}),
|
|
351
|
+
state.errorMessage
|
|
352
|
+
? React.createElement(Text, { color: 'red' }, state.errorMessage)
|
|
353
|
+
: null,
|
|
354
|
+
React.createElement(
|
|
355
|
+
Text,
|
|
356
|
+
{ dimColor: true },
|
|
357
|
+
'Use Up/Down to move, Space to toggle, Enter to open, q/Esc to cancel.'
|
|
358
|
+
)
|
|
359
|
+
);
|
|
155
360
|
}
|
|
156
361
|
|
|
157
|
-
export function
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
362
|
+
export async function promptForEnabledCliIds({
|
|
363
|
+
input = process.stdin,
|
|
364
|
+
output = process.stdout,
|
|
365
|
+
inspectedClis,
|
|
366
|
+
defaultEnabledCliIds,
|
|
367
|
+
renderFn = render
|
|
368
|
+
}) {
|
|
369
|
+
return new Promise((resolve, reject) => {
|
|
370
|
+
let app;
|
|
371
|
+
let settled = false;
|
|
372
|
+
const settle = (callback, value) => {
|
|
373
|
+
if (settled) {
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
161
376
|
|
|
162
|
-
|
|
163
|
-
|
|
377
|
+
settled = true;
|
|
378
|
+
callback(value);
|
|
379
|
+
app?.unmount();
|
|
380
|
+
};
|
|
164
381
|
|
|
165
|
-
|
|
382
|
+
app = renderFn(
|
|
383
|
+
React.createElement(CliSelectionPrompt, {
|
|
384
|
+
inspectedClis,
|
|
385
|
+
defaultEnabledCliIds,
|
|
386
|
+
onSubmit: (selectedIds) => settle(resolve, selectedIds),
|
|
387
|
+
onCancel: () => settle(reject, new CliSelectionCancelledError())
|
|
388
|
+
}),
|
|
389
|
+
{
|
|
390
|
+
stdin: input,
|
|
391
|
+
stdout: output,
|
|
392
|
+
stderr: output,
|
|
393
|
+
exitOnCtrlC: false
|
|
394
|
+
}
|
|
395
|
+
);
|
|
396
|
+
});
|
|
166
397
|
}
|
|
398
|
+
|
|
399
|
+
export function buildWtArgs({ cwd, tabs, shellCommand = DEFAULT_WINDOWS_SHELL_COMMAND }) {
|
|
400
|
+
const result = [];
|
|
401
|
+
for (let i = 0; i < tabs.length; i++) {
|
|
402
|
+
if (i > 0) result.push(';');
|
|
403
|
+
result.push(
|
|
404
|
+
'new-tab',
|
|
405
|
+
'--title',
|
|
406
|
+
tabs[i].title,
|
|
407
|
+
'-d',
|
|
408
|
+
cwd,
|
|
409
|
+
shellCommand,
|
|
410
|
+
'-NoExit',
|
|
411
|
+
'-ExecutionPolicy',
|
|
412
|
+
'Bypass',
|
|
413
|
+
'-Command',
|
|
414
|
+
tabs[i].command
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
return result;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function shellQuote(value) {
|
|
421
|
+
return `'${String(value).replaceAll("'", "'\\''")}'`;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function appleScriptQuote(value) {
|
|
425
|
+
return JSON.stringify(String(value));
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
export function buildMacTerminalScript({ cwd, tabs }) {
|
|
429
|
+
const commands = tabs.map(({ command }) => `cd ${shellQuote(cwd)} && ${command}`);
|
|
430
|
+
const lines = [
|
|
431
|
+
'tell application "Terminal"',
|
|
432
|
+
' activate'
|
|
433
|
+
];
|
|
434
|
+
|
|
435
|
+
for (const command of commands) {
|
|
436
|
+
lines.push(` do script ${appleScriptQuote(command)}`);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
lines.push('end tell');
|
|
440
|
+
return lines.join('\n');
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
export function buildMacTerminalArgs({ cwd, tabs }) {
|
|
444
|
+
return ['-e', buildMacTerminalScript({ cwd, tabs })];
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
export function commandExists(command, { env = process.env, platform = process.platform } = {}) {
|
|
448
|
+
const executable = platform === 'win32' ? 'where.exe' : 'sh';
|
|
449
|
+
const args = platform === 'win32'
|
|
450
|
+
? [command]
|
|
451
|
+
: ['-lc', `command -v ${shellQuote(command)}`];
|
|
452
|
+
|
|
453
|
+
const result = spawnSync(executable, args, {
|
|
454
|
+
env,
|
|
455
|
+
stdio: 'ignore',
|
|
456
|
+
windowsHide: true
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
return result.status === 0;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function getSystemWindowsPowerShellPath(env = process.env) {
|
|
463
|
+
const systemRoot = env.SystemRoot ?? env.SYSTEMROOT ?? env.windir ?? env.WINDIR;
|
|
464
|
+
if (!systemRoot) {
|
|
465
|
+
return null;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
return pathWin32.join(systemRoot, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe');
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
export function getWindowsShellCommand({
|
|
472
|
+
env = process.env,
|
|
473
|
+
platform = process.platform,
|
|
474
|
+
commandExistsFn = commandExists,
|
|
475
|
+
fileExistsFn = existsSync
|
|
476
|
+
} = {}) {
|
|
477
|
+
if (platform !== 'win32') {
|
|
478
|
+
return null;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
if (commandExistsFn('pwsh.exe', { env, platform })) {
|
|
482
|
+
return 'pwsh.exe';
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
if (commandExistsFn('powershell.exe', { env, platform })) {
|
|
486
|
+
return 'powershell.exe';
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const systemPowerShellPath = getSystemWindowsPowerShellPath(env);
|
|
490
|
+
if (systemPowerShellPath && fileExistsFn(systemPowerShellPath)) {
|
|
491
|
+
return systemPowerShellPath;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
return null;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
export function getMissingRequirements({
|
|
498
|
+
platform = process.platform,
|
|
499
|
+
env = process.env,
|
|
500
|
+
commandExistsFn = commandExists
|
|
501
|
+
} = {}) {
|
|
502
|
+
const requirements = HARD_REQUIREMENTS_BY_PLATFORM[platform];
|
|
503
|
+
if (!requirements) {
|
|
504
|
+
return ['Windows or macOS is required.'];
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const missing = requirements
|
|
508
|
+
.filter(({ command }) => !commandExistsFn(command, { env, platform }))
|
|
509
|
+
.map(({ label }) => `${label} was not found in PATH.`);
|
|
510
|
+
|
|
511
|
+
if (platform === 'win32' && !getWindowsShellCommand({ env, platform, commandExistsFn })) {
|
|
512
|
+
missing.push('PowerShell (pwsh.exe or powershell.exe) was not found.');
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
return missing;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
export function getAvailableCliTabs({
|
|
519
|
+
env = process.env,
|
|
520
|
+
platform = process.platform,
|
|
521
|
+
commandExistsFn = commandExists
|
|
522
|
+
} = {}) {
|
|
523
|
+
return CLI_COMMANDS.filter(({ command }) => commandExistsFn(command, { env, platform }));
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
export function getMissingCliLabels({
|
|
527
|
+
env = process.env,
|
|
528
|
+
platform = process.platform,
|
|
529
|
+
commandExistsFn = commandExists
|
|
530
|
+
} = {}) {
|
|
531
|
+
return CLI_COMMANDS
|
|
532
|
+
.filter(({ command }) => !commandExistsFn(command, { env, platform }))
|
|
533
|
+
.map(({ label }) => label);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
export function launchTerminals({ cwd = process.cwd(), env = process.env, platform = process.platform, tabs = CLI_COMMANDS } = {}) {
|
|
537
|
+
const executable = platform === 'win32' ? 'wt.exe' : 'osascript';
|
|
538
|
+
const shellCommand = platform === 'win32' ? getWindowsShellCommand({ env, platform }) : undefined;
|
|
539
|
+
if (platform === 'win32' && !shellCommand) {
|
|
540
|
+
throw new Error('PowerShell (pwsh.exe or powershell.exe) was not found.');
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
const args = platform === 'win32'
|
|
544
|
+
? buildWtArgs({ cwd, tabs, shellCommand })
|
|
545
|
+
: buildMacTerminalArgs({ cwd, tabs });
|
|
546
|
+
|
|
547
|
+
const child = spawn(executable, args, {
|
|
548
|
+
cwd,
|
|
549
|
+
env,
|
|
550
|
+
detached: true,
|
|
551
|
+
stdio: 'ignore',
|
|
552
|
+
windowsHide: false
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
child.unref();
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
export async function main(args = process.argv.slice(2), options = {}) {
|
|
559
|
+
const parsed = parseArgs(args);
|
|
560
|
+
|
|
561
|
+
if (parsed.action === 'help') {
|
|
562
|
+
console.log(HELP_TEXT.trimEnd());
|
|
563
|
+
return 0;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
if (parsed.action === 'version') {
|
|
567
|
+
console.log(getPackageVersion());
|
|
568
|
+
return 0;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
if (parsed.action === 'error') {
|
|
572
|
+
console.error(parsed.message);
|
|
573
|
+
console.error(`Run "${COMMAND} --help" for usage.`);
|
|
574
|
+
return 1;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
const missing = getMissingRequirements(options);
|
|
578
|
+
if (missing.length > 0) {
|
|
579
|
+
console.error(`${COMMAND} cannot start:`);
|
|
580
|
+
for (const message of missing) {
|
|
581
|
+
console.error(`- ${message}`);
|
|
582
|
+
}
|
|
583
|
+
return 1;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
const inspectedClis = inspectCliCommands(options);
|
|
587
|
+
const missingClis = getMissingLabelsFromInspection(inspectedClis);
|
|
588
|
+
for (const label of missingClis) {
|
|
589
|
+
console.warn(`Warning: ${label} was not found in PATH, skipping.`);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
const availableTabs = getAvailableTabsFromInspection(inspectedClis);
|
|
593
|
+
if (availableTabs.length === 0) {
|
|
594
|
+
console.error(`${COMMAND} cannot start: no CLI tools found in PATH.`);
|
|
595
|
+
return 1;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
let tabsToLaunch = availableTabs;
|
|
599
|
+
if (parsed.interactive) {
|
|
600
|
+
const input = options.input ?? process.stdin;
|
|
601
|
+
const output = options.output ?? process.stdout;
|
|
602
|
+
if (!isInteractiveTerminal({ input, output })) {
|
|
603
|
+
console.error(`${COMMAND} cannot prompt in a non-interactive terminal.`);
|
|
604
|
+
console.error(`Run "${COMMAND} --all" to open every available CLI without prompting.`);
|
|
605
|
+
return 1;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
const config = readConfig(options);
|
|
609
|
+
let enabledClis;
|
|
610
|
+
try {
|
|
611
|
+
enabledClis = await promptForEnabledCliIds({
|
|
612
|
+
input,
|
|
613
|
+
output,
|
|
614
|
+
inspectedClis,
|
|
615
|
+
defaultEnabledCliIds: config.enabledClis,
|
|
616
|
+
renderFn: options.renderFn
|
|
617
|
+
});
|
|
618
|
+
} catch (error) {
|
|
619
|
+
if (error instanceof CliSelectionCancelledError) {
|
|
620
|
+
console.error(`${COMMAND} cancelled.`);
|
|
621
|
+
return 1;
|
|
622
|
+
}
|
|
167
623
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
624
|
+
throw error;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
try {
|
|
628
|
+
writeConfig({ ...options, enabledClis });
|
|
629
|
+
} catch (error) {
|
|
630
|
+
console.warn(`Warning: could not save CLI selection: ${error.message}`);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
tabsToLaunch = filterTabsByEnabledCliIds(availableTabs, enabledClis);
|
|
634
|
+
if (tabsToLaunch.length === 0) {
|
|
635
|
+
console.error(`${COMMAND} cannot start: no selected CLI tools are available.`);
|
|
636
|
+
return 1;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
const launchTerminalsFn = options.launchTerminalsFn ?? launchTerminals;
|
|
641
|
+
launchTerminalsFn({ ...options, tabs: tabsToLaunch });
|
|
642
|
+
const launched = tabsToLaunch.map(({ title }) => title).join(', ');
|
|
643
|
+
const terminalName = (options.platform ?? process.platform) === 'win32' ? 'Windows Terminal' : 'Terminal.app';
|
|
644
|
+
console.log(`Opened ${launched} in ${terminalName}.`);
|
|
645
|
+
return 0;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
export function isEntrypoint(argvPath = process.argv[1], moduleUrl = import.meta.url) {
|
|
649
|
+
if (!argvPath) {
|
|
650
|
+
return false;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
const argvRealPath = realpathSync(resolve(argvPath));
|
|
654
|
+
const moduleRealPath = realpathSync(fileURLToPath(moduleUrl));
|
|
655
|
+
|
|
656
|
+
return argvRealPath === moduleRealPath;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
if (isEntrypoint()) {
|
|
660
|
+
main()
|
|
661
|
+
.then((exitCode) => {
|
|
662
|
+
process.exitCode = exitCode;
|
|
663
|
+
})
|
|
664
|
+
.catch((error) => {
|
|
665
|
+
console.error(error.message);
|
|
666
|
+
process.exitCode = 1;
|
|
667
|
+
});
|
|
668
|
+
}
|
package/package.json
CHANGED
|
@@ -1,45 +1,78 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "start-ai-cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
5
|
-
"type": "module",
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
"
|
|
23
|
-
"cli",
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
"
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
"
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
"
|
|
44
|
-
|
|
45
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "start-ai-cli",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Launch selected Codex CLI, Claude Code, and Cursor CLI tools from one project directory in Windows Terminal or macOS Terminal.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "bin/start-ai-cli.js",
|
|
7
|
+
"exports": "./bin/start-ai-cli.js",
|
|
8
|
+
"bin": {
|
|
9
|
+
"start-ai-cli": "bin/start-ai-cli.js"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"test": "node --test",
|
|
13
|
+
"pack:dry-run": "npm pack --dry-run",
|
|
14
|
+
"publish:dry-run": "npm publish --dry-run"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"bin/",
|
|
18
|
+
"README.md",
|
|
19
|
+
"LICENSE"
|
|
20
|
+
],
|
|
21
|
+
"keywords": [
|
|
22
|
+
"start-ai-cli",
|
|
23
|
+
"open-ai-cli",
|
|
24
|
+
"ai-cli",
|
|
25
|
+
"ai-agent",
|
|
26
|
+
"ai-coding",
|
|
27
|
+
"ai-coding-agent",
|
|
28
|
+
"coding-agent",
|
|
29
|
+
"codex",
|
|
30
|
+
"codex-cli",
|
|
31
|
+
"openai-codex",
|
|
32
|
+
"claude",
|
|
33
|
+
"claude-code",
|
|
34
|
+
"claude-code-cli",
|
|
35
|
+
"cursor",
|
|
36
|
+
"cursor-cli",
|
|
37
|
+
"cursor-agent",
|
|
38
|
+
"cli",
|
|
39
|
+
"cli-launcher",
|
|
40
|
+
"developer-tools",
|
|
41
|
+
"terminal",
|
|
42
|
+
"terminal-tabs",
|
|
43
|
+
"windows-terminal",
|
|
44
|
+
"macos",
|
|
45
|
+
"macos-terminal",
|
|
46
|
+
"productivity"
|
|
47
|
+
],
|
|
48
|
+
"author": {
|
|
49
|
+
"name": "guoxiao0521",
|
|
50
|
+
"url": "https://github.com/guoxiao0521"
|
|
51
|
+
},
|
|
52
|
+
"license": "MIT",
|
|
53
|
+
"repository": {
|
|
54
|
+
"type": "git",
|
|
55
|
+
"url": "git+https://github.com/guoxiao0521/open-ai-cli.git"
|
|
56
|
+
},
|
|
57
|
+
"bugs": {
|
|
58
|
+
"url": "https://github.com/guoxiao0521/open-ai-cli/issues"
|
|
59
|
+
},
|
|
60
|
+
"homepage": "https://github.com/guoxiao0521/open-ai-cli#readme",
|
|
61
|
+
"os": [
|
|
62
|
+
"win32",
|
|
63
|
+
"darwin"
|
|
64
|
+
],
|
|
65
|
+
"dependencies": {
|
|
66
|
+
"ink": "^7.0.6",
|
|
67
|
+
"react": "^19.2.7"
|
|
68
|
+
},
|
|
69
|
+
"devDependencies": {
|
|
70
|
+
"ink-testing-library": "^4.0.0"
|
|
71
|
+
},
|
|
72
|
+
"engines": {
|
|
73
|
+
"node": ">=22"
|
|
74
|
+
},
|
|
75
|
+
"publishConfig": {
|
|
76
|
+
"registry": "https://registry.npmjs.org/"
|
|
77
|
+
}
|
|
78
|
+
}
|