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.js
CHANGED
|
@@ -162,6 +162,18 @@ var Project = class {
|
|
|
162
162
|
// src/lib/tmux.ts
|
|
163
163
|
import { exec as execCallback, spawn } from "child_process";
|
|
164
164
|
import { promisify } from "util";
|
|
165
|
+
|
|
166
|
+
// src/lib/sanitize.ts
|
|
167
|
+
function sanitizeForShell(input4) {
|
|
168
|
+
if (!input4) return "";
|
|
169
|
+
return input4.replace(/[^a-zA-Z0-9_\-.]/g, "_");
|
|
170
|
+
}
|
|
171
|
+
function escapeForSingleQuotes(input4) {
|
|
172
|
+
if (!input4) return "";
|
|
173
|
+
return input4.replace(/'/g, "'\\''");
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// src/lib/tmux.ts
|
|
165
177
|
var exec = promisify(execCallback);
|
|
166
178
|
var TmuxManager = class {
|
|
167
179
|
sessionPrefix = "workon-";
|
|
@@ -175,18 +187,18 @@ var TmuxManager = class {
|
|
|
175
187
|
}
|
|
176
188
|
async sessionExists(sessionName) {
|
|
177
189
|
try {
|
|
178
|
-
await exec(`tmux has-session -t
|
|
190
|
+
await exec(`tmux has-session -t '${escapeForSingleQuotes(sessionName)}'`);
|
|
179
191
|
return true;
|
|
180
192
|
} catch {
|
|
181
193
|
return false;
|
|
182
194
|
}
|
|
183
195
|
}
|
|
184
196
|
getSessionName(projectName) {
|
|
185
|
-
return `${this.sessionPrefix}${projectName}`;
|
|
197
|
+
return `${this.sessionPrefix}${sanitizeForShell(projectName)}`;
|
|
186
198
|
}
|
|
187
199
|
async killSession(sessionName) {
|
|
188
200
|
try {
|
|
189
|
-
await exec(`tmux kill-session -t
|
|
201
|
+
await exec(`tmux kill-session -t '${escapeForSingleQuotes(sessionName)}'`);
|
|
190
202
|
return true;
|
|
191
203
|
} catch {
|
|
192
204
|
return false;
|
|
@@ -194,43 +206,62 @@ var TmuxManager = class {
|
|
|
194
206
|
}
|
|
195
207
|
async createSplitSession(projectName, projectPath, claudeArgs = []) {
|
|
196
208
|
const sessionName = this.getSessionName(projectName);
|
|
209
|
+
const escapedSession = escapeForSingleQuotes(sessionName);
|
|
210
|
+
const escapedPath = escapeForSingleQuotes(projectPath);
|
|
197
211
|
if (await this.sessionExists(sessionName)) {
|
|
198
212
|
await this.killSession(sessionName);
|
|
199
213
|
}
|
|
200
214
|
const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
|
|
201
|
-
|
|
202
|
-
await exec(
|
|
203
|
-
|
|
215
|
+
const escapedClaudeCmd = escapeForSingleQuotes(claudeCommand);
|
|
216
|
+
await exec(
|
|
217
|
+
`tmux new-session -d -s '${escapedSession}' -c '${escapedPath}' '${escapedClaudeCmd}'`
|
|
218
|
+
);
|
|
219
|
+
await exec(`tmux split-window -h -t '${escapedSession}' -c '${escapedPath}'`);
|
|
220
|
+
await exec(`tmux select-pane -t '${escapedSession}:0.0'`);
|
|
204
221
|
return sessionName;
|
|
205
222
|
}
|
|
206
223
|
async createThreePaneSession(projectName, projectPath, claudeArgs = [], npmCommand = "npm run dev") {
|
|
207
224
|
const sessionName = this.getSessionName(projectName);
|
|
225
|
+
const escapedSession = escapeForSingleQuotes(sessionName);
|
|
226
|
+
const escapedPath = escapeForSingleQuotes(projectPath);
|
|
208
227
|
if (await this.sessionExists(sessionName)) {
|
|
209
228
|
await this.killSession(sessionName);
|
|
210
229
|
}
|
|
211
230
|
const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
await exec(
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
await exec(`tmux
|
|
231
|
+
const escapedClaudeCmd = escapeForSingleQuotes(claudeCommand);
|
|
232
|
+
const escapedNpmCmd = escapeForSingleQuotes(npmCommand);
|
|
233
|
+
await exec(
|
|
234
|
+
`tmux new-session -d -s '${escapedSession}' -c '${escapedPath}' '${escapedClaudeCmd}'`
|
|
235
|
+
);
|
|
236
|
+
await exec(`tmux split-window -h -t '${escapedSession}' -c '${escapedPath}'`);
|
|
237
|
+
await exec(
|
|
238
|
+
`tmux split-window -v -t '${escapedSession}:0.1' -c '${escapedPath}' '${escapedNpmCmd}'`
|
|
239
|
+
);
|
|
240
|
+
await exec(`tmux set-option -t '${escapedSession}:0.2' remain-on-exit on`);
|
|
241
|
+
await exec(`tmux resize-pane -t '${escapedSession}:0.2' -y 10`);
|
|
242
|
+
await exec(`tmux select-pane -t '${escapedSession}:0.0'`);
|
|
218
243
|
return sessionName;
|
|
219
244
|
}
|
|
220
245
|
async createTwoPaneNpmSession(projectName, projectPath, npmCommand = "npm run dev") {
|
|
221
246
|
const sessionName = this.getSessionName(projectName);
|
|
247
|
+
const escapedSession = escapeForSingleQuotes(sessionName);
|
|
248
|
+
const escapedPath = escapeForSingleQuotes(projectPath);
|
|
249
|
+
const escapedNpmCmd = escapeForSingleQuotes(npmCommand);
|
|
222
250
|
if (await this.sessionExists(sessionName)) {
|
|
223
251
|
await this.killSession(sessionName);
|
|
224
252
|
}
|
|
225
|
-
await exec(`tmux new-session -d -s
|
|
226
|
-
await exec(
|
|
227
|
-
|
|
228
|
-
|
|
253
|
+
await exec(`tmux new-session -d -s '${escapedSession}' -c '${escapedPath}'`);
|
|
254
|
+
await exec(
|
|
255
|
+
`tmux split-window -h -t '${escapedSession}' -c '${escapedPath}' '${escapedNpmCmd}'`
|
|
256
|
+
);
|
|
257
|
+
await exec(`tmux set-option -t '${escapedSession}:0.1' remain-on-exit on`);
|
|
258
|
+
await exec(`tmux select-pane -t '${escapedSession}:0.0'`);
|
|
229
259
|
return sessionName;
|
|
230
260
|
}
|
|
231
261
|
async attachToSession(sessionName) {
|
|
262
|
+
const escapedSession = escapeForSingleQuotes(sessionName);
|
|
232
263
|
if (process.env.TMUX) {
|
|
233
|
-
await exec(`tmux switch-client -t
|
|
264
|
+
await exec(`tmux switch-client -t '${escapedSession}'`);
|
|
234
265
|
} else {
|
|
235
266
|
const isITerm = process.env.TERM_PROGRAM === "iTerm.app" || process.env.LC_TERMINAL === "iTerm2" || !!process.env.ITERM_SESSION_ID;
|
|
236
267
|
const useiTermIntegration = isITerm && !process.env.TMUX_CC_NOT_SUPPORTED;
|
|
@@ -249,53 +280,64 @@ var TmuxManager = class {
|
|
|
249
280
|
}
|
|
250
281
|
buildShellCommands(projectName, projectPath, claudeArgs = []) {
|
|
251
282
|
const sessionName = this.getSessionName(projectName);
|
|
283
|
+
const escapedSession = escapeForSingleQuotes(sessionName);
|
|
284
|
+
const escapedPath = escapeForSingleQuotes(projectPath);
|
|
252
285
|
const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
|
|
286
|
+
const escapedClaudeCmd = escapeForSingleQuotes(claudeCommand);
|
|
253
287
|
return [
|
|
254
|
-
`# Create tmux split session for ${projectName}`,
|
|
255
|
-
`tmux has-session -t
|
|
256
|
-
`tmux new-session -d -s
|
|
257
|
-
`tmux split-window -h -t
|
|
258
|
-
`tmux select-pane -t
|
|
288
|
+
`# Create tmux split session for ${sanitizeForShell(projectName)}`,
|
|
289
|
+
`tmux has-session -t '${escapedSession}' 2>/dev/null && tmux kill-session -t '${escapedSession}'`,
|
|
290
|
+
`tmux new-session -d -s '${escapedSession}' -c '${escapedPath}' '${escapedClaudeCmd}'`,
|
|
291
|
+
`tmux split-window -h -t '${escapedSession}' -c '${escapedPath}'`,
|
|
292
|
+
`tmux select-pane -t '${escapedSession}:0.0'`,
|
|
259
293
|
this.getAttachCommand(sessionName)
|
|
260
294
|
];
|
|
261
295
|
}
|
|
262
296
|
buildThreePaneShellCommands(projectName, projectPath, claudeArgs = [], npmCommand = "npm run dev") {
|
|
263
297
|
const sessionName = this.getSessionName(projectName);
|
|
298
|
+
const escapedSession = escapeForSingleQuotes(sessionName);
|
|
299
|
+
const escapedPath = escapeForSingleQuotes(projectPath);
|
|
264
300
|
const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
|
|
301
|
+
const escapedClaudeCmd = escapeForSingleQuotes(claudeCommand);
|
|
302
|
+
const escapedNpmCmd = escapeForSingleQuotes(npmCommand);
|
|
265
303
|
return [
|
|
266
|
-
`# Create tmux three-pane session for ${projectName}`,
|
|
267
|
-
`tmux has-session -t
|
|
268
|
-
`tmux new-session -d -s
|
|
269
|
-
`tmux split-window -h -t
|
|
270
|
-
`tmux split-window -v -t
|
|
271
|
-
`tmux set-option -t
|
|
272
|
-
`tmux resize-pane -t
|
|
273
|
-
`tmux select-pane -t
|
|
304
|
+
`# Create tmux three-pane session for ${sanitizeForShell(projectName)}`,
|
|
305
|
+
`tmux has-session -t '${escapedSession}' 2>/dev/null && tmux kill-session -t '${escapedSession}'`,
|
|
306
|
+
`tmux new-session -d -s '${escapedSession}' -c '${escapedPath}' '${escapedClaudeCmd}'`,
|
|
307
|
+
`tmux split-window -h -t '${escapedSession}' -c '${escapedPath}'`,
|
|
308
|
+
`tmux split-window -v -t '${escapedSession}:0.1' -c '${escapedPath}' '${escapedNpmCmd}'`,
|
|
309
|
+
`tmux set-option -t '${escapedSession}:0.2' remain-on-exit on`,
|
|
310
|
+
`tmux resize-pane -t '${escapedSession}:0.2' -y 10`,
|
|
311
|
+
`tmux select-pane -t '${escapedSession}:0.0'`,
|
|
274
312
|
this.getAttachCommand(sessionName)
|
|
275
313
|
];
|
|
276
314
|
}
|
|
277
315
|
buildTwoPaneNpmShellCommands(projectName, projectPath, npmCommand = "npm run dev") {
|
|
278
316
|
const sessionName = this.getSessionName(projectName);
|
|
317
|
+
const escapedSession = escapeForSingleQuotes(sessionName);
|
|
318
|
+
const escapedPath = escapeForSingleQuotes(projectPath);
|
|
319
|
+
const escapedNpmCmd = escapeForSingleQuotes(npmCommand);
|
|
279
320
|
return [
|
|
280
|
-
`# Create tmux two-pane session with npm for ${projectName}`,
|
|
281
|
-
`tmux has-session -t
|
|
282
|
-
`tmux new-session -d -s
|
|
283
|
-
`tmux split-window -h -t
|
|
284
|
-
`tmux set-option -t
|
|
285
|
-
`tmux select-pane -t
|
|
321
|
+
`# Create tmux two-pane session with npm for ${sanitizeForShell(projectName)}`,
|
|
322
|
+
`tmux has-session -t '${escapedSession}' 2>/dev/null && tmux kill-session -t '${escapedSession}'`,
|
|
323
|
+
`tmux new-session -d -s '${escapedSession}' -c '${escapedPath}'`,
|
|
324
|
+
`tmux split-window -h -t '${escapedSession}' -c '${escapedPath}' '${escapedNpmCmd}'`,
|
|
325
|
+
`tmux set-option -t '${escapedSession}:0.1' remain-on-exit on`,
|
|
326
|
+
`tmux select-pane -t '${escapedSession}:0.0'`,
|
|
286
327
|
this.getAttachCommand(sessionName)
|
|
287
328
|
];
|
|
288
329
|
}
|
|
289
330
|
getAttachCommand(sessionName) {
|
|
331
|
+
const escapedSession = escapeForSingleQuotes(sessionName);
|
|
290
332
|
if (process.env.TMUX) {
|
|
291
|
-
return `tmux switch-client -t
|
|
333
|
+
return `tmux switch-client -t '${escapedSession}'`;
|
|
292
334
|
}
|
|
293
335
|
const isITerm = process.env.TERM_PROGRAM === "iTerm.app" || process.env.LC_TERMINAL === "iTerm2" || process.env.ITERM_SESSION_ID;
|
|
294
336
|
const useiTermIntegration = isITerm && !process.env.TMUX_CC_NOT_SUPPORTED;
|
|
295
337
|
if (useiTermIntegration) {
|
|
296
|
-
return `tmux -CC attach-session -t
|
|
338
|
+
return `tmux -CC attach-session -t '${escapedSession}'`;
|
|
297
339
|
}
|
|
298
|
-
return `tmux attach-session -t
|
|
340
|
+
return `tmux attach-session -t '${escapedSession}'`;
|
|
299
341
|
}
|
|
300
342
|
async listWorkonSessions() {
|
|
301
343
|
try {
|
|
@@ -355,7 +397,8 @@ var EnvironmentRecognizer = class {
|
|
|
355
397
|
const git = simpleGit(gitDir.path);
|
|
356
398
|
const branchSummary = await git.branchLocal();
|
|
357
399
|
base.branch = branchSummary.current;
|
|
358
|
-
} catch {
|
|
400
|
+
} catch (error) {
|
|
401
|
+
this.log.debug(`Git branch detection failed: ${error.message}`);
|
|
359
402
|
}
|
|
360
403
|
}
|
|
361
404
|
return this.getProjectEnvironment(base, matching);
|
|
@@ -457,18 +500,22 @@ var CwdEvent = class {
|
|
|
457
500
|
const { project, isShellMode, shellCommands } = context;
|
|
458
501
|
const projectPath = project.path.path;
|
|
459
502
|
if (isShellMode) {
|
|
460
|
-
shellCommands.push(`
|
|
503
|
+
shellCommands.push(`pushd "${projectPath}" > /dev/null`);
|
|
461
504
|
} else {
|
|
462
505
|
const shell = process.env.SHELL || "/bin/bash";
|
|
463
|
-
spawn2(shell, [], {
|
|
506
|
+
const child = spawn2(shell, ["-i"], {
|
|
464
507
|
cwd: projectPath,
|
|
465
508
|
stdio: "inherit"
|
|
466
509
|
});
|
|
510
|
+
await new Promise((resolve, reject) => {
|
|
511
|
+
child.on("close", () => resolve());
|
|
512
|
+
child.on("error", (err) => reject(err));
|
|
513
|
+
});
|
|
467
514
|
}
|
|
468
515
|
},
|
|
469
516
|
generateShellCommand(context) {
|
|
470
517
|
const projectPath = context.project.path.path;
|
|
471
|
-
return [`
|
|
518
|
+
return [`pushd "${projectPath}" > /dev/null`];
|
|
472
519
|
}
|
|
473
520
|
};
|
|
474
521
|
}
|
|
@@ -527,7 +574,7 @@ var IdeEvent = class {
|
|
|
527
574
|
const projectPath = project.path.path;
|
|
528
575
|
const ide = project.ide || "code";
|
|
529
576
|
if (isShellMode) {
|
|
530
|
-
shellCommands.push(
|
|
577
|
+
shellCommands.push(`set +m; ${ide} "${projectPath}" &>/dev/null &`);
|
|
531
578
|
} else {
|
|
532
579
|
spawn3(ide, [projectPath], {
|
|
533
580
|
detached: true,
|
|
@@ -538,7 +585,7 @@ var IdeEvent = class {
|
|
|
538
585
|
generateShellCommand(context) {
|
|
539
586
|
const projectPath = context.project.path.path;
|
|
540
587
|
const ide = context.project.ide || "code";
|
|
541
|
-
return [
|
|
588
|
+
return [`set +m; ${ide} "${projectPath}" &>/dev/null &`];
|
|
542
589
|
}
|
|
543
590
|
};
|
|
544
591
|
}
|
|
@@ -1058,23 +1105,19 @@ var EventRegistryClass = class {
|
|
|
1058
1105
|
*/
|
|
1059
1106
|
registerEvents() {
|
|
1060
1107
|
for (const EventClass of ALL_EVENTS) {
|
|
1061
|
-
if (this.
|
|
1062
|
-
|
|
1063
|
-
this._events.set(metadata.name, EventClass);
|
|
1108
|
+
if (this.isValidEventClass(EventClass)) {
|
|
1109
|
+
this._events.set(EventClass.metadata.name, EventClass);
|
|
1064
1110
|
}
|
|
1065
1111
|
}
|
|
1066
1112
|
}
|
|
1067
1113
|
/**
|
|
1068
|
-
*
|
|
1114
|
+
* Type guard to check if an object is a valid EventHandlerClass
|
|
1069
1115
|
*/
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
} catch {
|
|
1076
|
-
return false;
|
|
1077
|
-
}
|
|
1116
|
+
isValidEventClass(obj) {
|
|
1117
|
+
if (typeof obj !== "function" && typeof obj !== "object") return false;
|
|
1118
|
+
if (obj === null) return false;
|
|
1119
|
+
const candidate = obj;
|
|
1120
|
+
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";
|
|
1078
1121
|
}
|
|
1079
1122
|
/**
|
|
1080
1123
|
* Get all valid event names from registered events
|
|
@@ -1096,12 +1139,11 @@ var EventRegistryClass = class {
|
|
|
1096
1139
|
getEventsForManageUI() {
|
|
1097
1140
|
this.ensureInitialized();
|
|
1098
1141
|
const events = [];
|
|
1099
|
-
for (const [name,
|
|
1100
|
-
const metadata = EventClass.metadata;
|
|
1142
|
+
for (const [name, eventClass] of this._events) {
|
|
1101
1143
|
events.push({
|
|
1102
|
-
name: metadata.displayName,
|
|
1144
|
+
name: eventClass.metadata.displayName,
|
|
1103
1145
|
value: name,
|
|
1104
|
-
description: metadata.description
|
|
1146
|
+
description: eventClass.metadata.description
|
|
1105
1147
|
});
|
|
1106
1148
|
}
|
|
1107
1149
|
return events.sort((a, b) => a.name.localeCompare(b.name));
|
|
@@ -1112,13 +1154,13 @@ var EventRegistryClass = class {
|
|
|
1112
1154
|
getTmuxEnabledEvents() {
|
|
1113
1155
|
this.ensureInitialized();
|
|
1114
1156
|
const tmuxEvents = [];
|
|
1115
|
-
for (const [name,
|
|
1116
|
-
const tmux =
|
|
1157
|
+
for (const [name, eventClass] of this._events) {
|
|
1158
|
+
const tmux = eventClass.tmux;
|
|
1117
1159
|
if (tmux) {
|
|
1118
1160
|
tmuxEvents.push({
|
|
1119
1161
|
name,
|
|
1120
|
-
event:
|
|
1121
|
-
priority: tmux.getLayoutPriority
|
|
1162
|
+
event: eventClass,
|
|
1163
|
+
priority: tmux.getLayoutPriority()
|
|
1122
1164
|
});
|
|
1123
1165
|
}
|
|
1124
1166
|
}
|
|
@@ -1130,16 +1172,15 @@ var EventRegistryClass = class {
|
|
|
1130
1172
|
getAllEvents() {
|
|
1131
1173
|
this.ensureInitialized();
|
|
1132
1174
|
const events = [];
|
|
1133
|
-
for (const [name,
|
|
1134
|
-
const typedClass = EventClass;
|
|
1175
|
+
for (const [name, eventClass] of this._events) {
|
|
1135
1176
|
events.push({
|
|
1136
1177
|
name,
|
|
1137
|
-
metadata:
|
|
1138
|
-
hasValidation: !!
|
|
1139
|
-
hasConfiguration: !!
|
|
1140
|
-
hasProcessing: !!
|
|
1141
|
-
hasTmux: !!
|
|
1142
|
-
hasHelp: !!
|
|
1178
|
+
metadata: eventClass.metadata,
|
|
1179
|
+
hasValidation: !!eventClass.validation,
|
|
1180
|
+
hasConfiguration: !!eventClass.configuration,
|
|
1181
|
+
hasProcessing: !!eventClass.processing,
|
|
1182
|
+
hasTmux: !!eventClass.tmux,
|
|
1183
|
+
hasHelp: !!eventClass.help
|
|
1143
1184
|
});
|
|
1144
1185
|
}
|
|
1145
1186
|
return events;
|