@bobfrankston/winpos 2.0.40 → 2.0.42

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
@@ -15,13 +15,13 @@ TypeScript implementation of WinPos - A Windows window positioning utility for m
15
15
  ## Installation
16
16
 
17
17
  ```bash
18
- npm install @bobfrankston/tswinpos
18
+ npm install @bobfrankston/winpos
19
19
  ```
20
20
 
21
21
  Or with Bun:
22
22
 
23
23
  ```bash
24
- bun add @bobfrankston/tswinpos
24
+ bun add @bobfrankston/winpos
25
25
  ```
26
26
 
27
27
  ## Usage
@@ -30,34 +30,34 @@ bun add @bobfrankston/tswinpos
30
30
 
31
31
  ```bash
32
32
  # Move window to screen 0 at position (100, 200)
33
- tswinpos "Notepad" 100 200 0
33
+ winpos "Notepad" 100 200 0
34
34
 
35
35
  # Move window using percentages
36
- tswinpos "Chrome*" 50% 50% 1
36
+ winpos "Chrome*" 50% 50% 1
37
37
 
38
38
  # Set window size as well
39
- tswinpos "Visual Studio Code" 0 0 0 1920 1080
39
+ winpos "Visual Studio Code" 0 0 0 1920 1080
40
40
 
41
41
  # List all windows
42
- tswinpos *
42
+ winpos *
43
43
 
44
44
  # Get screen count
45
- tswinpos -c
45
+ winpos -c
46
46
 
47
47
  # Debug mode
48
- tswinpos -d "Firefox" 0 0 1
48
+ winpos -d "Firefox" 0 0 1
49
49
  ```
50
50
 
51
51
  ### Pattern Matching
52
52
 
53
- - **Exact match**: `tswinpos "Notepad" 0 0 0`
54
- - **Prefix match**: `tswinpos "Chrome*" 0 0 0`
55
- - **Regex match**: `tswinpos "/Visual.*Code/" 0 0 0`
53
+ - **Exact match**: `winpos "Notepad" 0 0 0`
54
+ - **Prefix match**: `winpos "Chrome*" 0 0 0`
55
+ - **Regex match**: `winpos "/Visual.*Code/" 0 0 0`
56
56
 
57
57
  ### As a Library
58
58
 
59
59
  ```typescript
60
- import { run } from '@bobfrankston/tswinpos';
60
+ import { run } from '@bobfrankston/winpos';
61
61
 
62
62
  // Move a window programmatically
63
63
  run(['Chrome*', '0', '0', '1', '1920', '1080']);
@@ -89,7 +89,7 @@ Screens are numbered starting from 0, sorted by position:
89
89
  ## Parameters
90
90
 
91
91
  ```
92
- tswinpos [-d -c *] <window title> <x> <y> <screen> [<width> <height>]
92
+ winpos [-d -c *] <window title> <x> <y> <screen> [<width> <height>]
93
93
  ```
94
94
 
95
95
  - `<window title>` - Window title (exact, prefix with `*`, or regex with `/.../ `)
package/index.d.ts CHANGED
@@ -7,6 +7,22 @@ import { user32, RECT } from './ffi-wrapper.js';
7
7
  import { enumerateScreens, sortScreens, ScreenInfo } from './screens.js';
8
8
  import { enumerateWindows, findWindowsByPattern, WindowInfo, WindowState, getWindowState, setWindowState, isMinimized, isMaximized, isNormal, minimizeWindow, maximizeWindow, restoreWindow } from './windows.js';
9
9
  import { enumerateTabs, TabInfo, mightHaveTabs, TAB_ENUMERATION_NOTE } from './tabs.js';
10
+ export interface WindowConfig {
11
+ name: string;
12
+ regex?: boolean;
13
+ pos?: {
14
+ x: number | string;
15
+ y: number | string;
16
+ screen?: number;
17
+ };
18
+ size?: {
19
+ w: number | string;
20
+ h: number | string;
21
+ };
22
+ minimize?: boolean;
23
+ maximize?: boolean;
24
+ }
25
+ export type ConfigFile = WindowConfig | WindowConfig[];
10
26
  export { RECT, ScreenInfo, WindowInfo, WindowState, TabInfo, enumerateScreens, sortScreens, enumerateWindows, findWindowsByPattern, getWindowState, setWindowState, isMinimized, isMaximized, isNormal, minimizeWindow, maximizeWindow, restoreWindow, enumerateTabs, mightHaveTabs, TAB_ENUMERATION_NOTE, user32 };
11
27
  /**
12
28
  * Convert screen-relative coordinates to global coordinates
package/index.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":";AACA;;;GAGG;AAEH,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,EACH,gBAAgB,EAChB,oBAAoB,EAEpB,UAAU,EACV,WAAW,EACX,cAAc,EACd,cAAc,EACd,WAAW,EACX,WAAW,EACX,QAAQ,EACR,cAAc,EACd,cAAc,EACd,aAAa,EAChB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAKxF,OAAO,EACH,IAAI,EACJ,UAAU,EACV,UAAU,EACV,WAAW,EACX,OAAO,EACP,gBAAgB,EAChB,WAAW,EACX,gBAAgB,EAChB,oBAAoB,EACpB,cAAc,EACd,cAAc,EACd,WAAW,EACX,WAAW,EACX,QAAQ,EACR,cAAc,EACd,cAAc,EACd,aAAa,EACb,aAAa,EACb,aAAa,EACb,oBAAoB,EACpB,MAAM,EACT,CAAC;AAQF;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAyBlG;AAkLD,wBAAgB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,QAwEjC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":";AACA;;;GAGG;AAEH,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,EACH,gBAAgB,EAChB,oBAAoB,EAEpB,UAAU,EACV,WAAW,EACX,cAAc,EACd,cAAc,EACd,WAAW,EACX,WAAW,EACX,QAAQ,EACR,cAAc,EACd,cAAc,EACd,aAAa,EAChB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAMxF,MAAM,WAAW,YAAY;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,GAAG,CAAC,EAAE;QAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAClE,IAAI,CAAC,EAAE;QAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC;IAClD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,MAAM,UAAU,GAAG,YAAY,GAAG,YAAY,EAAE,CAAC;AAkBvD,OAAO,EACH,IAAI,EACJ,UAAU,EACV,UAAU,EACV,WAAW,EACX,OAAO,EACP,gBAAgB,EAChB,WAAW,EACX,gBAAgB,EAChB,oBAAoB,EACpB,cAAc,EACd,cAAc,EACd,WAAW,EACX,WAAW,EACX,QAAQ,EACR,cAAc,EACd,cAAc,EACd,aAAa,EACb,aAAa,EACb,aAAa,EACb,oBAAoB,EACpB,MAAM,EACT,CAAC;AA8RF;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAyBlG;AAmND,wBAAgB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,QAuHjC"}
package/index.js CHANGED
@@ -7,11 +7,269 @@ import { user32 } from './ffi-wrapper.js';
7
7
  import { enumerateScreens, sortScreens } from './screens.js';
8
8
  import { enumerateWindows, findWindowsByPattern, loadIgnorePatterns, WindowState, getWindowState, setWindowState, isMinimized, isMaximized, isNormal, minimizeWindow, maximizeWindow, restoreWindow } from './windows.js';
9
9
  import { enumerateTabs, mightHaveTabs, TAB_ENUMERATION_NOTE } from './tabs.js';
10
- import { join } from 'path';
10
+ import { join, resolve, isAbsolute } from 'path';
11
+ import { readFileSync, writeFileSync, existsSync } from 'fs';
11
12
  import * as packageJson from './package.json' with { type: 'json' };
12
13
  // Re-export public API for library usage
13
14
  export { WindowState, enumerateScreens, sortScreens, enumerateWindows, findWindowsByPattern, getWindowState, setWindowState, isMinimized, isMaximized, isNormal, minimizeWindow, maximizeWindow, restoreWindow, enumerateTabs, mightHaveTabs, TAB_ENUMERATION_NOTE, user32 };
15
+ // WindowConfig and ConfigFile types are exported from interface definitions above
14
16
  // Note: screenPosToPos is exported separately below (after the function definition)
17
+ /**
18
+ * Load window config from JSON file
19
+ */
20
+ function loadConfigFile(filePath) {
21
+ const absPath = isAbsolute(filePath) ? filePath : resolve(process.cwd(), filePath);
22
+ if (!existsSync(absPath)) {
23
+ throw new Error(`Config file not found: ${absPath}`);
24
+ }
25
+ const content = readFileSync(absPath, 'utf-8');
26
+ return JSON.parse(content);
27
+ }
28
+ /**
29
+ * Save window config to JSON file
30
+ * If file exists and contains array, merge; otherwise create/overwrite
31
+ */
32
+ function saveConfigFile(filePath, config, merge = false) {
33
+ const absPath = isAbsolute(filePath) ? filePath : resolve(process.cwd(), filePath);
34
+ let finalConfig;
35
+ if (merge && existsSync(absPath)) {
36
+ const existing = loadConfigFile(absPath);
37
+ if (Array.isArray(existing)) {
38
+ // Find and update existing entry with same name, or append
39
+ const idx = existing.findIndex(e => e.name === config.name);
40
+ if (idx >= 0) {
41
+ existing[idx] = config;
42
+ }
43
+ else {
44
+ existing.push(config);
45
+ }
46
+ finalConfig = existing;
47
+ }
48
+ else {
49
+ // Convert single entry to array
50
+ if (existing.name === config.name) {
51
+ finalConfig = config; // Replace single entry
52
+ }
53
+ else {
54
+ finalConfig = [existing, config];
55
+ }
56
+ }
57
+ }
58
+ else {
59
+ finalConfig = config;
60
+ }
61
+ writeFileSync(absPath, JSON.stringify(finalConfig, null, 2), 'utf-8');
62
+ console.log(`Config saved to: ${absPath}`);
63
+ }
64
+ /**
65
+ * Build WindowConfig from CLI options
66
+ */
67
+ function buildConfigFromOptions(opts) {
68
+ if (!opts.windowTitle)
69
+ return null;
70
+ const config = {
71
+ name: opts.windowTitle
72
+ };
73
+ // Check if name is a regex pattern (enclosed in /.../)
74
+ if (config.name.startsWith('/') && config.name.endsWith('/')) {
75
+ config.regex = true;
76
+ config.name = config.name.slice(1, -1); // Store without delimiters
77
+ }
78
+ if (opts.pos) {
79
+ config.pos = { x: opts.pos.x, y: opts.pos.y };
80
+ if (opts.pos.screen !== undefined) {
81
+ config.pos.screen = opts.pos.screen;
82
+ }
83
+ }
84
+ if (opts.size) {
85
+ config.size = { w: opts.size.w, h: opts.size.h };
86
+ }
87
+ if (opts.minimize) {
88
+ config.minimize = true;
89
+ }
90
+ if (opts.maximize) {
91
+ config.maximize = true;
92
+ }
93
+ return config;
94
+ }
95
+ /**
96
+ * Parse CLI arguments into structured options
97
+ */
98
+ function parseArgs(args) {
99
+ const opts = {
100
+ debug: false,
101
+ debugVerbose: false,
102
+ showCount: false,
103
+ positionalArgs: []
104
+ };
105
+ let i = 0;
106
+ while (i < args.length) {
107
+ const arg = args[i];
108
+ // Check for .json file as first non-flag arg (auto-load)
109
+ if (!arg.startsWith('-') && arg.toLowerCase().endsWith('.json') && !opts.loadFile) {
110
+ opts.loadFile = arg;
111
+ i++;
112
+ continue;
113
+ }
114
+ if (arg.startsWith('-')) {
115
+ const flag = arg.substring(1).toLowerCase();
116
+ switch (flag) {
117
+ case 'd':
118
+ opts.debug = true;
119
+ break;
120
+ case 'dbg':
121
+ opts.debugVerbose = true;
122
+ break;
123
+ case 'c':
124
+ opts.showCount = true;
125
+ break;
126
+ case 'version':
127
+ case 'v':
128
+ console.log(`winpos version ${packageJson.default.version}`);
129
+ process.exit(0);
130
+ break;
131
+ case 'pos':
132
+ i++;
133
+ if (i < args.length) {
134
+ const parts = args[i].split(',');
135
+ opts.pos = { x: parts[0], y: parts[1] };
136
+ if (parts.length > 2) {
137
+ opts.pos.screen = parseInt(parts[2], 10);
138
+ }
139
+ }
140
+ break;
141
+ case 'size':
142
+ i++;
143
+ if (i < args.length) {
144
+ const parts = args[i].split(',');
145
+ opts.size = { w: parts[0], h: parts[1] };
146
+ }
147
+ break;
148
+ case 'load':
149
+ i++;
150
+ if (i < args.length) {
151
+ opts.loadFile = args[i];
152
+ }
153
+ break;
154
+ case 'save':
155
+ i++;
156
+ if (i < args.length) {
157
+ opts.saveFile = args[i];
158
+ }
159
+ break;
160
+ case 'title':
161
+ i++;
162
+ if (i < args.length) {
163
+ opts.windowTitle = args[i];
164
+ }
165
+ break;
166
+ case 'min':
167
+ opts.minimize = true;
168
+ break;
169
+ case 'max':
170
+ opts.maximize = true;
171
+ break;
172
+ case 'help':
173
+ case '?':
174
+ return opts; // Will trigger usage display
175
+ default:
176
+ // Unknown flag - could be negative number for position
177
+ if (/^-?\d+/.test(arg)) {
178
+ opts.positionalArgs.push(arg);
179
+ }
180
+ else {
181
+ console.log(`Unknown option: ${arg}`);
182
+ }
183
+ break;
184
+ }
185
+ }
186
+ else {
187
+ opts.positionalArgs.push(arg);
188
+ }
189
+ i++;
190
+ }
191
+ // First positional arg is window title (if using new flag-based syntax and -title not set)
192
+ const usingFlagSyntax = opts.pos || opts.size || opts.loadFile || opts.saveFile || opts.minimize || opts.maximize;
193
+ if (opts.positionalArgs.length > 0 && usingFlagSyntax) {
194
+ if (!opts.windowTitle) {
195
+ opts.windowTitle = opts.positionalArgs[0];
196
+ opts.positionalArgs = opts.positionalArgs.slice(1);
197
+ }
198
+ // Error if stray positional args remain when using flag-based syntax
199
+ if (opts.positionalArgs.length > 0) {
200
+ console.error(`Error: unexpected arguments: ${opts.positionalArgs.join(' ')}`);
201
+ console.error('When using flags, only window title should be positional');
202
+ process.exit(1);
203
+ }
204
+ }
205
+ return opts;
206
+ }
207
+ /**
208
+ * Apply a single window config
209
+ */
210
+ function applyWindowConfig(config, screensInfo) {
211
+ // Build pattern for matching
212
+ let pattern;
213
+ if (config.regex) {
214
+ pattern = `/${config.name}/`;
215
+ }
216
+ else {
217
+ pattern = config.name;
218
+ }
219
+ let matchedWindows = findWindowsByPattern(pattern);
220
+ matchedWindows = matchedWindows.filter(w => !w.title.toLowerCase().includes('tswinpos'));
221
+ if (matchedWindows.length === 0) {
222
+ console.log(`No windows found matching '${config.name}'`);
223
+ return;
224
+ }
225
+ // Handle minimize
226
+ if (config.minimize) {
227
+ for (const window of matchedWindows) {
228
+ if (dbgVerbose)
229
+ console.log(`[DBG] Minimizing: "${window.title}"`);
230
+ minimizeWindow(window.hwnd);
231
+ }
232
+ return;
233
+ }
234
+ // Handle maximize
235
+ if (config.maximize) {
236
+ for (const window of matchedWindows) {
237
+ if (dbgVerbose)
238
+ console.log(`[DBG] Maximizing: "${window.title}"`);
239
+ maximizeWindow(window.hwnd);
240
+ }
241
+ return;
242
+ }
243
+ // Handle positioning
244
+ if (!config.pos)
245
+ return;
246
+ const screenIndex = config.pos.screen ?? 0;
247
+ if (screenIndex < 0 || screenIndex >= screensInfo.length) {
248
+ console.log(`Invalid screen ${screenIndex} for window '${config.name}'`);
249
+ return;
250
+ }
251
+ const targetScreen = screensInfo[screenIndex];
252
+ const bounds = targetScreen.bounds;
253
+ const x = parseDimension(String(config.pos.x), bounds.Width) + bounds.Left;
254
+ const y = parseDimension(String(config.pos.y), bounds.Height) + bounds.Top;
255
+ let width = config.size ? parseDimension(String(config.size.w), bounds.Width) : 0;
256
+ let height = config.size ? parseDimension(String(config.size.h), bounds.Height) : 0;
257
+ for (const window of matchedWindows) {
258
+ let w = width, h = height;
259
+ if (w === 0)
260
+ w = window.rect.Right - window.rect.Left;
261
+ if (h === 0)
262
+ h = window.rect.Bottom - window.rect.Top;
263
+ if (dbgVerbose) {
264
+ console.log(`[DBG] Window: "${window.title}" -> Position: (${x}, ${y}) Size: ${w}x${h} Screen: ${screenIndex}`);
265
+ }
266
+ if (isMinimized(window.hwnd) || isMaximized(window.hwnd)) {
267
+ restoreWindow(window.hwnd);
268
+ }
269
+ user32.MoveWindow(window.hwnd, x, y, w, h, true);
270
+ user32.SetForegroundWindow(window.hwnd);
271
+ }
272
+ }
15
273
  let dbg = false;
16
274
  let dbgVerbose = false; // -dbg flag for detailed output
17
275
  let screens = [];
@@ -125,6 +383,27 @@ function getScreenForWindow(windowRect) {
125
383
  return screens.findIndex(s => s.primary) || 0;
126
384
  }
127
385
  const padn = (n, p = 6) => n.toString().padStart(p);
386
+ /**
387
+ * Show position info for windows matching pattern
388
+ */
389
+ function showWindowInfo(pattern) {
390
+ let matchedWindows = findWindowsByPattern(pattern);
391
+ matchedWindows = matchedWindows.filter(w => !w.title.toLowerCase().includes('tswinpos'));
392
+ if (matchedWindows.length === 0) {
393
+ console.log(`No windows found matching '${pattern}'`);
394
+ return;
395
+ }
396
+ for (const window of matchedWindows) {
397
+ const screenIndex = getScreenForWindow(window.rect);
398
+ const screen = screens[screenIndex];
399
+ const x = window.rect.Left - screen.bounds.Left;
400
+ const y = window.rect.Top - screen.bounds.Top;
401
+ const width = window.rect.Right - window.rect.Left;
402
+ const height = window.rect.Bottom - window.rect.Top;
403
+ console.log(`${window.title}`);
404
+ console.log(` pos: ${x},${y},${screenIndex} size: ${width},${height}`);
405
+ }
406
+ }
128
407
  function listAllWindows(includeIgnored = false) {
129
408
  console.log('====================================');
130
409
  console.log('Screens');
@@ -157,23 +436,32 @@ function listAllWindows(includeIgnored = false) {
157
436
  function usage(msg = '') {
158
437
  if (msg)
159
438
  console.log(msg);
160
- console.log('Usage: winpos [-d -dbg -c -version *] <window title> <x> <y> <screen> [<width> <height>]');
161
- console.log(' winpos <window title> min | -min');
162
- console.log(' -d debug, -dbg verbose output (version, title, position, size), -c return count, -version (or -v) show version');
163
- console.log(' where <screen> is a number from 0 to N-1, where N is the number of screens.');
164
- console.log(' <x> and <y> can be a number of pixels or a percentage (e.g. 50%)');
165
- console.log(' Use "min" or "-min" in place of <x> to minimize the window to the taskbar');
166
- console.log(' title can be a regex pattern (if enclosed in /.../) or a prefix (if it ends with \'*\')');
167
- console.log(' <width> and <height> are optional and can be a number of pixels or a percentage (e.g. 50%) or 0 (to keep the current size)');
168
- console.log('To list all windows: tswinpos *');
169
- console.log('To list all windows (including ignored): tswinpos **');
170
- console.log('To return the window count use -c');
439
+ console.log('Usage: winpos [options] <title> <x> <y> <screen> [<width> <height>]');
440
+ console.log(' winpos [options] <title> -pos x,y[,screen] [-size w,h]');
441
+ console.log(' winpos [options] -title <name> -pos x,y[,screen] [-size w,h]');
442
+ console.log(' winpos <title> min | -min');
443
+ console.log(' winpos <config.json>');
444
+ console.log(' winpos -load <config.json> [-save <output.json>]');
445
+ console.log();
446
+ console.log('Options:');
447
+ console.log(' -d Debug mode');
448
+ console.log(' -dbg Verbose debug output');
449
+ console.log(' -c Return screen count as exit code');
450
+ console.log(' -v/-version Show version');
451
+ console.log(' -title name Window title/pattern (alternative to positional)');
452
+ console.log(' -pos x,y[,s] Position (x,y with optional screen index)');
453
+ console.log(' -size w,h Window size (width,height)');
454
+ console.log(' -min Minimize window');
455
+ console.log(' -max Maximize window');
456
+ console.log(' -load file Load config from JSON file');
457
+ console.log(' -save file Save config to JSON file (merges if file exists)');
171
458
  console.log();
172
- console.log(' +---+---+');
173
- console.log(' | 2 | 3 |');
174
- console.log(' +---+---+');
175
- console.log(' | 0 | 1 |');
176
- console.log(' +---+---+');
459
+ console.log('Position/size values can be pixels or percentages (e.g. 50%)');
460
+ console.log('Title can be regex (/pattern/) or prefix (title*)');
461
+ console.log('* = list windows, ** = list all including ignored');
462
+ console.log();
463
+ console.log('JSON format: {"name":"app","regex":false,"pos":{"x":0,"y":0,"screen":0},"size":{"w":800,"h":600}}');
464
+ console.log(' or array: [{...}, {...}]');
177
465
  console.log();
178
466
  console.log('Screen | X Y | W H ');
179
467
  console.log('------------------------------------');
@@ -181,8 +469,6 @@ function usage(msg = '') {
181
469
  const screen = screens[i];
182
470
  console.log(`${i.toString().padStart(6)} | ${screen.bounds.Left.toString().padStart(5)} ${screen.bounds.Top.toString().padStart(5)} | ${screen.bounds.Width.toString().padStart(5)} ${screen.bounds.Height.toString().padStart(5)}`);
183
471
  }
184
- console.log();
185
- console.log('0-Lower right, 1-Lower Left, 2-Upper left, 3-Upper right');
186
472
  process.exit(1);
187
473
  }
188
474
  export function run(args) {
@@ -200,56 +486,94 @@ export function run(args) {
200
486
  // Load ignore patterns from the same directory as the script
201
487
  const ignoresPath = join(import.meta.dirname, 'ignores.txt');
202
488
  loadIgnorePatterns(ignoresPath);
203
- // Parse options
204
- while (args.length > 0 && args[0].startsWith('-')) {
205
- const opt = args[0].substring(1);
206
- switch (opt) {
207
- case 'd':
208
- dbg = true;
209
- console.log('Debug mode enabled.');
210
- break;
211
- case 'dbg':
212
- dbgVerbose = true;
213
- console.log(`[DBG] winpos version ${packageJson.default.version}`);
214
- break;
215
- case 'c':
216
- console.log(screens.length);
217
- process.exit(screens.length);
218
- break;
219
- case 'version':
220
- case 'v':
221
- console.log(`tswinpos version ${packageJson.default.version}`);
222
- process.exit(0);
223
- break;
224
- default:
225
- usage(`Unknown option: ${args[0]}`);
226
- break;
489
+ // Parse arguments using new parser
490
+ const opts = parseArgs(args);
491
+ // Set debug flags
492
+ dbg = opts.debug;
493
+ dbgVerbose = opts.debugVerbose;
494
+ if (dbg)
495
+ console.log('Debug mode enabled.');
496
+ if (dbgVerbose)
497
+ console.log(`[DBG] winpos version ${packageJson.default.version}`);
498
+ // Handle -c (screen count)
499
+ if (opts.showCount) {
500
+ console.log(screens.length);
501
+ process.exit(screens.length);
502
+ }
503
+ // Handle load file (JSON config)
504
+ if (opts.loadFile) {
505
+ try {
506
+ const config = loadConfigFile(opts.loadFile);
507
+ const configs = Array.isArray(config) ? config : [config];
508
+ // If -save specified with CLI params, merge new config into loaded
509
+ if (opts.saveFile && opts.windowTitle) {
510
+ const newConfig = buildConfigFromOptions(opts);
511
+ if (newConfig) {
512
+ // Apply the CLI-specified window first
513
+ applyWindowConfig(newConfig, screens);
514
+ // Save merged config
515
+ saveConfigFile(opts.saveFile, newConfig, true);
516
+ }
517
+ }
518
+ // Apply all loaded configs
519
+ for (const cfg of configs) {
520
+ applyWindowConfig(cfg, screens);
521
+ }
522
+ return;
523
+ }
524
+ catch (e) {
525
+ console.error(`Error loading config: ${e.message}`);
526
+ process.exit(1);
227
527
  }
228
- args = args.slice(1);
229
528
  }
230
- // Handle commands
231
- // console.log(DEBUG: args.length=', args.length, 'args[0]=', args[0], 'check=', args.length === 1 && args[0] === '*');
529
+ // Handle new flag-based syntax (-pos, -size, -min, -max)
530
+ if (opts.pos || opts.size || opts.minimize || opts.maximize) {
531
+ if (!opts.windowTitle) {
532
+ usage('Window title required when using -pos, -size, -min, or -max');
533
+ return;
534
+ }
535
+ const config = buildConfigFromOptions(opts);
536
+ if (config) {
537
+ applyWindowConfig(config, screens);
538
+ // Save if requested
539
+ if (opts.saveFile) {
540
+ saveConfigFile(opts.saveFile, config, existsSync(opts.saveFile));
541
+ }
542
+ }
543
+ return;
544
+ }
545
+ // If only -title provided (no -pos/-size/-min/-max), show window position info
546
+ if (opts.windowTitle && !opts.loadFile) {
547
+ showWindowInfo(opts.windowTitle);
548
+ return;
549
+ }
550
+ // Fall back to legacy positional argument handling
551
+ const posArgs = opts.positionalArgs;
232
552
  if (dbg)
233
- console.log(`DEBUG[${packageJson.default.version}]: ${args.join(' ')}`);
234
- if (args.length === 1 && args[0] === '*') {
553
+ console.log(`DEBUG[${packageJson.default.version}]: ${posArgs.join(' ')}`);
554
+ if (posArgs.length === 1 && posArgs[0] === '*') {
235
555
  listAllWindows();
236
556
  return;
237
557
  }
238
- if (args.length === 1 && args[0] === '**') {
558
+ if (posArgs.length === 1 && posArgs[0] === '**') {
239
559
  listAllWindows(true);
240
560
  return;
241
561
  }
242
562
  // Check for minimize command (title + min/-min)
243
- // TODO: Refactor to eliminate duplicate min/minimize check - consolidate with adjustWindow logic
244
- if (args.length === 2 && (args[1].toLowerCase() === 'min' || args[1].toLowerCase() === '-min')) {
245
- adjustWindow(args);
563
+ if (posArgs.length === 2 && (posArgs[1].toLowerCase() === 'min' || posArgs[1].toLowerCase() === '-min')) {
564
+ adjustWindow(posArgs);
565
+ return;
566
+ }
567
+ // Single positional arg = show window info
568
+ if (posArgs.length === 1) {
569
+ showWindowInfo(posArgs[0]);
246
570
  return;
247
571
  }
248
- if (args.length < 4) {
572
+ if (posArgs.length < 4) {
249
573
  usage();
250
574
  return;
251
575
  }
252
- adjustWindow(args);
576
+ adjustWindow(posArgs);
253
577
  }
254
578
  // CLI entry point using import.meta.main
255
579
  if (import.meta.main) {