wtsm 1.0.2 → 1.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/README.md CHANGED
@@ -7,7 +7,7 @@ A CLI tool to define, save, and launch named Windows Terminal sessions with spec
7
7
  Ensure the tool is linked globally:
8
8
 
9
9
  ```bash
10
- npm link
10
+ npm i wtsm -g
11
11
  ```
12
12
 
13
13
  ## Usage Guide
@@ -70,6 +70,6 @@ _Opens a new Windows Terminal window with all configured tabs._
70
70
 
71
71
  ### 4 Not implemented features
72
72
 
73
- - clean scroll page,
74
- - Automatically use current shell
73
+ - currently only supports powershell
74
+ - Automatically use current shell profile
75
75
  - Pane capture
package/dist/index.mjs ADDED
@@ -0,0 +1,363 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import chalk from "chalk";
4
+ import fs from "fs";
5
+ import path from "path";
6
+ import os from "os";
7
+ import inquirer from "inquirer";
8
+ import readline from "readline";
9
+ import { spawn } from "child_process";
10
+
11
+ //#region src/lib/session.ts
12
+ const SESSION_FILE = path.join(os.homedir(), ".wts-sessions.json");
13
+ function loadSessions() {
14
+ if (!fs.existsSync(SESSION_FILE)) return {};
15
+ try {
16
+ const data = fs.readFileSync(SESSION_FILE, "utf-8");
17
+ return JSON.parse(data);
18
+ } catch {
19
+ console.error("Error reading session file.");
20
+ return {};
21
+ }
22
+ }
23
+ function saveSessions(sessions) {
24
+ fs.writeFileSync(SESSION_FILE, JSON.stringify(sessions, null, 2));
25
+ }
26
+
27
+ //#endregion
28
+ //#region src/commands/create.ts
29
+ function createCommand() {
30
+ const cmd = new Command("create");
31
+ cmd.arguments("<name>");
32
+ cmd.description("Create a new session config");
33
+ cmd.action((name) => {
34
+ const sessions = loadSessions();
35
+ if (sessions[name]) {
36
+ console.log(chalk.yellow(`Session '${name}' already exists.`));
37
+ return;
38
+ }
39
+ sessions[name] = [];
40
+ saveSessions(sessions);
41
+ console.log(chalk.green(`Session '${name}' created.`));
42
+ });
43
+ return cmd;
44
+ }
45
+
46
+ //#endregion
47
+ //#region src/commands/add.ts
48
+ async function addToSession(sessionName, cwd) {
49
+ const sessions = loadSessions();
50
+ if (!sessions[sessionName]) {
51
+ console.log(chalk.red(`Session '${sessionName}' not found. Use 's create ${sessionName}' first.`));
52
+ return;
53
+ }
54
+ const handleExit = (str, key) => {
55
+ if (key && (key.ctrl && key.name === "c" || key.name === "q" || key.name === "escape")) process.exit(0);
56
+ };
57
+ process.stdin.on("keypress", handleExit);
58
+ try {
59
+ const { command } = await inquirer.prompt([{
60
+ type: "input",
61
+ name: "command",
62
+ message: " Command to run on start (optional):"
63
+ }]);
64
+ const newTab = {
65
+ path: cwd,
66
+ command: command.trim() || null
67
+ };
68
+ sessions[sessionName].push(newTab);
69
+ saveSessions(sessions);
70
+ console.log(chalk.green(`Added current path to session '${sessionName}'.`));
71
+ } finally {
72
+ process.stdin.removeListener("keypress", handleExit);
73
+ }
74
+ }
75
+ function addCommand() {
76
+ const cmd = new Command("add");
77
+ cmd.arguments("[name]");
78
+ cmd.description("Add current path to a session");
79
+ cmd.action(async (nameOrIndex) => {
80
+ const sessions = loadSessions();
81
+ const sessionNames = Object.keys(sessions);
82
+ if (sessionNames.length === 0) {
83
+ console.log(chalk.red("No sessions found. Create one first with 's create <name>'"));
84
+ return;
85
+ }
86
+ let targetSession = nameOrIndex;
87
+ if (!targetSession) {
88
+ console.log(chalk.cyan.bold(" Available Sessions"));
89
+ console.log(chalk.gray(" ------------------"));
90
+ sessionNames.forEach((name, idx) => {
91
+ console.log(` ${chalk.yellow(idx + 1)}. ${chalk.white(name)}`);
92
+ });
93
+ console.log();
94
+ const handleExit = (str, key) => {
95
+ if (key && (key.ctrl && key.name === "c" || key.name === "q" || key.name === "escape")) process.exit(0);
96
+ };
97
+ process.stdin.on("keypress", handleExit);
98
+ let answer;
99
+ try {
100
+ answer = await inquirer.prompt([{
101
+ type: "input",
102
+ name: "input",
103
+ message: " Select session (number or name):",
104
+ validate: (input) => {
105
+ if (input === "q") return true;
106
+ if (!input) return "Please enter a value";
107
+ const num = parseInt(input, 10);
108
+ if (!isNaN(num)) {
109
+ if (num < 1 || num > sessionNames.length) return "Invalid number";
110
+ } else if (!sessions[input]) return "Session not found";
111
+ return true;
112
+ }
113
+ }]);
114
+ } finally {
115
+ process.stdin.removeListener("keypress", handleExit);
116
+ }
117
+ if (answer.input === "q") process.exit(0);
118
+ const num = parseInt(answer.input, 10);
119
+ if (!isNaN(num)) targetSession = sessionNames[num - 1];
120
+ else targetSession = answer.input;
121
+ } else if (nameOrIndex) {
122
+ const num = parseInt(nameOrIndex, 10);
123
+ if (!isNaN(num)) {
124
+ if (num >= 1 && num <= sessionNames.length) targetSession = sessionNames[num - 1];
125
+ }
126
+ }
127
+ if (targetSession) await addToSession(targetSession, process.cwd());
128
+ });
129
+ return cmd;
130
+ }
131
+
132
+ //#endregion
133
+ //#region src/commands/list.ts
134
+ function interactiveList() {
135
+ const sessions = loadSessions();
136
+ const sessionNames = Object.keys(sessions);
137
+ if (sessionNames.length === 0) {
138
+ console.log("No sessions found.");
139
+ return Promise.resolve();
140
+ }
141
+ let view = "sessions";
142
+ let selectedIndex = 0;
143
+ let activeSessionName = null;
144
+ let tabs = [];
145
+ let renderedLines = 0;
146
+ const { stdin, stdout } = process;
147
+ if (!process.stdin.isTTY) {
148
+ console.log(chalk.red("Interactive mode requires a TTY. Please run in a terminal."));
149
+ return Promise.resolve();
150
+ }
151
+ readline.emitKeypressEvents(stdin);
152
+ stdin.setRawMode(true);
153
+ stdin.resume();
154
+ stdin.setEncoding("utf8");
155
+ stdout.write("\x1B[?25l");
156
+ function cleanup() {
157
+ stdout.write("\x1B[?25h");
158
+ stdin.setRawMode(false);
159
+ stdin.pause();
160
+ stdin.removeListener("keypress", handleInput);
161
+ }
162
+ function render() {
163
+ if (renderedLines === 0) stdout.write("\x1B[2J\x1B[H");
164
+ else stdout.write(`\x1b[${renderedLines}A\x1b[0J`);
165
+ if (view === "sessions") {
166
+ console.log(chalk.cyan.bold(" Windows Terminal Sessions"));
167
+ console.log(chalk.gray(" -------------------------"));
168
+ sessionNames.forEach((name, idx) => {
169
+ if (idx === selectedIndex) console.log(chalk.green.bold(`> ${name}`));
170
+ else console.log(` ${name}`);
171
+ });
172
+ console.log(chalk.gray("\n (↑/↓: Move, Enter/→: Open, Ctrl+D: Delete, q/Esc: Exit)"));
173
+ renderedLines = 4 + sessionNames.length;
174
+ } else if (view === "tabs") {
175
+ console.log(chalk.cyan.bold(` Session: ${activeSessionName}`));
176
+ console.log(chalk.gray(" -------------------------"));
177
+ if (tabs.length === 0) console.log(" (Empty Session)");
178
+ else tabs.forEach((tab, idx) => {
179
+ const title = tab.path + (tab.command ? ` [${tab.command}]` : "");
180
+ if (idx === selectedIndex) console.log(chalk.green.bold(`> ${idx + 1}. ${title}`));
181
+ else console.log(` ${idx + 1}. ${title}`);
182
+ });
183
+ console.log(chalk.gray("\n (←: Back, Ctrl+D: Delete, q/Esc: Exit)"));
184
+ renderedLines = 4 + tabs.length;
185
+ }
186
+ }
187
+ const handleInput = (str, key) => {
188
+ if (!key) return;
189
+ if (key.sequence === "" || key.name === "q" || key.name === "escape") {
190
+ cleanup();
191
+ process.exit(0);
192
+ }
193
+ if (key.ctrl && key.name === "d") {
194
+ if (view === "sessions") {
195
+ const nameToDelete = sessionNames[selectedIndex];
196
+ if (nameToDelete) {
197
+ delete sessions[nameToDelete];
198
+ saveSessions(sessions);
199
+ const idx = sessionNames.indexOf(nameToDelete);
200
+ sessionNames.splice(idx, 1);
201
+ if (selectedIndex >= sessionNames.length) selectedIndex = Math.max(0, sessionNames.length - 1);
202
+ render();
203
+ }
204
+ } else if (view === "tabs") {
205
+ const currentTabs = sessions[activeSessionName];
206
+ if (currentTabs && currentTabs.length > 0) {
207
+ currentTabs.splice(selectedIndex, 1);
208
+ saveSessions(sessions);
209
+ tabs = currentTabs;
210
+ if (selectedIndex >= tabs.length) selectedIndex = Math.max(0, tabs.length - 1);
211
+ render();
212
+ }
213
+ }
214
+ return;
215
+ }
216
+ if (key.name === "up") {
217
+ selectedIndex--;
218
+ const max = view === "sessions" ? sessionNames.length : (sessions[activeSessionName] || []).length;
219
+ if (max > 0) {
220
+ if (selectedIndex < 0) selectedIndex = max - 1;
221
+ render();
222
+ }
223
+ }
224
+ if (key.name === "down") {
225
+ selectedIndex++;
226
+ const max = view === "sessions" ? sessionNames.length : (sessions[activeSessionName] || []).length;
227
+ if (max > 0) {
228
+ if (selectedIndex >= max) selectedIndex = 0;
229
+ render();
230
+ }
231
+ }
232
+ if (key.name === "right" || key.name === "return" || key.name === "enter") {
233
+ if (view === "sessions") {
234
+ activeSessionName = sessionNames[selectedIndex];
235
+ tabs = sessions[activeSessionName];
236
+ if (tabs) {
237
+ view = "tabs";
238
+ selectedIndex = 0;
239
+ render();
240
+ }
241
+ }
242
+ }
243
+ if (key.name === "left") {
244
+ if (view === "tabs") {
245
+ view = "sessions";
246
+ const prevIdx = sessionNames.indexOf(activeSessionName);
247
+ selectedIndex = prevIdx >= 0 ? prevIdx : 0;
248
+ activeSessionName = null;
249
+ tabs = [];
250
+ render();
251
+ }
252
+ }
253
+ };
254
+ render();
255
+ stdin.on("keypress", handleInput);
256
+ return new Promise(() => {});
257
+ }
258
+ function listCommand() {
259
+ const cmd = new Command("ls");
260
+ cmd.alias("list");
261
+ cmd.arguments("[name]");
262
+ cmd.description("List sessions (interactive) or tabs in a session");
263
+ cmd.action((name) => {
264
+ if (!name) interactiveList();
265
+ else {
266
+ const sessions = loadSessions();
267
+ if (!sessions[name]) {
268
+ console.log(chalk.red(`Session '${name}' not found.`));
269
+ return;
270
+ }
271
+ console.log(chalk.cyan(`Session: ${name}`));
272
+ const tabs = sessions[name];
273
+ if (tabs.length === 0) console.log(" (Empty)");
274
+ else tabs.forEach((tab, idx) => {
275
+ console.log(` ${chalk.bold(idx + 1)}. Path: ${tab.path}`);
276
+ if (tab.command) console.log(` Cmd: ${chalk.gray(tab.command)}`);
277
+ });
278
+ }
279
+ });
280
+ return cmd;
281
+ }
282
+
283
+ //#endregion
284
+ //#region src/commands/restore.ts
285
+ function restoreSession(sessionName) {
286
+ const sessions = loadSessions();
287
+ if (!sessions[sessionName]) {
288
+ console.log(chalk.red(`Session '${sessionName}' not found.`));
289
+ console.log("Available sessions:", Object.keys(sessions).join(", "));
290
+ return;
291
+ }
292
+ const tabs = sessions[sessionName];
293
+ if (tabs.length === 0) {
294
+ console.log(chalk.yellow(`Session '${sessionName}' is empty.`));
295
+ return;
296
+ }
297
+ const args = [];
298
+ tabs.forEach((tab, index) => {
299
+ if (index > 0) {
300
+ args.push(";");
301
+ args.push("new-tab");
302
+ }
303
+ args.push("-p");
304
+ args.push("Windows PowerShell");
305
+ if (tab.path) {
306
+ args.push("-d");
307
+ args.push(tab.path);
308
+ }
309
+ if (tab.command) {
310
+ args.push("powershell");
311
+ args.push("-NoExit");
312
+ args.push("-Command");
313
+ args.push(tab.command);
314
+ }
315
+ });
316
+ console.log(chalk.blue(`Launching session '${sessionName}'...`));
317
+ spawn("wt", args, {
318
+ detached: true,
319
+ stdio: "ignore",
320
+ shell: false
321
+ }).unref();
322
+ }
323
+
324
+ //#endregion
325
+ //#region src/lib/utils.ts
326
+ function setupKeypressEvents() {
327
+ readline.emitKeypressEvents(process.stdin);
328
+ }
329
+
330
+ //#endregion
331
+ //#region src/cli.ts
332
+ setupKeypressEvents();
333
+ function createCLI() {
334
+ const program = new Command();
335
+ program.name("s").description("Windows Terminal Session Manager").version("1.0.3");
336
+ program.addCommand(createCommand());
337
+ program.addCommand(addCommand());
338
+ program.addCommand(listCommand());
339
+ return program;
340
+ }
341
+ const rawArgs = process.argv.slice(2);
342
+ if (rawArgs.length > 0 && ![
343
+ "create",
344
+ "add",
345
+ "ls",
346
+ "list",
347
+ "help",
348
+ "--help",
349
+ "-h",
350
+ "--version",
351
+ "-V"
352
+ ].includes(rawArgs[0])) {
353
+ const sessionName = rawArgs[0];
354
+ if (rawArgs[1] === "add") addToSession(sessionName, process.cwd());
355
+ else restoreSession(sessionName);
356
+ } else createCLI().parse(process.argv);
357
+
358
+ //#endregion
359
+ //#region src/index.ts
360
+ createCLI();
361
+
362
+ //#endregion
363
+ export { };
package/package.json CHANGED
@@ -1,20 +1,48 @@
1
1
  {
2
2
  "name": "wtsm",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "Windows Terminal Session Manager",
5
- "main": "index.js",
5
+ "type": "module",
6
+ "main": "./dist/index.mjs",
6
7
  "bin": {
7
- "s": "./index.js"
8
+ "s": "./dist/index.mjs"
9
+ },
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "engines": {
14
+ "node": ">=18"
8
15
  },
9
16
  "scripts": {
10
- "test": "echo \"Error: no test specified\" && exit 1"
17
+ "build": "tsdown",
18
+ "dev": "tsx src/index.ts",
19
+ "typecheck": "tsc --noEmit"
11
20
  },
12
- "author": "",
13
- "license": "ISC",
14
- "type": "module",
21
+ "keywords": [
22
+ "windows-terminal",
23
+ "session-manager",
24
+ "cli",
25
+ "terminal",
26
+ "wt"
27
+ ],
28
+ "homepage": "https://github.com/adityacodepublic/wts-cli#readme",
29
+ "bugs": {
30
+ "url": "https://github.com/adityacodepublic/wts-cli/issues"
31
+ },
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "https://github.com/adityacodepublic/wts-cli.git"
35
+ },
36
+ "author": "adityacodepublic (https://github.com/adityacodepublic)",
37
+ "license": "MIT",
15
38
  "dependencies": {
16
39
  "chalk": "^5.6.2",
17
40
  "commander": "^14.0.2",
18
41
  "inquirer": "^13.2.1"
42
+ },
43
+ "devDependencies": {
44
+ "tsdown": "^0.20.1",
45
+ "tsx": "^4.21.0",
46
+ "typescript": "^5.9.3"
19
47
  }
20
48
  }
package/index.js DELETED
@@ -1,427 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { Command } from 'commander';
4
- import inquirer from 'inquirer';
5
- import chalk from 'chalk';
6
- import fs from 'fs';
7
- import path from 'path';
8
- import os from 'os';
9
- import { spawn } from 'child_process';
10
- import readline from 'readline';
11
-
12
- const SESSION_FILE = path.join(os.homedir(), '.wts-sessions.json');
13
- const program = new Command();
14
- readline.emitKeypressEvents(process.stdin);
15
-
16
- // --- Data Helpers ---
17
-
18
- function loadSessions() {
19
- if (!fs.existsSync(SESSION_FILE)) {
20
- return {};
21
- }
22
- try {
23
- return JSON.parse(fs.readFileSync(SESSION_FILE, 'utf-8'));
24
- } catch (e) {
25
- console.error(chalk.red('Error reading session file.'));
26
- return {};
27
- }
28
- }
29
-
30
- function saveSessions(sessions) {
31
- fs.writeFileSync(SESSION_FILE, JSON.stringify(sessions, null, 2));
32
- }
33
-
34
- // --- Logic Helpers ---
35
-
36
- async function addToSession(sessionName, cwd) {
37
- const sessions = loadSessions();
38
- if (!sessions[sessionName]) {
39
- console.log(chalk.red(`Session '${sessionName}' not found. Use 's create ${sessionName}' first.`));
40
- return;
41
- }
42
-
43
- const handleExit = (str, key) => {
44
- if (key && (key.ctrl && key.name === 'c' || key.name === 'q' || key.name === 'escape')) {
45
- process.exit(0);
46
- }
47
- };
48
- process.stdin.on('keypress', handleExit);
49
-
50
- try {
51
- const { command } = await inquirer.prompt([
52
- {
53
- type: 'input',
54
- name: 'command',
55
- message: ' Command to run on start (optional):',
56
- }
57
- ]);
58
-
59
- sessions[sessionName].push({
60
- path: cwd,
61
- command: command.trim() || null
62
- });
63
-
64
- saveSessions(sessions);
65
- console.log(chalk.green(`Added current path to session '${sessionName}'.`));
66
- } finally {
67
- process.stdin.removeListener('keypress', handleExit);
68
- }
69
- }
70
-
71
- function restoreSession(sessionName) {
72
- const sessions = loadSessions();
73
- if (!sessions[sessionName]) {
74
- console.log(chalk.red(`Session '${sessionName}' not found.`));
75
- console.log('Available sessions:', Object.keys(sessions).join(', '));
76
- return;
77
- }
78
-
79
- const tabs = sessions[sessionName];
80
- if (tabs.length === 0) {
81
- console.log(chalk.yellow(`Session '${sessionName}' is empty.`));
82
- return;
83
- }
84
-
85
- const args = [];
86
-
87
- tabs.forEach((tab, index) => {
88
- if (index > 0) {
89
- args.push(';');
90
- args.push('new-tab');
91
- }
92
-
93
- // Force PowerShell profile
94
- args.push('-p');
95
- args.push('Windows PowerShell');
96
-
97
- if (tab.path) {
98
- args.push('-d');
99
- args.push(tab.path);
100
- }
101
-
102
- if (tab.command) {
103
- // PowerShell command execution
104
- args.push('powershell'); // Redundant if profile is set, but ensures command syntax works if profile is weird
105
- args.push('-NoExit');
106
- args.push('-Command');
107
- args.push(tab.command);
108
- }
109
- });
110
-
111
- console.log(chalk.blue(`Launching session '${sessionName}'...`));
112
- const subprocess = spawn('wt', args, { detached: true, stdio: 'ignore', shell: false });
113
- subprocess.unref();
114
- }
115
-
116
-
117
-
118
- async function interactiveList() {
119
- const sessions = loadSessions();
120
- const sessionNames = Object.keys(sessions);
121
-
122
- if (sessionNames.length === 0) {
123
- console.log("No sessions found.");
124
- return;
125
- }
126
-
127
- // State
128
- let view = 'sessions'; // 'sessions' | 'tabs'
129
- let selectedIndex = 0;
130
- let activeSessionName = null;
131
- let tabs = [];
132
-
133
- // Input Handling
134
- const { stdin, stdout } = process;
135
- readline.emitKeypressEvents(stdin); // Required for keypress events
136
- stdin.setRawMode(true);
137
- stdin.resume();
138
- stdin.setEncoding('utf8');
139
-
140
- // Helper to hide cursor
141
- stdout.write('\x1B[?25l');
142
-
143
- function cleanup() {
144
- stdout.write('\x1B[?25h'); // Show cursor
145
- stdin.setRawMode(false);
146
- stdin.pause();
147
- stdin.removeListener('keypress', handleInput);
148
- }
149
-
150
- function render() {
151
- // Clear screen for full-screen feel, or use clearDown if preferred.
152
- // console.clear() is robust.
153
- console.clear();
154
-
155
- if (view === 'sessions') {
156
- console.log(chalk.cyan.bold(" Windows Terminal Sessions"));
157
- console.log(chalk.gray(" -------------------------"));
158
-
159
- sessionNames.forEach((name, idx) => {
160
- if (idx === selectedIndex) {
161
- console.log(chalk.green.bold(`> ${name}`));
162
- } else {
163
- console.log(` ${name}`);
164
- }
165
- });
166
-
167
- console.log(chalk.gray("\n (↑/↓: Move, Enter/→: Open, Ctrl+D: Delete, q/Esc: Exit)"));
168
-
169
- } else if (view === 'tabs') {
170
- console.log(chalk.cyan.bold(` Session: ${activeSessionName}`));
171
- console.log(chalk.gray(" -------------------------"));
172
-
173
- if (tabs.length === 0) {
174
- console.log(" (Empty Session)");
175
- } else {
176
- tabs.forEach((tab, idx) => {
177
- const title = tab.path + (tab.command ? ` [${tab.command}]` : '');
178
- if (idx === selectedIndex) {
179
- console.log(chalk.green.bold(`> ${idx + 1}. ${title}`));
180
- } else {
181
- console.log(` ${idx + 1}. ${title}`);
182
- }
183
- });
184
- }
185
-
186
- console.log(chalk.gray("\n (←: Back, Ctrl+D: Delete, q/Esc: Exit)"));
187
- }
188
- }
189
-
190
- const handleInput = (str, key) => {
191
- if (!key) return;
192
-
193
- // Ctrl+C (End of Text) or 'q' or Esc
194
- if (key.sequence === '\u0003' || key.name === 'q' || key.name === 'escape') {
195
- cleanup();
196
- process.exit(0);
197
- }
198
-
199
- // Ctrl+D (Delete)
200
- if (key.ctrl && key.name === 'd') {
201
- if (view === 'sessions') {
202
- const nameToDelete = sessionNames[selectedIndex];
203
- if (nameToDelete) {
204
- delete sessions[nameToDelete];
205
- saveSessions(sessions);
206
- // Refresh list
207
- const idx = sessionNames.indexOf(nameToDelete);
208
- sessionNames.splice(idx, 1);
209
- if (selectedIndex >= sessionNames.length) selectedIndex = Math.max(0, sessionNames.length - 1);
210
- render();
211
- }
212
- } else if (view === 'tabs') {
213
- const currentTabs = sessions[activeSessionName];
214
- if (currentTabs && currentTabs.length > 0) {
215
- currentTabs.splice(selectedIndex, 1);
216
- saveSessions(sessions);
217
- if (selectedIndex >= currentTabs.length) selectedIndex = Math.max(0, currentTabs.length - 1);
218
- render();
219
- }
220
- }
221
- return;
222
- }
223
-
224
- // Up Arrow
225
- if (key.name === 'up') {
226
- selectedIndex--;
227
- const max = view === 'sessions' ? sessionNames.length : (sessions[activeSessionName] || []).length;
228
- if (max > 0) {
229
- if (selectedIndex < 0) selectedIndex = max - 1; // Wrap top
230
- render();
231
- }
232
- }
233
-
234
- // Down Arrow
235
- if (key.name === 'down') {
236
- selectedIndex++;
237
- const max = view === 'sessions' ? sessionNames.length : (sessions[activeSessionName] || []).length;
238
- if (max > 0) {
239
- if (selectedIndex >= max) selectedIndex = 0; // Wrap bottom
240
- render();
241
- }
242
- }
243
-
244
- // Right Arrow or Enter
245
- if (key.name === 'right' || key.name === 'return' || key.name === 'enter') {
246
- if (view === 'sessions') {
247
- activeSessionName = sessionNames[selectedIndex];
248
- tabs = sessions[activeSessionName];
249
- if (tabs) { // Only switch if valid
250
- view = 'tabs';
251
- selectedIndex = 0; // Reset index for tabs list
252
- render();
253
- }
254
- }
255
- }
256
-
257
- // Left Arrow
258
- if (key.name === 'left') {
259
- if (view === 'tabs') {
260
- view = 'sessions';
261
- // Try to restore index of previous session
262
- const prevIdx = sessionNames.indexOf(activeSessionName);
263
- selectedIndex = prevIdx >= 0 ? prevIdx : 0;
264
-
265
- activeSessionName = null;
266
- tabs = [];
267
- render();
268
- }
269
- }
270
- };
271
-
272
- // Initial Render
273
- render();
274
- stdin.on('keypress', handleInput);
275
-
276
- // Return a promise that never resolves so the program waits for input
277
- return new Promise(() => { });
278
- }
279
-
280
-
281
-
282
-
283
- // --- Commands ---
284
-
285
-
286
- program
287
- .name('s')
288
- .description('Windows Terminal Session Manager')
289
- .version('1.0.0');
290
-
291
- // 1. s create <name>
292
- program
293
- .command('create <name>')
294
- .description('Create a new session config')
295
- .action((name) => {
296
- const sessions = loadSessions();
297
- if (sessions[name]) {
298
- console.log(chalk.yellow(`Session '${name}' already exists.`));
299
- return;
300
- }
301
- sessions[name] = [];
302
- saveSessions(sessions);
303
- console.log(chalk.green(`Session '${name}' created.`));
304
- });
305
-
306
- // 2. s add [name] (Modified)
307
- program
308
- .command('add [name]')
309
- .description('Add current path to a session')
310
- .action(async (nameOrIndex) => {
311
- const sessions = loadSessions();
312
- const sessionNames = Object.keys(sessions);
313
-
314
- if (sessionNames.length === 0) {
315
- console.log(chalk.red("No sessions found. Create one first with 's create <name>'"));
316
- return;
317
- }
318
-
319
- let targetSession = nameOrIndex;
320
-
321
- if (!targetSession) {
322
- // Print numbered list
323
- console.log(chalk.cyan.bold(" Available Sessions"));
324
- console.log(chalk.gray(" ------------------"));
325
- sessionNames.forEach((name, idx) => {
326
- console.log(` ${chalk.yellow(idx + 1)}. ${chalk.white(name)}`);
327
- });
328
- console.log();
329
-
330
- const handleExit = (str, key) => {
331
- if (key && (key.ctrl && key.name === 'c' || key.name === 'q' || key.name === 'escape')) {
332
- process.exit(0);
333
- }
334
- };
335
- process.stdin.on('keypress', handleExit);
336
-
337
- let answer;
338
- try {
339
- answer = await inquirer.prompt([
340
- {
341
- type: 'input',
342
- name: 'input',
343
- message: ' Select session (number or name):',
344
- validate: (input) => {
345
- if (input === 'q') return true;
346
- if (!input) return "Please enter a value";
347
- const num = parseInt(input, 10);
348
- if (!isNaN(num)) {
349
- if (num < 1 || num > sessionNames.length) return "Invalid number";
350
- } else {
351
- if (!sessions[input]) return "Session not found";
352
- }
353
- return true;
354
- }
355
- }
356
- ]);
357
- } finally {
358
- process.stdin.removeListener('keypress', handleExit);
359
- }
360
-
361
- if (answer.input === 'q') process.exit(0);
362
-
363
- const num = parseInt(answer.input, 10);
364
- if (!isNaN(num)) {
365
- targetSession = sessionNames[num - 1];
366
- } else {
367
- targetSession = answer.input;
368
- }
369
- } else {
370
- // Check if user provided a number directly in CLI: `s add 1`
371
- const num = parseInt(nameOrIndex, 10);
372
- if (!isNaN(num)) {
373
- if (num >= 1 && num <= sessionNames.length) {
374
- targetSession = sessionNames[num - 1];
375
- }
376
- }
377
- }
378
-
379
- await addToSession(targetSession, process.cwd());
380
- });
381
-
382
- // 5. s ls (Renamed & Interactive)
383
- program
384
- .command('ls [name]')
385
- .alias('list')
386
- .description('List sessions (interactive) or tabs in a session')
387
- .action((name) => {
388
- if (!name) {
389
- // Interactive Mode
390
- interactiveList();
391
- } else {
392
- // List tabs in specific session (Legacy/Scriptable mode)
393
- const sessions = loadSessions();
394
- if (!sessions[name]) {
395
- console.log(chalk.red(`Session '${name}' not found.`));
396
- return;
397
- }
398
- console.log(chalk.cyan(`Session: ${name}`));
399
- const tabs = sessions[name];
400
- if (tabs.length === 0) {
401
- console.log(" (Empty)");
402
- } else {
403
- tabs.forEach((tab, idx) => {
404
- console.log(` ${chalk.bold(idx + 1)}. Path: ${tab.path}`);
405
- if (tab.command) console.log(` Cmd: ${chalk.gray(tab.command)}`);
406
- });
407
- }
408
- }
409
- });
410
-
411
-
412
- // --- Custom Dispatcher ---
413
- const rawArgs = process.argv.slice(2);
414
- const knownCommands = ['create', 'add', 'ls', 'list', 'help', '--help', '-h', '--version', '-V'];
415
-
416
- if (rawArgs.length > 0 && !knownCommands.includes(rawArgs[0])) {
417
- const sessionName = rawArgs[0];
418
- const secondArg = rawArgs[1];
419
-
420
- if (secondArg === 'add') {
421
- addToSession(sessionName, process.cwd());
422
- } else {
423
- restoreSession(sessionName);
424
- }
425
- } else {
426
- program.parse(process.argv);
427
- }