workon 3.2.0 → 3.2.2
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/dist/cli.js +431 -364
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +114 -73
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -6
- package/dist/index.d.ts +19 -6
- package/dist/index.js +114 -73
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -204,6 +204,18 @@ var Project = class {
|
|
|
204
204
|
// src/lib/tmux.ts
|
|
205
205
|
var import_child_process = require("child_process");
|
|
206
206
|
var import_util = require("util");
|
|
207
|
+
|
|
208
|
+
// src/lib/sanitize.ts
|
|
209
|
+
function sanitizeForShell(input4) {
|
|
210
|
+
if (!input4) return "";
|
|
211
|
+
return input4.replace(/[^a-zA-Z0-9_\-.]/g, "_");
|
|
212
|
+
}
|
|
213
|
+
function escapeForSingleQuotes(input4) {
|
|
214
|
+
if (!input4) return "";
|
|
215
|
+
return input4.replace(/'/g, "'\\''");
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// src/lib/tmux.ts
|
|
207
219
|
var exec = (0, import_util.promisify)(import_child_process.exec);
|
|
208
220
|
var TmuxManager = class {
|
|
209
221
|
sessionPrefix = "workon-";
|
|
@@ -217,18 +229,18 @@ var TmuxManager = class {
|
|
|
217
229
|
}
|
|
218
230
|
async sessionExists(sessionName) {
|
|
219
231
|
try {
|
|
220
|
-
await exec(`tmux has-session -t
|
|
232
|
+
await exec(`tmux has-session -t '${escapeForSingleQuotes(sessionName)}'`);
|
|
221
233
|
return true;
|
|
222
234
|
} catch {
|
|
223
235
|
return false;
|
|
224
236
|
}
|
|
225
237
|
}
|
|
226
238
|
getSessionName(projectName) {
|
|
227
|
-
return `${this.sessionPrefix}${projectName}`;
|
|
239
|
+
return `${this.sessionPrefix}${sanitizeForShell(projectName)}`;
|
|
228
240
|
}
|
|
229
241
|
async killSession(sessionName) {
|
|
230
242
|
try {
|
|
231
|
-
await exec(`tmux kill-session -t
|
|
243
|
+
await exec(`tmux kill-session -t '${escapeForSingleQuotes(sessionName)}'`);
|
|
232
244
|
return true;
|
|
233
245
|
} catch {
|
|
234
246
|
return false;
|
|
@@ -236,43 +248,62 @@ var TmuxManager = class {
|
|
|
236
248
|
}
|
|
237
249
|
async createSplitSession(projectName, projectPath, claudeArgs = []) {
|
|
238
250
|
const sessionName = this.getSessionName(projectName);
|
|
251
|
+
const escapedSession = escapeForSingleQuotes(sessionName);
|
|
252
|
+
const escapedPath = escapeForSingleQuotes(projectPath);
|
|
239
253
|
if (await this.sessionExists(sessionName)) {
|
|
240
254
|
await this.killSession(sessionName);
|
|
241
255
|
}
|
|
242
256
|
const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
|
|
243
|
-
|
|
244
|
-
await exec(
|
|
245
|
-
|
|
257
|
+
const escapedClaudeCmd = escapeForSingleQuotes(claudeCommand);
|
|
258
|
+
await exec(
|
|
259
|
+
`tmux new-session -d -s '${escapedSession}' -c '${escapedPath}' '${escapedClaudeCmd}'`
|
|
260
|
+
);
|
|
261
|
+
await exec(`tmux split-window -h -t '${escapedSession}' -c '${escapedPath}'`);
|
|
262
|
+
await exec(`tmux select-pane -t '${escapedSession}:0.0'`);
|
|
246
263
|
return sessionName;
|
|
247
264
|
}
|
|
248
265
|
async createThreePaneSession(projectName, projectPath, claudeArgs = [], npmCommand = "npm run dev") {
|
|
249
266
|
const sessionName = this.getSessionName(projectName);
|
|
267
|
+
const escapedSession = escapeForSingleQuotes(sessionName);
|
|
268
|
+
const escapedPath = escapeForSingleQuotes(projectPath);
|
|
250
269
|
if (await this.sessionExists(sessionName)) {
|
|
251
270
|
await this.killSession(sessionName);
|
|
252
271
|
}
|
|
253
272
|
const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
await exec(
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
await exec(`tmux
|
|
273
|
+
const escapedClaudeCmd = escapeForSingleQuotes(claudeCommand);
|
|
274
|
+
const escapedNpmCmd = escapeForSingleQuotes(npmCommand);
|
|
275
|
+
await exec(
|
|
276
|
+
`tmux new-session -d -s '${escapedSession}' -c '${escapedPath}' '${escapedClaudeCmd}'`
|
|
277
|
+
);
|
|
278
|
+
await exec(`tmux split-window -h -t '${escapedSession}' -c '${escapedPath}'`);
|
|
279
|
+
await exec(
|
|
280
|
+
`tmux split-window -v -t '${escapedSession}:0.1' -c '${escapedPath}' '${escapedNpmCmd}'`
|
|
281
|
+
);
|
|
282
|
+
await exec(`tmux set-option -t '${escapedSession}:0.2' remain-on-exit on`);
|
|
283
|
+
await exec(`tmux resize-pane -t '${escapedSession}:0.2' -y 10`);
|
|
284
|
+
await exec(`tmux select-pane -t '${escapedSession}:0.0'`);
|
|
260
285
|
return sessionName;
|
|
261
286
|
}
|
|
262
287
|
async createTwoPaneNpmSession(projectName, projectPath, npmCommand = "npm run dev") {
|
|
263
288
|
const sessionName = this.getSessionName(projectName);
|
|
289
|
+
const escapedSession = escapeForSingleQuotes(sessionName);
|
|
290
|
+
const escapedPath = escapeForSingleQuotes(projectPath);
|
|
291
|
+
const escapedNpmCmd = escapeForSingleQuotes(npmCommand);
|
|
264
292
|
if (await this.sessionExists(sessionName)) {
|
|
265
293
|
await this.killSession(sessionName);
|
|
266
294
|
}
|
|
267
|
-
await exec(`tmux new-session -d -s
|
|
268
|
-
await exec(
|
|
269
|
-
|
|
270
|
-
|
|
295
|
+
await exec(`tmux new-session -d -s '${escapedSession}' -c '${escapedPath}'`);
|
|
296
|
+
await exec(
|
|
297
|
+
`tmux split-window -h -t '${escapedSession}' -c '${escapedPath}' '${escapedNpmCmd}'`
|
|
298
|
+
);
|
|
299
|
+
await exec(`tmux set-option -t '${escapedSession}:0.1' remain-on-exit on`);
|
|
300
|
+
await exec(`tmux select-pane -t '${escapedSession}:0.0'`);
|
|
271
301
|
return sessionName;
|
|
272
302
|
}
|
|
273
303
|
async attachToSession(sessionName) {
|
|
304
|
+
const escapedSession = escapeForSingleQuotes(sessionName);
|
|
274
305
|
if (process.env.TMUX) {
|
|
275
|
-
await exec(`tmux switch-client -t
|
|
306
|
+
await exec(`tmux switch-client -t '${escapedSession}'`);
|
|
276
307
|
} else {
|
|
277
308
|
const isITerm = process.env.TERM_PROGRAM === "iTerm.app" || process.env.LC_TERMINAL === "iTerm2" || !!process.env.ITERM_SESSION_ID;
|
|
278
309
|
const useiTermIntegration = isITerm && !process.env.TMUX_CC_NOT_SUPPORTED;
|
|
@@ -291,53 +322,64 @@ var TmuxManager = class {
|
|
|
291
322
|
}
|
|
292
323
|
buildShellCommands(projectName, projectPath, claudeArgs = []) {
|
|
293
324
|
const sessionName = this.getSessionName(projectName);
|
|
325
|
+
const escapedSession = escapeForSingleQuotes(sessionName);
|
|
326
|
+
const escapedPath = escapeForSingleQuotes(projectPath);
|
|
294
327
|
const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
|
|
328
|
+
const escapedClaudeCmd = escapeForSingleQuotes(claudeCommand);
|
|
295
329
|
return [
|
|
296
|
-
`# Create tmux split session for ${projectName}`,
|
|
297
|
-
`tmux has-session -t
|
|
298
|
-
`tmux new-session -d -s
|
|
299
|
-
`tmux split-window -h -t
|
|
300
|
-
`tmux select-pane -t
|
|
330
|
+
`# Create tmux split session for ${sanitizeForShell(projectName)}`,
|
|
331
|
+
`tmux has-session -t '${escapedSession}' 2>/dev/null && tmux kill-session -t '${escapedSession}'`,
|
|
332
|
+
`tmux new-session -d -s '${escapedSession}' -c '${escapedPath}' '${escapedClaudeCmd}'`,
|
|
333
|
+
`tmux split-window -h -t '${escapedSession}' -c '${escapedPath}'`,
|
|
334
|
+
`tmux select-pane -t '${escapedSession}:0.0'`,
|
|
301
335
|
this.getAttachCommand(sessionName)
|
|
302
336
|
];
|
|
303
337
|
}
|
|
304
338
|
buildThreePaneShellCommands(projectName, projectPath, claudeArgs = [], npmCommand = "npm run dev") {
|
|
305
339
|
const sessionName = this.getSessionName(projectName);
|
|
340
|
+
const escapedSession = escapeForSingleQuotes(sessionName);
|
|
341
|
+
const escapedPath = escapeForSingleQuotes(projectPath);
|
|
306
342
|
const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
|
|
343
|
+
const escapedClaudeCmd = escapeForSingleQuotes(claudeCommand);
|
|
344
|
+
const escapedNpmCmd = escapeForSingleQuotes(npmCommand);
|
|
307
345
|
return [
|
|
308
|
-
`# Create tmux three-pane session for ${projectName}`,
|
|
309
|
-
`tmux has-session -t
|
|
310
|
-
`tmux new-session -d -s
|
|
311
|
-
`tmux split-window -h -t
|
|
312
|
-
`tmux split-window -v -t
|
|
313
|
-
`tmux set-option -t
|
|
314
|
-
`tmux resize-pane -t
|
|
315
|
-
`tmux select-pane -t
|
|
346
|
+
`# Create tmux three-pane session for ${sanitizeForShell(projectName)}`,
|
|
347
|
+
`tmux has-session -t '${escapedSession}' 2>/dev/null && tmux kill-session -t '${escapedSession}'`,
|
|
348
|
+
`tmux new-session -d -s '${escapedSession}' -c '${escapedPath}' '${escapedClaudeCmd}'`,
|
|
349
|
+
`tmux split-window -h -t '${escapedSession}' -c '${escapedPath}'`,
|
|
350
|
+
`tmux split-window -v -t '${escapedSession}:0.1' -c '${escapedPath}' '${escapedNpmCmd}'`,
|
|
351
|
+
`tmux set-option -t '${escapedSession}:0.2' remain-on-exit on`,
|
|
352
|
+
`tmux resize-pane -t '${escapedSession}:0.2' -y 10`,
|
|
353
|
+
`tmux select-pane -t '${escapedSession}:0.0'`,
|
|
316
354
|
this.getAttachCommand(sessionName)
|
|
317
355
|
];
|
|
318
356
|
}
|
|
319
357
|
buildTwoPaneNpmShellCommands(projectName, projectPath, npmCommand = "npm run dev") {
|
|
320
358
|
const sessionName = this.getSessionName(projectName);
|
|
359
|
+
const escapedSession = escapeForSingleQuotes(sessionName);
|
|
360
|
+
const escapedPath = escapeForSingleQuotes(projectPath);
|
|
361
|
+
const escapedNpmCmd = escapeForSingleQuotes(npmCommand);
|
|
321
362
|
return [
|
|
322
|
-
`# Create tmux two-pane session with npm for ${projectName}`,
|
|
323
|
-
`tmux has-session -t
|
|
324
|
-
`tmux new-session -d -s
|
|
325
|
-
`tmux split-window -h -t
|
|
326
|
-
`tmux set-option -t
|
|
327
|
-
`tmux select-pane -t
|
|
363
|
+
`# Create tmux two-pane session with npm for ${sanitizeForShell(projectName)}`,
|
|
364
|
+
`tmux has-session -t '${escapedSession}' 2>/dev/null && tmux kill-session -t '${escapedSession}'`,
|
|
365
|
+
`tmux new-session -d -s '${escapedSession}' -c '${escapedPath}'`,
|
|
366
|
+
`tmux split-window -h -t '${escapedSession}' -c '${escapedPath}' '${escapedNpmCmd}'`,
|
|
367
|
+
`tmux set-option -t '${escapedSession}:0.1' remain-on-exit on`,
|
|
368
|
+
`tmux select-pane -t '${escapedSession}:0.0'`,
|
|
328
369
|
this.getAttachCommand(sessionName)
|
|
329
370
|
];
|
|
330
371
|
}
|
|
331
372
|
getAttachCommand(sessionName) {
|
|
373
|
+
const escapedSession = escapeForSingleQuotes(sessionName);
|
|
332
374
|
if (process.env.TMUX) {
|
|
333
|
-
return `tmux switch-client -t
|
|
375
|
+
return `tmux switch-client -t '${escapedSession}'`;
|
|
334
376
|
}
|
|
335
377
|
const isITerm = process.env.TERM_PROGRAM === "iTerm.app" || process.env.LC_TERMINAL === "iTerm2" || process.env.ITERM_SESSION_ID;
|
|
336
378
|
const useiTermIntegration = isITerm && !process.env.TMUX_CC_NOT_SUPPORTED;
|
|
337
379
|
if (useiTermIntegration) {
|
|
338
|
-
return `tmux -CC attach-session -t
|
|
380
|
+
return `tmux -CC attach-session -t '${escapedSession}'`;
|
|
339
381
|
}
|
|
340
|
-
return `tmux attach-session -t
|
|
382
|
+
return `tmux attach-session -t '${escapedSession}'`;
|
|
341
383
|
}
|
|
342
384
|
async listWorkonSessions() {
|
|
343
385
|
try {
|
|
@@ -397,7 +439,8 @@ var EnvironmentRecognizer = class {
|
|
|
397
439
|
const git = (0, import_simple_git.simpleGit)(gitDir.path);
|
|
398
440
|
const branchSummary = await git.branchLocal();
|
|
399
441
|
base.branch = branchSummary.current;
|
|
400
|
-
} catch {
|
|
442
|
+
} catch (error) {
|
|
443
|
+
this.log.debug(`Git branch detection failed: ${error.message}`);
|
|
401
444
|
}
|
|
402
445
|
}
|
|
403
446
|
return this.getProjectEnvironment(base, matching);
|
|
@@ -499,18 +542,22 @@ var CwdEvent = class {
|
|
|
499
542
|
const { project, isShellMode, shellCommands } = context;
|
|
500
543
|
const projectPath = project.path.path;
|
|
501
544
|
if (isShellMode) {
|
|
502
|
-
shellCommands.push(`
|
|
545
|
+
shellCommands.push(`pushd "${projectPath}" > /dev/null`);
|
|
503
546
|
} else {
|
|
504
547
|
const shell = process.env.SHELL || "/bin/bash";
|
|
505
|
-
(0, import_child_process2.spawn)(shell, [], {
|
|
548
|
+
const child = (0, import_child_process2.spawn)(shell, ["-i"], {
|
|
506
549
|
cwd: projectPath,
|
|
507
550
|
stdio: "inherit"
|
|
508
551
|
});
|
|
552
|
+
await new Promise((resolve, reject) => {
|
|
553
|
+
child.on("close", () => resolve());
|
|
554
|
+
child.on("error", (err) => reject(err));
|
|
555
|
+
});
|
|
509
556
|
}
|
|
510
557
|
},
|
|
511
558
|
generateShellCommand(context) {
|
|
512
559
|
const projectPath = context.project.path.path;
|
|
513
|
-
return [`
|
|
560
|
+
return [`pushd "${projectPath}" > /dev/null`];
|
|
514
561
|
}
|
|
515
562
|
};
|
|
516
563
|
}
|
|
@@ -569,7 +616,7 @@ var IdeEvent = class {
|
|
|
569
616
|
const projectPath = project.path.path;
|
|
570
617
|
const ide = project.ide || "code";
|
|
571
618
|
if (isShellMode) {
|
|
572
|
-
shellCommands.push(
|
|
619
|
+
shellCommands.push(`set +m; ${ide} "${projectPath}" &>/dev/null &`);
|
|
573
620
|
} else {
|
|
574
621
|
(0, import_child_process3.spawn)(ide, [projectPath], {
|
|
575
622
|
detached: true,
|
|
@@ -580,7 +627,7 @@ var IdeEvent = class {
|
|
|
580
627
|
generateShellCommand(context) {
|
|
581
628
|
const projectPath = context.project.path.path;
|
|
582
629
|
const ide = context.project.ide || "code";
|
|
583
|
-
return [
|
|
630
|
+
return [`set +m; ${ide} "${projectPath}" &>/dev/null &`];
|
|
584
631
|
}
|
|
585
632
|
};
|
|
586
633
|
}
|
|
@@ -1100,23 +1147,19 @@ var EventRegistryClass = class {
|
|
|
1100
1147
|
*/
|
|
1101
1148
|
registerEvents() {
|
|
1102
1149
|
for (const EventClass of ALL_EVENTS) {
|
|
1103
|
-
if (this.
|
|
1104
|
-
|
|
1105
|
-
this._events.set(metadata.name, EventClass);
|
|
1150
|
+
if (this.isValidEventClass(EventClass)) {
|
|
1151
|
+
this._events.set(EventClass.metadata.name, EventClass);
|
|
1106
1152
|
}
|
|
1107
1153
|
}
|
|
1108
1154
|
}
|
|
1109
1155
|
/**
|
|
1110
|
-
*
|
|
1156
|
+
* Type guard to check if an object is a valid EventHandlerClass
|
|
1111
1157
|
*/
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
} catch {
|
|
1118
|
-
return false;
|
|
1119
|
-
}
|
|
1158
|
+
isValidEventClass(obj) {
|
|
1159
|
+
if (typeof obj !== "function" && typeof obj !== "object") return false;
|
|
1160
|
+
if (obj === null) return false;
|
|
1161
|
+
const candidate = obj;
|
|
1162
|
+
return candidate.metadata !== void 0 && typeof candidate.metadata.name === "string" && typeof candidate.metadata.displayName === "string" && candidate.validation !== void 0 && typeof candidate.validation.validateConfig === "function" && candidate.configuration !== void 0 && typeof candidate.configuration.configureInteractive === "function" && candidate.processing !== void 0 && typeof candidate.processing.processEvent === "function";
|
|
1120
1163
|
}
|
|
1121
1164
|
/**
|
|
1122
1165
|
* Get all valid event names from registered events
|
|
@@ -1138,12 +1181,11 @@ var EventRegistryClass = class {
|
|
|
1138
1181
|
getEventsForManageUI() {
|
|
1139
1182
|
this.ensureInitialized();
|
|
1140
1183
|
const events = [];
|
|
1141
|
-
for (const [name,
|
|
1142
|
-
const metadata = EventClass.metadata;
|
|
1184
|
+
for (const [name, eventClass] of this._events) {
|
|
1143
1185
|
events.push({
|
|
1144
|
-
name: metadata.displayName,
|
|
1186
|
+
name: eventClass.metadata.displayName,
|
|
1145
1187
|
value: name,
|
|
1146
|
-
description: metadata.description
|
|
1188
|
+
description: eventClass.metadata.description
|
|
1147
1189
|
});
|
|
1148
1190
|
}
|
|
1149
1191
|
return events.sort((a, b) => a.name.localeCompare(b.name));
|
|
@@ -1154,13 +1196,13 @@ var EventRegistryClass = class {
|
|
|
1154
1196
|
getTmuxEnabledEvents() {
|
|
1155
1197
|
this.ensureInitialized();
|
|
1156
1198
|
const tmuxEvents = [];
|
|
1157
|
-
for (const [name,
|
|
1158
|
-
const tmux =
|
|
1199
|
+
for (const [name, eventClass] of this._events) {
|
|
1200
|
+
const tmux = eventClass.tmux;
|
|
1159
1201
|
if (tmux) {
|
|
1160
1202
|
tmuxEvents.push({
|
|
1161
1203
|
name,
|
|
1162
|
-
event:
|
|
1163
|
-
priority: tmux.getLayoutPriority
|
|
1204
|
+
event: eventClass,
|
|
1205
|
+
priority: tmux.getLayoutPriority()
|
|
1164
1206
|
});
|
|
1165
1207
|
}
|
|
1166
1208
|
}
|
|
@@ -1172,16 +1214,15 @@ var EventRegistryClass = class {
|
|
|
1172
1214
|
getAllEvents() {
|
|
1173
1215
|
this.ensureInitialized();
|
|
1174
1216
|
const events = [];
|
|
1175
|
-
for (const [name,
|
|
1176
|
-
const typedClass = EventClass;
|
|
1217
|
+
for (const [name, eventClass] of this._events) {
|
|
1177
1218
|
events.push({
|
|
1178
1219
|
name,
|
|
1179
|
-
metadata:
|
|
1180
|
-
hasValidation: !!
|
|
1181
|
-
hasConfiguration: !!
|
|
1182
|
-
hasProcessing: !!
|
|
1183
|
-
hasTmux: !!
|
|
1184
|
-
hasHelp: !!
|
|
1220
|
+
metadata: eventClass.metadata,
|
|
1221
|
+
hasValidation: !!eventClass.validation,
|
|
1222
|
+
hasConfiguration: !!eventClass.configuration,
|
|
1223
|
+
hasProcessing: !!eventClass.processing,
|
|
1224
|
+
hasTmux: !!eventClass.tmux,
|
|
1225
|
+
hasHelp: !!eventClass.help
|
|
1185
1226
|
});
|
|
1186
1227
|
}
|
|
1187
1228
|
return events;
|