workon 3.2.1 → 3.2.3
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 +456 -269
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +141 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +36 -2
- package/dist/index.d.ts +36 -2
- package/dist/index.js +141 -19
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -11,18 +11,99 @@ var __export = (target, all) => {
|
|
|
11
11
|
|
|
12
12
|
// src/lib/config.ts
|
|
13
13
|
import Conf from "conf";
|
|
14
|
-
|
|
14
|
+
import { openSync, closeSync, unlinkSync, existsSync, mkdirSync } from "fs";
|
|
15
|
+
import { dirname } from "path";
|
|
16
|
+
var TRANSIENT_PROPS, FileLock, Config;
|
|
15
17
|
var init_config = __esm({
|
|
16
18
|
"src/lib/config.ts"() {
|
|
17
19
|
"use strict";
|
|
18
20
|
TRANSIENT_PROPS = ["pkg", "work"];
|
|
19
|
-
|
|
21
|
+
FileLock = class _FileLock {
|
|
22
|
+
lockPath;
|
|
23
|
+
fd = null;
|
|
24
|
+
static LOCK_TIMEOUT_MS = 5e3;
|
|
25
|
+
static RETRY_INTERVAL_MS = 50;
|
|
26
|
+
constructor(configPath) {
|
|
27
|
+
this.lockPath = `${configPath}.lock`;
|
|
28
|
+
}
|
|
29
|
+
async acquire() {
|
|
30
|
+
const startTime = Date.now();
|
|
31
|
+
const lockDir = dirname(this.lockPath);
|
|
32
|
+
if (!existsSync(lockDir)) {
|
|
33
|
+
mkdirSync(lockDir, { recursive: true });
|
|
34
|
+
}
|
|
35
|
+
while (Date.now() - startTime < _FileLock.LOCK_TIMEOUT_MS) {
|
|
36
|
+
try {
|
|
37
|
+
this.fd = openSync(this.lockPath, "wx");
|
|
38
|
+
return;
|
|
39
|
+
} catch (error) {
|
|
40
|
+
if (error.code === "EEXIST") {
|
|
41
|
+
try {
|
|
42
|
+
const stat = await import("fs").then((fs) => fs.promises.stat(this.lockPath));
|
|
43
|
+
const age = Date.now() - stat.mtimeMs;
|
|
44
|
+
if (age > _FileLock.LOCK_TIMEOUT_MS) {
|
|
45
|
+
try {
|
|
46
|
+
unlinkSync(this.lockPath);
|
|
47
|
+
} catch {
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
} catch {
|
|
51
|
+
}
|
|
52
|
+
await new Promise((resolve2) => setTimeout(resolve2, _FileLock.RETRY_INTERVAL_MS));
|
|
53
|
+
} else {
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
throw new Error("Failed to acquire config lock: timeout");
|
|
59
|
+
}
|
|
60
|
+
release() {
|
|
61
|
+
if (this.fd !== null) {
|
|
62
|
+
try {
|
|
63
|
+
closeSync(this.fd);
|
|
64
|
+
} catch {
|
|
65
|
+
}
|
|
66
|
+
this.fd = null;
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
unlinkSync(this.lockPath);
|
|
70
|
+
} catch {
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
Config = class _Config {
|
|
75
|
+
static _instance = null;
|
|
20
76
|
_transient = {};
|
|
77
|
+
// Using definite assignment assertion since singleton pattern may return existing instance
|
|
21
78
|
_store;
|
|
79
|
+
_lock;
|
|
22
80
|
constructor() {
|
|
81
|
+
if (_Config._instance && process.env.NODE_ENV !== "test") {
|
|
82
|
+
return _Config._instance;
|
|
83
|
+
}
|
|
23
84
|
this._store = new Conf({
|
|
24
|
-
projectName: "workon"
|
|
85
|
+
projectName: "workon",
|
|
86
|
+
...process.env.WORKON_CONFIG_DIR && { cwd: process.env.WORKON_CONFIG_DIR }
|
|
25
87
|
});
|
|
88
|
+
this._lock = new FileLock(this._store.path);
|
|
89
|
+
if (process.env.NODE_ENV !== "test") {
|
|
90
|
+
_Config._instance = this;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Get the singleton instance (creates one if needed)
|
|
95
|
+
*/
|
|
96
|
+
static getInstance() {
|
|
97
|
+
if (!_Config._instance) {
|
|
98
|
+
_Config._instance = new _Config();
|
|
99
|
+
}
|
|
100
|
+
return _Config._instance;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Reset the singleton instance (for testing purposes)
|
|
104
|
+
*/
|
|
105
|
+
static resetInstance() {
|
|
106
|
+
_Config._instance = null;
|
|
26
107
|
}
|
|
27
108
|
get(key, defaultValue) {
|
|
28
109
|
const rootKey = key.split(".")[0];
|
|
@@ -37,10 +118,9 @@ var init_config = __esm({
|
|
|
37
118
|
this._transient[key] = value;
|
|
38
119
|
} else {
|
|
39
120
|
if (value === void 0) {
|
|
40
|
-
|
|
41
|
-
} else {
|
|
42
|
-
this._store.set(key, value);
|
|
121
|
+
throw new Error(`Cannot set '${key}' to undefined. Use delete() to remove keys.`);
|
|
43
122
|
}
|
|
123
|
+
this._store.set(key, value);
|
|
44
124
|
}
|
|
45
125
|
}
|
|
46
126
|
has(key) {
|
|
@@ -58,6 +138,9 @@ var init_config = __esm({
|
|
|
58
138
|
this._store.delete(key);
|
|
59
139
|
}
|
|
60
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* Get all projects. Returns a fresh copy from the store.
|
|
143
|
+
*/
|
|
61
144
|
getProjects() {
|
|
62
145
|
return this.get("projects") ?? {};
|
|
63
146
|
}
|
|
@@ -65,15 +148,50 @@ var init_config = __esm({
|
|
|
65
148
|
const projects = this.getProjects();
|
|
66
149
|
return projects[name];
|
|
67
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* Set a project with file locking to prevent race conditions.
|
|
153
|
+
* This ensures atomic read-modify-write operations.
|
|
154
|
+
*/
|
|
155
|
+
async setProjectSafe(name, config) {
|
|
156
|
+
await this._lock.acquire();
|
|
157
|
+
try {
|
|
158
|
+
const freshProjects = this._store.get("projects") ?? {};
|
|
159
|
+
freshProjects[name] = config;
|
|
160
|
+
this._store.set("projects", freshProjects);
|
|
161
|
+
} finally {
|
|
162
|
+
this._lock.release();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Synchronous version of setProject for backwards compatibility.
|
|
167
|
+
* Note: This is less safe than setProjectSafe() in concurrent scenarios.
|
|
168
|
+
* Consider migrating to setProjectSafe() for critical operations.
|
|
169
|
+
*/
|
|
68
170
|
setProject(name, config) {
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
this.set("projects",
|
|
171
|
+
const freshProjects = this._store.get("projects") ?? {};
|
|
172
|
+
freshProjects[name] = config;
|
|
173
|
+
this._store.set("projects", freshProjects);
|
|
72
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* Delete a project with file locking to prevent race conditions.
|
|
177
|
+
*/
|
|
178
|
+
async deleteProjectSafe(name) {
|
|
179
|
+
await this._lock.acquire();
|
|
180
|
+
try {
|
|
181
|
+
const freshProjects = this._store.get("projects") ?? {};
|
|
182
|
+
delete freshProjects[name];
|
|
183
|
+
this._store.set("projects", freshProjects);
|
|
184
|
+
} finally {
|
|
185
|
+
this._lock.release();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Synchronous version of deleteProject for backwards compatibility.
|
|
190
|
+
*/
|
|
73
191
|
deleteProject(name) {
|
|
74
|
-
const
|
|
75
|
-
delete
|
|
76
|
-
this.set("projects",
|
|
192
|
+
const freshProjects = this._store.get("projects") ?? {};
|
|
193
|
+
delete freshProjects[name];
|
|
194
|
+
this._store.set("projects", freshProjects);
|
|
77
195
|
}
|
|
78
196
|
getDefaults() {
|
|
79
197
|
return this.get("project_defaults");
|
|
@@ -280,7 +398,7 @@ var init_environment = __esm({
|
|
|
280
398
|
}
|
|
281
399
|
static ensureConfigured() {
|
|
282
400
|
if (!this.configured) {
|
|
283
|
-
this.config =
|
|
401
|
+
this.config = Config.getInstance();
|
|
284
402
|
this.log = {
|
|
285
403
|
debug: () => {
|
|
286
404
|
},
|
|
@@ -345,18 +463,22 @@ var init_cwd = __esm({
|
|
|
345
463
|
const { project, isShellMode, shellCommands } = context;
|
|
346
464
|
const projectPath = project.path.path;
|
|
347
465
|
if (isShellMode) {
|
|
348
|
-
shellCommands.push(`
|
|
466
|
+
shellCommands.push(`pushd "${projectPath}" > /dev/null`);
|
|
349
467
|
} else {
|
|
350
468
|
const shell = process.env.SHELL || "/bin/bash";
|
|
351
|
-
spawn(shell, [], {
|
|
469
|
+
const child = spawn(shell, ["-i"], {
|
|
352
470
|
cwd: projectPath,
|
|
353
471
|
stdio: "inherit"
|
|
354
472
|
});
|
|
473
|
+
await new Promise((resolve2, reject) => {
|
|
474
|
+
child.on("close", () => resolve2());
|
|
475
|
+
child.on("error", (err) => reject(err));
|
|
476
|
+
});
|
|
355
477
|
}
|
|
356
478
|
},
|
|
357
479
|
generateShellCommand(context) {
|
|
358
480
|
const projectPath = context.project.path.path;
|
|
359
|
-
return [`
|
|
481
|
+
return [`pushd "${projectPath}" > /dev/null`];
|
|
360
482
|
}
|
|
361
483
|
};
|
|
362
484
|
}
|
|
@@ -421,7 +543,7 @@ var init_ide = __esm({
|
|
|
421
543
|
const projectPath = project.path.path;
|
|
422
544
|
const ide = project.ide || "code";
|
|
423
545
|
if (isShellMode) {
|
|
424
|
-
shellCommands.push(
|
|
546
|
+
shellCommands.push(`set +m; ${ide} "${projectPath}" &>/dev/null &`);
|
|
425
547
|
} else {
|
|
426
548
|
spawn2(ide, [projectPath], {
|
|
427
549
|
detached: true,
|
|
@@ -432,7 +554,7 @@ var init_ide = __esm({
|
|
|
432
554
|
generateShellCommand(context) {
|
|
433
555
|
const projectPath = context.project.path.path;
|
|
434
556
|
const ide = context.project.ide || "code";
|
|
435
|
-
return [
|
|
557
|
+
return [`set +m; ${ide} "${projectPath}" &>/dev/null &`];
|
|
436
558
|
}
|
|
437
559
|
};
|
|
438
560
|
}
|
|
@@ -628,10 +750,10 @@ var init_claude = __esm({
|
|
|
628
750
|
}
|
|
629
751
|
static getClaudeCommand(config) {
|
|
630
752
|
if (typeof config === "boolean" || config === void 0) {
|
|
631
|
-
return "claude";
|
|
753
|
+
return "claude --dangerously-skip-permissions";
|
|
632
754
|
}
|
|
633
755
|
const flags = config.flags || [];
|
|
634
|
-
return flags.length > 0 ? `claude ${flags.join(" ")}` : "claude";
|
|
756
|
+
return flags.length > 0 ? `claude --dangerously-skip-permissions ${flags.join(" ")}` : "claude --dangerously-skip-permissions";
|
|
635
757
|
}
|
|
636
758
|
static get processing() {
|
|
637
759
|
return {
|
|
@@ -1094,12 +1216,213 @@ var init_registry = __esm({
|
|
|
1094
1216
|
}
|
|
1095
1217
|
});
|
|
1096
1218
|
|
|
1219
|
+
// src/lib/sanitize.ts
|
|
1220
|
+
function sanitizeForShell(input6) {
|
|
1221
|
+
if (!input6) return "";
|
|
1222
|
+
return input6.replace(/[^a-zA-Z0-9_\-.]/g, "_");
|
|
1223
|
+
}
|
|
1224
|
+
function escapeForSingleQuotes(input6) {
|
|
1225
|
+
if (!input6) return "";
|
|
1226
|
+
return input6.replace(/'/g, "'\\''");
|
|
1227
|
+
}
|
|
1228
|
+
var init_sanitize = __esm({
|
|
1229
|
+
"src/lib/sanitize.ts"() {
|
|
1230
|
+
"use strict";
|
|
1231
|
+
}
|
|
1232
|
+
});
|
|
1233
|
+
|
|
1234
|
+
// src/lib/tmux.ts
|
|
1235
|
+
import { exec as execCallback, spawn as spawn7 } from "child_process";
|
|
1236
|
+
import { promisify } from "util";
|
|
1237
|
+
var exec, TmuxManager;
|
|
1238
|
+
var init_tmux = __esm({
|
|
1239
|
+
"src/lib/tmux.ts"() {
|
|
1240
|
+
"use strict";
|
|
1241
|
+
init_sanitize();
|
|
1242
|
+
exec = promisify(execCallback);
|
|
1243
|
+
TmuxManager = class {
|
|
1244
|
+
sessionPrefix = "workon-";
|
|
1245
|
+
async isTmuxAvailable() {
|
|
1246
|
+
try {
|
|
1247
|
+
await exec("which tmux");
|
|
1248
|
+
return true;
|
|
1249
|
+
} catch {
|
|
1250
|
+
return false;
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
async sessionExists(sessionName) {
|
|
1254
|
+
try {
|
|
1255
|
+
await exec(`tmux has-session -t '${escapeForSingleQuotes(sessionName)}'`);
|
|
1256
|
+
return true;
|
|
1257
|
+
} catch {
|
|
1258
|
+
return false;
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
getSessionName(projectName) {
|
|
1262
|
+
return `${this.sessionPrefix}${sanitizeForShell(projectName)}`;
|
|
1263
|
+
}
|
|
1264
|
+
async killSession(sessionName) {
|
|
1265
|
+
try {
|
|
1266
|
+
await exec(`tmux kill-session -t '${escapeForSingleQuotes(sessionName)}'`);
|
|
1267
|
+
return true;
|
|
1268
|
+
} catch {
|
|
1269
|
+
return false;
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
async createSplitSession(projectName, projectPath, claudeArgs = []) {
|
|
1273
|
+
const sessionName = this.getSessionName(projectName);
|
|
1274
|
+
const escapedSession = escapeForSingleQuotes(sessionName);
|
|
1275
|
+
const escapedPath = escapeForSingleQuotes(projectPath);
|
|
1276
|
+
if (await this.sessionExists(sessionName)) {
|
|
1277
|
+
await this.killSession(sessionName);
|
|
1278
|
+
}
|
|
1279
|
+
const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
|
|
1280
|
+
const escapedClaudeCmd = escapeForSingleQuotes(claudeCommand);
|
|
1281
|
+
await exec(
|
|
1282
|
+
`tmux new-session -d -s '${escapedSession}' -c '${escapedPath}' '${escapedClaudeCmd}'`
|
|
1283
|
+
);
|
|
1284
|
+
await exec(`tmux split-window -h -t '${escapedSession}' -c '${escapedPath}'`);
|
|
1285
|
+
await exec(`tmux select-pane -t '${escapedSession}:0.0'`);
|
|
1286
|
+
return sessionName;
|
|
1287
|
+
}
|
|
1288
|
+
async createThreePaneSession(projectName, projectPath, claudeArgs = [], npmCommand = "npm run dev") {
|
|
1289
|
+
const sessionName = this.getSessionName(projectName);
|
|
1290
|
+
const escapedSession = escapeForSingleQuotes(sessionName);
|
|
1291
|
+
const escapedPath = escapeForSingleQuotes(projectPath);
|
|
1292
|
+
if (await this.sessionExists(sessionName)) {
|
|
1293
|
+
await this.killSession(sessionName);
|
|
1294
|
+
}
|
|
1295
|
+
const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
|
|
1296
|
+
const escapedClaudeCmd = escapeForSingleQuotes(claudeCommand);
|
|
1297
|
+
const escapedNpmCmd = escapeForSingleQuotes(npmCommand);
|
|
1298
|
+
await exec(
|
|
1299
|
+
`tmux new-session -d -s '${escapedSession}' -c '${escapedPath}' '${escapedClaudeCmd}'`
|
|
1300
|
+
);
|
|
1301
|
+
await exec(`tmux split-window -h -t '${escapedSession}' -c '${escapedPath}'`);
|
|
1302
|
+
await exec(
|
|
1303
|
+
`tmux split-window -v -t '${escapedSession}:0.1' -c '${escapedPath}' '${escapedNpmCmd}'`
|
|
1304
|
+
);
|
|
1305
|
+
await exec(`tmux set-option -t '${escapedSession}:0.2' remain-on-exit on`);
|
|
1306
|
+
await exec(`tmux resize-pane -t '${escapedSession}:0.2' -y 10`);
|
|
1307
|
+
await exec(`tmux select-pane -t '${escapedSession}:0.0'`);
|
|
1308
|
+
return sessionName;
|
|
1309
|
+
}
|
|
1310
|
+
async createTwoPaneNpmSession(projectName, projectPath, npmCommand = "npm run dev") {
|
|
1311
|
+
const sessionName = this.getSessionName(projectName);
|
|
1312
|
+
const escapedSession = escapeForSingleQuotes(sessionName);
|
|
1313
|
+
const escapedPath = escapeForSingleQuotes(projectPath);
|
|
1314
|
+
const escapedNpmCmd = escapeForSingleQuotes(npmCommand);
|
|
1315
|
+
if (await this.sessionExists(sessionName)) {
|
|
1316
|
+
await this.killSession(sessionName);
|
|
1317
|
+
}
|
|
1318
|
+
await exec(`tmux new-session -d -s '${escapedSession}' -c '${escapedPath}'`);
|
|
1319
|
+
await exec(
|
|
1320
|
+
`tmux split-window -h -t '${escapedSession}' -c '${escapedPath}' '${escapedNpmCmd}'`
|
|
1321
|
+
);
|
|
1322
|
+
await exec(`tmux set-option -t '${escapedSession}:0.1' remain-on-exit on`);
|
|
1323
|
+
await exec(`tmux select-pane -t '${escapedSession}:0.0'`);
|
|
1324
|
+
return sessionName;
|
|
1325
|
+
}
|
|
1326
|
+
async attachToSession(sessionName) {
|
|
1327
|
+
const escapedSession = escapeForSingleQuotes(sessionName);
|
|
1328
|
+
if (process.env.TMUX) {
|
|
1329
|
+
await exec(`tmux switch-client -t '${escapedSession}'`);
|
|
1330
|
+
} else {
|
|
1331
|
+
const isITerm = process.env.TERM_PROGRAM === "iTerm.app" || process.env.LC_TERMINAL === "iTerm2" || !!process.env.ITERM_SESSION_ID;
|
|
1332
|
+
const useiTermIntegration = isITerm && !process.env.TMUX_CC_NOT_SUPPORTED;
|
|
1333
|
+
if (useiTermIntegration) {
|
|
1334
|
+
spawn7("tmux", ["-CC", "attach-session", "-t", sessionName], {
|
|
1335
|
+
stdio: "inherit",
|
|
1336
|
+
detached: true
|
|
1337
|
+
});
|
|
1338
|
+
} else {
|
|
1339
|
+
spawn7("tmux", ["attach-session", "-t", sessionName], {
|
|
1340
|
+
stdio: "inherit",
|
|
1341
|
+
detached: true
|
|
1342
|
+
});
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
buildShellCommands(projectName, projectPath, claudeArgs = []) {
|
|
1347
|
+
const sessionName = this.getSessionName(projectName);
|
|
1348
|
+
const escapedSession = escapeForSingleQuotes(sessionName);
|
|
1349
|
+
const escapedPath = escapeForSingleQuotes(projectPath);
|
|
1350
|
+
const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
|
|
1351
|
+
const escapedClaudeCmd = escapeForSingleQuotes(claudeCommand);
|
|
1352
|
+
return [
|
|
1353
|
+
`# Create tmux split session for ${sanitizeForShell(projectName)}`,
|
|
1354
|
+
`tmux has-session -t '${escapedSession}' 2>/dev/null && tmux kill-session -t '${escapedSession}'`,
|
|
1355
|
+
`tmux new-session -d -s '${escapedSession}' -c '${escapedPath}' '${escapedClaudeCmd}'`,
|
|
1356
|
+
`tmux split-window -h -t '${escapedSession}' -c '${escapedPath}'`,
|
|
1357
|
+
`tmux select-pane -t '${escapedSession}:0.0'`,
|
|
1358
|
+
this.getAttachCommand(sessionName)
|
|
1359
|
+
];
|
|
1360
|
+
}
|
|
1361
|
+
buildThreePaneShellCommands(projectName, projectPath, claudeArgs = [], npmCommand = "npm run dev") {
|
|
1362
|
+
const sessionName = this.getSessionName(projectName);
|
|
1363
|
+
const escapedSession = escapeForSingleQuotes(sessionName);
|
|
1364
|
+
const escapedPath = escapeForSingleQuotes(projectPath);
|
|
1365
|
+
const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
|
|
1366
|
+
const escapedClaudeCmd = escapeForSingleQuotes(claudeCommand);
|
|
1367
|
+
const escapedNpmCmd = escapeForSingleQuotes(npmCommand);
|
|
1368
|
+
return [
|
|
1369
|
+
`# Create tmux three-pane session for ${sanitizeForShell(projectName)}`,
|
|
1370
|
+
`tmux has-session -t '${escapedSession}' 2>/dev/null && tmux kill-session -t '${escapedSession}'`,
|
|
1371
|
+
`tmux new-session -d -s '${escapedSession}' -c '${escapedPath}' '${escapedClaudeCmd}'`,
|
|
1372
|
+
`tmux split-window -h -t '${escapedSession}' -c '${escapedPath}'`,
|
|
1373
|
+
`tmux split-window -v -t '${escapedSession}:0.1' -c '${escapedPath}' '${escapedNpmCmd}'`,
|
|
1374
|
+
`tmux set-option -t '${escapedSession}:0.2' remain-on-exit on`,
|
|
1375
|
+
`tmux resize-pane -t '${escapedSession}:0.2' -y 10`,
|
|
1376
|
+
`tmux select-pane -t '${escapedSession}:0.0'`,
|
|
1377
|
+
this.getAttachCommand(sessionName)
|
|
1378
|
+
];
|
|
1379
|
+
}
|
|
1380
|
+
buildTwoPaneNpmShellCommands(projectName, projectPath, npmCommand = "npm run dev") {
|
|
1381
|
+
const sessionName = this.getSessionName(projectName);
|
|
1382
|
+
const escapedSession = escapeForSingleQuotes(sessionName);
|
|
1383
|
+
const escapedPath = escapeForSingleQuotes(projectPath);
|
|
1384
|
+
const escapedNpmCmd = escapeForSingleQuotes(npmCommand);
|
|
1385
|
+
return [
|
|
1386
|
+
`# Create tmux two-pane session with npm for ${sanitizeForShell(projectName)}`,
|
|
1387
|
+
`tmux has-session -t '${escapedSession}' 2>/dev/null && tmux kill-session -t '${escapedSession}'`,
|
|
1388
|
+
`tmux new-session -d -s '${escapedSession}' -c '${escapedPath}'`,
|
|
1389
|
+
`tmux split-window -h -t '${escapedSession}' -c '${escapedPath}' '${escapedNpmCmd}'`,
|
|
1390
|
+
`tmux set-option -t '${escapedSession}:0.1' remain-on-exit on`,
|
|
1391
|
+
`tmux select-pane -t '${escapedSession}:0.0'`,
|
|
1392
|
+
this.getAttachCommand(sessionName)
|
|
1393
|
+
];
|
|
1394
|
+
}
|
|
1395
|
+
getAttachCommand(sessionName) {
|
|
1396
|
+
const escapedSession = escapeForSingleQuotes(sessionName);
|
|
1397
|
+
if (process.env.TMUX) {
|
|
1398
|
+
return `tmux switch-client -t '${escapedSession}'`;
|
|
1399
|
+
}
|
|
1400
|
+
const isITerm = process.env.TERM_PROGRAM === "iTerm.app" || process.env.LC_TERMINAL === "iTerm2" || process.env.ITERM_SESSION_ID;
|
|
1401
|
+
const useiTermIntegration = isITerm && !process.env.TMUX_CC_NOT_SUPPORTED;
|
|
1402
|
+
if (useiTermIntegration) {
|
|
1403
|
+
return `tmux -CC attach-session -t '${escapedSession}'`;
|
|
1404
|
+
}
|
|
1405
|
+
return `tmux attach-session -t '${escapedSession}'`;
|
|
1406
|
+
}
|
|
1407
|
+
async listWorkonSessions() {
|
|
1408
|
+
try {
|
|
1409
|
+
const { stdout } = await exec('tmux list-sessions -F "#{session_name}"');
|
|
1410
|
+
return stdout.trim().split("\n").filter((session) => session.startsWith(this.sessionPrefix)).map((session) => session.replace(this.sessionPrefix, ""));
|
|
1411
|
+
} catch {
|
|
1412
|
+
return [];
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
};
|
|
1416
|
+
}
|
|
1417
|
+
});
|
|
1418
|
+
|
|
1097
1419
|
// src/types/constants.ts
|
|
1098
1420
|
var IDE_CHOICES;
|
|
1099
1421
|
var init_constants = __esm({
|
|
1100
1422
|
"src/types/constants.ts"() {
|
|
1101
1423
|
"use strict";
|
|
1102
1424
|
IDE_CHOICES = [
|
|
1425
|
+
{ name: "Cursor", value: "cursor" },
|
|
1103
1426
|
{ name: "Visual Studio Code", value: "vscode" },
|
|
1104
1427
|
{ name: "Visual Studio Code (code)", value: "code" },
|
|
1105
1428
|
{ name: "IntelliJ IDEA", value: "idea" },
|
|
@@ -1264,8 +1587,7 @@ async function initProject(defaultName, fromUser, ctx) {
|
|
|
1264
1587
|
ide,
|
|
1265
1588
|
events
|
|
1266
1589
|
};
|
|
1267
|
-
|
|
1268
|
-
config.set("projects", projects);
|
|
1590
|
+
await config.setProjectSafe(name, projectConfig);
|
|
1269
1591
|
log.info("Your project has been initialized.");
|
|
1270
1592
|
log.info(`Use 'workon ${name}' to start working!`);
|
|
1271
1593
|
}
|
|
@@ -1286,8 +1608,7 @@ async function initBranch(defaultName, ctx) {
|
|
|
1286
1608
|
branch
|
|
1287
1609
|
});
|
|
1288
1610
|
const branchConfig = mergedConfig;
|
|
1289
|
-
|
|
1290
|
-
config.set("projects", projects);
|
|
1611
|
+
await config.setProjectSafe(branchName, branchConfig);
|
|
1291
1612
|
log.info("Your branch configuration has been initialized.");
|
|
1292
1613
|
log.info(`Use 'workon ${branchName}' to start working!`);
|
|
1293
1614
|
}
|
|
@@ -1485,7 +1806,7 @@ async function createProjectManage(ctx) {
|
|
|
1485
1806
|
default: true
|
|
1486
1807
|
});
|
|
1487
1808
|
if (confirmed) {
|
|
1488
|
-
config.
|
|
1809
|
+
await config.setProjectSafe(name, projectConfig);
|
|
1489
1810
|
log.info(`Project '${name}' created successfully!`);
|
|
1490
1811
|
}
|
|
1491
1812
|
}
|
|
@@ -1563,7 +1884,7 @@ async function editProjectManage(ctx) {
|
|
|
1563
1884
|
default: true
|
|
1564
1885
|
});
|
|
1565
1886
|
if (confirmed) {
|
|
1566
|
-
config.
|
|
1887
|
+
await config.setProjectSafe(name, updatedConfig);
|
|
1567
1888
|
log.info(`Project '${name}' updated successfully!`);
|
|
1568
1889
|
}
|
|
1569
1890
|
}
|
|
@@ -1589,7 +1910,7 @@ async function deleteProjectManage(ctx) {
|
|
|
1589
1910
|
});
|
|
1590
1911
|
if (deleteAll) {
|
|
1591
1912
|
for (const branch of branches) {
|
|
1592
|
-
config.
|
|
1913
|
+
await config.deleteProjectSafe(branch);
|
|
1593
1914
|
}
|
|
1594
1915
|
}
|
|
1595
1916
|
}
|
|
@@ -1598,7 +1919,7 @@ async function deleteProjectManage(ctx) {
|
|
|
1598
1919
|
default: false
|
|
1599
1920
|
});
|
|
1600
1921
|
if (confirmed) {
|
|
1601
|
-
config.
|
|
1922
|
+
await config.deleteProjectSafe(name);
|
|
1602
1923
|
log.info(`Project '${name}' deleted.`);
|
|
1603
1924
|
}
|
|
1604
1925
|
}
|
|
@@ -1678,7 +1999,7 @@ async function editBranchManage(projectName, ctx) {
|
|
|
1678
1999
|
default: true
|
|
1679
2000
|
});
|
|
1680
2001
|
if (confirmed) {
|
|
1681
|
-
config.
|
|
2002
|
+
await config.setProjectSafe(branchName, updatedConfig);
|
|
1682
2003
|
log.info(`Branch configuration '${branchName}' updated successfully!`);
|
|
1683
2004
|
}
|
|
1684
2005
|
}
|
|
@@ -1703,7 +2024,7 @@ async function deleteBranchManage(projectName, ctx) {
|
|
|
1703
2024
|
default: false
|
|
1704
2025
|
});
|
|
1705
2026
|
if (confirmed) {
|
|
1706
|
-
config.
|
|
2027
|
+
await config.deleteProjectSafe(branchName);
|
|
1707
2028
|
log.info(`Branch configuration '${branchName}' deleted.`);
|
|
1708
2029
|
}
|
|
1709
2030
|
}
|
|
@@ -1732,215 +2053,21 @@ var init_interactive = __esm({
|
|
|
1732
2053
|
}
|
|
1733
2054
|
});
|
|
1734
2055
|
|
|
1735
|
-
// src/commands/index.ts
|
|
1736
|
-
init_config();
|
|
1737
|
-
init_environment();
|
|
1738
|
-
init_registry();
|
|
1739
|
-
import { Command as Command8 } from "commander";
|
|
1740
|
-
import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
|
|
1741
|
-
import { join, dirname } from "path";
|
|
1742
|
-
import { fileURLToPath } from "url";
|
|
1743
|
-
import loog from "loog";
|
|
1744
|
-
import omelette from "omelette";
|
|
1745
|
-
import File7 from "phylo";
|
|
1746
|
-
|
|
1747
2056
|
// src/commands/open.ts
|
|
1748
|
-
|
|
2057
|
+
var open_exports = {};
|
|
2058
|
+
__export(open_exports, {
|
|
2059
|
+
createOpenCommand: () => createOpenCommand,
|
|
2060
|
+
runOpen: () => runOpen
|
|
2061
|
+
});
|
|
1749
2062
|
import { Command } from "commander";
|
|
1750
2063
|
import File4 from "phylo";
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
// src/lib/sanitize.ts
|
|
1757
|
-
function sanitizeForShell(input6) {
|
|
1758
|
-
if (!input6) return "";
|
|
1759
|
-
return input6.replace(/[^a-zA-Z0-9_\-.]/g, "_");
|
|
1760
|
-
}
|
|
1761
|
-
function escapeForSingleQuotes(input6) {
|
|
1762
|
-
if (!input6) return "";
|
|
1763
|
-
return input6.replace(/'/g, "'\\''");
|
|
1764
|
-
}
|
|
1765
|
-
|
|
1766
|
-
// src/lib/tmux.ts
|
|
1767
|
-
var exec = promisify(execCallback);
|
|
1768
|
-
var TmuxManager = class {
|
|
1769
|
-
sessionPrefix = "workon-";
|
|
1770
|
-
async isTmuxAvailable() {
|
|
1771
|
-
try {
|
|
1772
|
-
await exec("which tmux");
|
|
1773
|
-
return true;
|
|
1774
|
-
} catch {
|
|
1775
|
-
return false;
|
|
1776
|
-
}
|
|
1777
|
-
}
|
|
1778
|
-
async sessionExists(sessionName) {
|
|
1779
|
-
try {
|
|
1780
|
-
await exec(`tmux has-session -t '${escapeForSingleQuotes(sessionName)}'`);
|
|
1781
|
-
return true;
|
|
1782
|
-
} catch {
|
|
1783
|
-
return false;
|
|
1784
|
-
}
|
|
1785
|
-
}
|
|
1786
|
-
getSessionName(projectName) {
|
|
1787
|
-
return `${this.sessionPrefix}${sanitizeForShell(projectName)}`;
|
|
1788
|
-
}
|
|
1789
|
-
async killSession(sessionName) {
|
|
1790
|
-
try {
|
|
1791
|
-
await exec(`tmux kill-session -t '${escapeForSingleQuotes(sessionName)}'`);
|
|
1792
|
-
return true;
|
|
1793
|
-
} catch {
|
|
1794
|
-
return false;
|
|
1795
|
-
}
|
|
1796
|
-
}
|
|
1797
|
-
async createSplitSession(projectName, projectPath, claudeArgs = []) {
|
|
1798
|
-
const sessionName = this.getSessionName(projectName);
|
|
1799
|
-
const escapedSession = escapeForSingleQuotes(sessionName);
|
|
1800
|
-
const escapedPath = escapeForSingleQuotes(projectPath);
|
|
1801
|
-
if (await this.sessionExists(sessionName)) {
|
|
1802
|
-
await this.killSession(sessionName);
|
|
1803
|
-
}
|
|
1804
|
-
const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
|
|
1805
|
-
const escapedClaudeCmd = escapeForSingleQuotes(claudeCommand);
|
|
1806
|
-
await exec(
|
|
1807
|
-
`tmux new-session -d -s '${escapedSession}' -c '${escapedPath}' '${escapedClaudeCmd}'`
|
|
1808
|
-
);
|
|
1809
|
-
await exec(`tmux split-window -h -t '${escapedSession}' -c '${escapedPath}'`);
|
|
1810
|
-
await exec(`tmux select-pane -t '${escapedSession}:0.0'`);
|
|
1811
|
-
return sessionName;
|
|
1812
|
-
}
|
|
1813
|
-
async createThreePaneSession(projectName, projectPath, claudeArgs = [], npmCommand = "npm run dev") {
|
|
1814
|
-
const sessionName = this.getSessionName(projectName);
|
|
1815
|
-
const escapedSession = escapeForSingleQuotes(sessionName);
|
|
1816
|
-
const escapedPath = escapeForSingleQuotes(projectPath);
|
|
1817
|
-
if (await this.sessionExists(sessionName)) {
|
|
1818
|
-
await this.killSession(sessionName);
|
|
1819
|
-
}
|
|
1820
|
-
const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
|
|
1821
|
-
const escapedClaudeCmd = escapeForSingleQuotes(claudeCommand);
|
|
1822
|
-
const escapedNpmCmd = escapeForSingleQuotes(npmCommand);
|
|
1823
|
-
await exec(
|
|
1824
|
-
`tmux new-session -d -s '${escapedSession}' -c '${escapedPath}' '${escapedClaudeCmd}'`
|
|
1825
|
-
);
|
|
1826
|
-
await exec(`tmux split-window -h -t '${escapedSession}' -c '${escapedPath}'`);
|
|
1827
|
-
await exec(
|
|
1828
|
-
`tmux split-window -v -t '${escapedSession}:0.1' -c '${escapedPath}' '${escapedNpmCmd}'`
|
|
1829
|
-
);
|
|
1830
|
-
await exec(`tmux set-option -t '${escapedSession}:0.2' remain-on-exit on`);
|
|
1831
|
-
await exec(`tmux resize-pane -t '${escapedSession}:0.2' -y 10`);
|
|
1832
|
-
await exec(`tmux select-pane -t '${escapedSession}:0.0'`);
|
|
1833
|
-
return sessionName;
|
|
1834
|
-
}
|
|
1835
|
-
async createTwoPaneNpmSession(projectName, projectPath, npmCommand = "npm run dev") {
|
|
1836
|
-
const sessionName = this.getSessionName(projectName);
|
|
1837
|
-
const escapedSession = escapeForSingleQuotes(sessionName);
|
|
1838
|
-
const escapedPath = escapeForSingleQuotes(projectPath);
|
|
1839
|
-
const escapedNpmCmd = escapeForSingleQuotes(npmCommand);
|
|
1840
|
-
if (await this.sessionExists(sessionName)) {
|
|
1841
|
-
await this.killSession(sessionName);
|
|
1842
|
-
}
|
|
1843
|
-
await exec(`tmux new-session -d -s '${escapedSession}' -c '${escapedPath}'`);
|
|
1844
|
-
await exec(
|
|
1845
|
-
`tmux split-window -h -t '${escapedSession}' -c '${escapedPath}' '${escapedNpmCmd}'`
|
|
1846
|
-
);
|
|
1847
|
-
await exec(`tmux set-option -t '${escapedSession}:0.1' remain-on-exit on`);
|
|
1848
|
-
await exec(`tmux select-pane -t '${escapedSession}:0.0'`);
|
|
1849
|
-
return sessionName;
|
|
1850
|
-
}
|
|
1851
|
-
async attachToSession(sessionName) {
|
|
1852
|
-
const escapedSession = escapeForSingleQuotes(sessionName);
|
|
1853
|
-
if (process.env.TMUX) {
|
|
1854
|
-
await exec(`tmux switch-client -t '${escapedSession}'`);
|
|
1855
|
-
} else {
|
|
1856
|
-
const isITerm = process.env.TERM_PROGRAM === "iTerm.app" || process.env.LC_TERMINAL === "iTerm2" || !!process.env.ITERM_SESSION_ID;
|
|
1857
|
-
const useiTermIntegration = isITerm && !process.env.TMUX_CC_NOT_SUPPORTED;
|
|
1858
|
-
if (useiTermIntegration) {
|
|
1859
|
-
spawn7("tmux", ["-CC", "attach-session", "-t", sessionName], {
|
|
1860
|
-
stdio: "inherit",
|
|
1861
|
-
detached: true
|
|
1862
|
-
});
|
|
1863
|
-
} else {
|
|
1864
|
-
spawn7("tmux", ["attach-session", "-t", sessionName], {
|
|
1865
|
-
stdio: "inherit",
|
|
1866
|
-
detached: true
|
|
1867
|
-
});
|
|
1868
|
-
}
|
|
1869
|
-
}
|
|
1870
|
-
}
|
|
1871
|
-
buildShellCommands(projectName, projectPath, claudeArgs = []) {
|
|
1872
|
-
const sessionName = this.getSessionName(projectName);
|
|
1873
|
-
const escapedSession = escapeForSingleQuotes(sessionName);
|
|
1874
|
-
const escapedPath = escapeForSingleQuotes(projectPath);
|
|
1875
|
-
const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
|
|
1876
|
-
const escapedClaudeCmd = escapeForSingleQuotes(claudeCommand);
|
|
1877
|
-
return [
|
|
1878
|
-
`# Create tmux split session for ${sanitizeForShell(projectName)}`,
|
|
1879
|
-
`tmux has-session -t '${escapedSession}' 2>/dev/null && tmux kill-session -t '${escapedSession}'`,
|
|
1880
|
-
`tmux new-session -d -s '${escapedSession}' -c '${escapedPath}' '${escapedClaudeCmd}'`,
|
|
1881
|
-
`tmux split-window -h -t '${escapedSession}' -c '${escapedPath}'`,
|
|
1882
|
-
`tmux select-pane -t '${escapedSession}:0.0'`,
|
|
1883
|
-
this.getAttachCommand(sessionName)
|
|
1884
|
-
];
|
|
1885
|
-
}
|
|
1886
|
-
buildThreePaneShellCommands(projectName, projectPath, claudeArgs = [], npmCommand = "npm run dev") {
|
|
1887
|
-
const sessionName = this.getSessionName(projectName);
|
|
1888
|
-
const escapedSession = escapeForSingleQuotes(sessionName);
|
|
1889
|
-
const escapedPath = escapeForSingleQuotes(projectPath);
|
|
1890
|
-
const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
|
|
1891
|
-
const escapedClaudeCmd = escapeForSingleQuotes(claudeCommand);
|
|
1892
|
-
const escapedNpmCmd = escapeForSingleQuotes(npmCommand);
|
|
1893
|
-
return [
|
|
1894
|
-
`# Create tmux three-pane session for ${sanitizeForShell(projectName)}`,
|
|
1895
|
-
`tmux has-session -t '${escapedSession}' 2>/dev/null && tmux kill-session -t '${escapedSession}'`,
|
|
1896
|
-
`tmux new-session -d -s '${escapedSession}' -c '${escapedPath}' '${escapedClaudeCmd}'`,
|
|
1897
|
-
`tmux split-window -h -t '${escapedSession}' -c '${escapedPath}'`,
|
|
1898
|
-
`tmux split-window -v -t '${escapedSession}:0.1' -c '${escapedPath}' '${escapedNpmCmd}'`,
|
|
1899
|
-
`tmux set-option -t '${escapedSession}:0.2' remain-on-exit on`,
|
|
1900
|
-
`tmux resize-pane -t '${escapedSession}:0.2' -y 10`,
|
|
1901
|
-
`tmux select-pane -t '${escapedSession}:0.0'`,
|
|
1902
|
-
this.getAttachCommand(sessionName)
|
|
1903
|
-
];
|
|
1904
|
-
}
|
|
1905
|
-
buildTwoPaneNpmShellCommands(projectName, projectPath, npmCommand = "npm run dev") {
|
|
1906
|
-
const sessionName = this.getSessionName(projectName);
|
|
1907
|
-
const escapedSession = escapeForSingleQuotes(sessionName);
|
|
1908
|
-
const escapedPath = escapeForSingleQuotes(projectPath);
|
|
1909
|
-
const escapedNpmCmd = escapeForSingleQuotes(npmCommand);
|
|
1910
|
-
return [
|
|
1911
|
-
`# Create tmux two-pane session with npm for ${sanitizeForShell(projectName)}`,
|
|
1912
|
-
`tmux has-session -t '${escapedSession}' 2>/dev/null && tmux kill-session -t '${escapedSession}'`,
|
|
1913
|
-
`tmux new-session -d -s '${escapedSession}' -c '${escapedPath}'`,
|
|
1914
|
-
`tmux split-window -h -t '${escapedSession}' -c '${escapedPath}' '${escapedNpmCmd}'`,
|
|
1915
|
-
`tmux set-option -t '${escapedSession}:0.1' remain-on-exit on`,
|
|
1916
|
-
`tmux select-pane -t '${escapedSession}:0.0'`,
|
|
1917
|
-
this.getAttachCommand(sessionName)
|
|
1918
|
-
];
|
|
1919
|
-
}
|
|
1920
|
-
getAttachCommand(sessionName) {
|
|
1921
|
-
const escapedSession = escapeForSingleQuotes(sessionName);
|
|
1922
|
-
if (process.env.TMUX) {
|
|
1923
|
-
return `tmux switch-client -t '${escapedSession}'`;
|
|
1924
|
-
}
|
|
1925
|
-
const isITerm = process.env.TERM_PROGRAM === "iTerm.app" || process.env.LC_TERMINAL === "iTerm2" || process.env.ITERM_SESSION_ID;
|
|
1926
|
-
const useiTermIntegration = isITerm && !process.env.TMUX_CC_NOT_SUPPORTED;
|
|
1927
|
-
if (useiTermIntegration) {
|
|
1928
|
-
return `tmux -CC attach-session -t '${escapedSession}'`;
|
|
1929
|
-
}
|
|
1930
|
-
return `tmux attach-session -t '${escapedSession}'`;
|
|
1931
|
-
}
|
|
1932
|
-
async listWorkonSessions() {
|
|
1933
|
-
try {
|
|
1934
|
-
const { stdout } = await exec('tmux list-sessions -F "#{session_name}"');
|
|
1935
|
-
return stdout.trim().split("\n").filter((session) => session.startsWith(this.sessionPrefix)).map((session) => session.replace(this.sessionPrefix, ""));
|
|
1936
|
-
} catch {
|
|
1937
|
-
return [];
|
|
1938
|
-
}
|
|
2064
|
+
async function runOpen(projectArg, options, ctx) {
|
|
2065
|
+
const { log } = ctx;
|
|
2066
|
+
if (options.debug) {
|
|
2067
|
+
log.setLogLevel("debug");
|
|
1939
2068
|
}
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
// src/commands/open.ts
|
|
1943
|
-
init_registry();
|
|
2069
|
+
await processProject(projectArg, options, ctx);
|
|
2070
|
+
}
|
|
1944
2071
|
function createOpenCommand(ctx) {
|
|
1945
2072
|
const { config, log } = ctx;
|
|
1946
2073
|
const command = new Command("open").description("Open a project by passing its project id").argument("[project]", "The id of the project to open (supports project:command syntax)").option("-d, --debug", "Enable debug logging").option("-n, --dry-run", "Show what would happen without executing").option("--shell", "Output shell commands instead of spawning processes").action(async (projectArg, options) => {
|
|
@@ -1985,9 +2112,9 @@ async function processProject(projectParam, options, ctx) {
|
|
|
1985
2112
|
const projectEnv = ProjectEnvironment.load(projectCfg, config.getDefaults());
|
|
1986
2113
|
await switchTo(projectEnv, requestedCommands, options, ctx);
|
|
1987
2114
|
} else {
|
|
1988
|
-
log.
|
|
1989
|
-
|
|
1990
|
-
|
|
2115
|
+
log.error(`Project '${projectName}' not found.`);
|
|
2116
|
+
log.info(`Run 'workon' without arguments to see available projects or create a new one.`);
|
|
2117
|
+
process.exit(1);
|
|
1991
2118
|
}
|
|
1992
2119
|
}
|
|
1993
2120
|
function validateRequestedCommands(requestedCommands, projectConfig, projectName) {
|
|
@@ -2055,7 +2182,8 @@ function resolveCommandDependencies(requestedCommands, project) {
|
|
|
2055
2182
|
}
|
|
2056
2183
|
function getClaudeArgs(project) {
|
|
2057
2184
|
const claudeConfig = project.events.claude;
|
|
2058
|
-
|
|
2185
|
+
const userFlags = typeof claudeConfig === "object" && claudeConfig.flags ? claudeConfig.flags : [];
|
|
2186
|
+
return ["--dangerously-skip-permissions", ...userFlags];
|
|
2059
2187
|
}
|
|
2060
2188
|
async function getNpmCommand(project) {
|
|
2061
2189
|
const npmConfig = project.events.npm;
|
|
@@ -2069,12 +2197,25 @@ async function handleTmuxLayout(project, layout, options, shellCommands, events,
|
|
|
2069
2197
|
let tmuxHandled = false;
|
|
2070
2198
|
if (isShellMode) {
|
|
2071
2199
|
if (await tmux.isTmuxAvailable()) {
|
|
2200
|
+
const remainingEvents = events.filter((e) => !layout.handledEvents.includes(e));
|
|
2201
|
+
for (const event of remainingEvents) {
|
|
2202
|
+
const eventHandler = EventRegistry.getEventByName(event);
|
|
2203
|
+
if (eventHandler) {
|
|
2204
|
+
const cmds = eventHandler.processing.generateShellCommand({
|
|
2205
|
+
project,
|
|
2206
|
+
isShellMode: true,
|
|
2207
|
+
shellCommands: []
|
|
2208
|
+
});
|
|
2209
|
+
shellCommands.push(...cmds);
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2072
2212
|
const commands = buildLayoutShellCommands(tmux, project, layout);
|
|
2073
2213
|
shellCommands.push(...commands);
|
|
2074
2214
|
tmuxHandled = true;
|
|
2075
2215
|
} else {
|
|
2076
2216
|
log.debug("Tmux not available, falling back to normal mode");
|
|
2077
|
-
|
|
2217
|
+
const remainingEvents = events.filter((e) => !layout.handledEvents.includes(e));
|
|
2218
|
+
buildFallbackCommandsWithEvents(shellCommands, project, layout, remainingEvents, ctx);
|
|
2078
2219
|
tmuxHandled = true;
|
|
2079
2220
|
}
|
|
2080
2221
|
} else if (!dryRun) {
|
|
@@ -2098,7 +2239,7 @@ async function handleTmuxLayout(project, layout, options, shellCommands, events,
|
|
|
2098
2239
|
await processEvent(event, { project, isShellMode, shellCommands }, ctx);
|
|
2099
2240
|
}
|
|
2100
2241
|
}
|
|
2101
|
-
if (!dryRun) {
|
|
2242
|
+
if (!dryRun && !isShellMode) {
|
|
2102
2243
|
for (const event of events.filter((e) => !layout.handledEvents.includes(e))) {
|
|
2103
2244
|
await processEvent(event, { project, isShellMode, shellCommands }, ctx);
|
|
2104
2245
|
}
|
|
@@ -2119,14 +2260,19 @@ function buildLayoutShellCommands(tmux, project, layout) {
|
|
|
2119
2260
|
return tmux.buildTwoPaneNpmShellCommands(project.name, project.path.path, layout.npmCommand);
|
|
2120
2261
|
}
|
|
2121
2262
|
}
|
|
2122
|
-
function
|
|
2123
|
-
shellCommands.push(`
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2263
|
+
function buildFallbackCommandsWithEvents(shellCommands, project, layout, remainingEvents, _ctx) {
|
|
2264
|
+
shellCommands.push(`echo "\u26A0 tmux not available - install with: brew install tmux" >&2`);
|
|
2265
|
+
shellCommands.push(`pushd "${project.path.path}" > /dev/null`);
|
|
2266
|
+
for (const event of remainingEvents) {
|
|
2267
|
+
const eventHandler = EventRegistry.getEventByName(event);
|
|
2268
|
+
if (eventHandler) {
|
|
2269
|
+
const cmds = eventHandler.processing.generateShellCommand({
|
|
2270
|
+
project,
|
|
2271
|
+
isShellMode: true,
|
|
2272
|
+
shellCommands: []
|
|
2273
|
+
});
|
|
2274
|
+
shellCommands.push(...cmds);
|
|
2275
|
+
}
|
|
2130
2276
|
}
|
|
2131
2277
|
}
|
|
2132
2278
|
async function createTmuxSession(tmux, project, layout) {
|
|
@@ -2227,6 +2373,27 @@ Available commands for '${projectName}':`);
|
|
|
2227
2373
|
console.log(` workon ${projectName}:cwd --shell # Output shell commands
|
|
2228
2374
|
`);
|
|
2229
2375
|
}
|
|
2376
|
+
var init_open = __esm({
|
|
2377
|
+
"src/commands/open.ts"() {
|
|
2378
|
+
"use strict";
|
|
2379
|
+
init_environment();
|
|
2380
|
+
init_tmux();
|
|
2381
|
+
init_registry();
|
|
2382
|
+
}
|
|
2383
|
+
});
|
|
2384
|
+
|
|
2385
|
+
// src/commands/index.ts
|
|
2386
|
+
init_config();
|
|
2387
|
+
init_environment();
|
|
2388
|
+
init_registry();
|
|
2389
|
+
init_open();
|
|
2390
|
+
import { Command as Command8 } from "commander";
|
|
2391
|
+
import { readFileSync as readFileSync2, existsSync as existsSync3 } from "fs";
|
|
2392
|
+
import { join, dirname as dirname2 } from "path";
|
|
2393
|
+
import { fileURLToPath } from "url";
|
|
2394
|
+
import loog from "loog";
|
|
2395
|
+
import omelette from "omelette";
|
|
2396
|
+
import File7 from "phylo";
|
|
2230
2397
|
|
|
2231
2398
|
// src/commands/config/index.ts
|
|
2232
2399
|
import { Command as Command5 } from "commander";
|
|
@@ -2449,7 +2616,7 @@ async function createProject(ctx) {
|
|
|
2449
2616
|
default: true
|
|
2450
2617
|
});
|
|
2451
2618
|
if (confirmed) {
|
|
2452
|
-
config.
|
|
2619
|
+
await config.setProjectSafe(name, projectConfig);
|
|
2453
2620
|
log.info(`Project '${name}' created successfully!`);
|
|
2454
2621
|
log.info(`Use 'workon ${name}' to start working!`);
|
|
2455
2622
|
} else {
|
|
@@ -2539,7 +2706,7 @@ async function editProject(ctx) {
|
|
|
2539
2706
|
default: true
|
|
2540
2707
|
});
|
|
2541
2708
|
if (confirmed) {
|
|
2542
|
-
config.
|
|
2709
|
+
await config.setProjectSafe(name, updatedConfig);
|
|
2543
2710
|
log.info(`Project '${name}' updated successfully!`);
|
|
2544
2711
|
} else {
|
|
2545
2712
|
log.info("Edit cancelled.");
|
|
@@ -2562,7 +2729,7 @@ async function deleteProject(ctx) {
|
|
|
2562
2729
|
default: false
|
|
2563
2730
|
});
|
|
2564
2731
|
if (confirmed) {
|
|
2565
|
-
config.
|
|
2732
|
+
await config.deleteProjectSafe(name);
|
|
2566
2733
|
log.info(`Project '${name}' deleted.`);
|
|
2567
2734
|
} else {
|
|
2568
2735
|
log.info("Delete cancelled.");
|
|
@@ -2588,7 +2755,7 @@ async function listProjects(ctx) {
|
|
|
2588
2755
|
|
|
2589
2756
|
// src/commands/add.ts
|
|
2590
2757
|
import { Command as Command7 } from "commander";
|
|
2591
|
-
import { existsSync, readFileSync } from "fs";
|
|
2758
|
+
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
2592
2759
|
import { basename, resolve } from "path";
|
|
2593
2760
|
import File6 from "phylo";
|
|
2594
2761
|
import { confirm as confirm5 } from "@inquirer/prompts";
|
|
@@ -2610,7 +2777,7 @@ async function addProject(pathArg, options, ctx) {
|
|
|
2610
2777
|
const projects = config.getProjects();
|
|
2611
2778
|
const targetPath = resolve(pathArg);
|
|
2612
2779
|
log.debug(`Resolved path: ${targetPath}`);
|
|
2613
|
-
if (!
|
|
2780
|
+
if (!existsSync2(targetPath)) {
|
|
2614
2781
|
log.error(`Path does not exist: ${targetPath}`);
|
|
2615
2782
|
process.exit(1);
|
|
2616
2783
|
}
|
|
@@ -2644,7 +2811,7 @@ async function addProject(pathArg, options, ctx) {
|
|
|
2644
2811
|
return;
|
|
2645
2812
|
}
|
|
2646
2813
|
}
|
|
2647
|
-
const ide = options.ide || discovery.detectedIde || "
|
|
2814
|
+
const ide = options.ide || discovery.detectedIde || "code";
|
|
2648
2815
|
log.debug(`IDE: ${ide}`);
|
|
2649
2816
|
let relativePath = targetPath;
|
|
2650
2817
|
if (defaults?.base) {
|
|
@@ -2672,7 +2839,10 @@ async function addProject(pathArg, options, ctx) {
|
|
|
2672
2839
|
projectConfig.events.npm = scripts.dev ? "dev" : "start";
|
|
2673
2840
|
}
|
|
2674
2841
|
}
|
|
2675
|
-
|
|
2842
|
+
if (discovery.hasClaude) {
|
|
2843
|
+
projectConfig.events.claude = true;
|
|
2844
|
+
}
|
|
2845
|
+
await config.setProjectSafe(projectName, projectConfig);
|
|
2676
2846
|
log.info(`Added project '${projectName}'`);
|
|
2677
2847
|
log.info(` Path: ${relativePath}`);
|
|
2678
2848
|
log.info(` IDE: ${ide}`);
|
|
@@ -2686,11 +2856,12 @@ function discoverProject(targetPath, log) {
|
|
|
2686
2856
|
name: dirName,
|
|
2687
2857
|
isNode: false,
|
|
2688
2858
|
isBun: false,
|
|
2859
|
+
hasClaude: false,
|
|
2689
2860
|
detectedIde: null,
|
|
2690
2861
|
packageJson: null
|
|
2691
2862
|
};
|
|
2692
2863
|
const packageJsonPath = resolve(targetPath, "package.json");
|
|
2693
|
-
if (
|
|
2864
|
+
if (existsSync2(packageJsonPath)) {
|
|
2694
2865
|
discovery.isNode = true;
|
|
2695
2866
|
log.debug("Detected Node project (package.json found)");
|
|
2696
2867
|
try {
|
|
@@ -2706,25 +2877,34 @@ function discoverProject(targetPath, log) {
|
|
|
2706
2877
|
}
|
|
2707
2878
|
}
|
|
2708
2879
|
const bunLockPath = resolve(targetPath, "bun.lockb");
|
|
2709
|
-
if (
|
|
2880
|
+
if (existsSync2(bunLockPath)) {
|
|
2710
2881
|
discovery.isBun = true;
|
|
2711
2882
|
log.debug("Detected Bun project (bun.lockb found)");
|
|
2712
2883
|
}
|
|
2713
2884
|
const vscodeDir = resolve(targetPath, ".vscode");
|
|
2885
|
+
const cursorDir = resolve(targetPath, ".cursor");
|
|
2714
2886
|
const ideaDir = resolve(targetPath, ".idea");
|
|
2715
|
-
if (
|
|
2716
|
-
discovery.detectedIde = "
|
|
2887
|
+
if (existsSync2(cursorDir)) {
|
|
2888
|
+
discovery.detectedIde = "cursor";
|
|
2889
|
+
log.debug("Detected Cursor (.cursor directory found)");
|
|
2890
|
+
} else if (existsSync2(vscodeDir)) {
|
|
2891
|
+
discovery.detectedIde = "code";
|
|
2717
2892
|
log.debug("Detected VS Code (.vscode directory found)");
|
|
2718
|
-
} else if (
|
|
2893
|
+
} else if (existsSync2(ideaDir)) {
|
|
2719
2894
|
discovery.detectedIde = "idea";
|
|
2720
2895
|
log.debug("Detected IntelliJ IDEA (.idea directory found)");
|
|
2721
2896
|
}
|
|
2897
|
+
const claudeMdPath = resolve(targetPath, "CLAUDE.md");
|
|
2898
|
+
if (existsSync2(claudeMdPath)) {
|
|
2899
|
+
discovery.hasClaude = true;
|
|
2900
|
+
log.debug("Detected Claude Code project (CLAUDE.md found)");
|
|
2901
|
+
}
|
|
2722
2902
|
return discovery;
|
|
2723
2903
|
}
|
|
2724
2904
|
|
|
2725
2905
|
// src/commands/index.ts
|
|
2726
2906
|
var __filename = fileURLToPath(import.meta.url);
|
|
2727
|
-
var __dirname =
|
|
2907
|
+
var __dirname = dirname2(__filename);
|
|
2728
2908
|
function findPackageJson() {
|
|
2729
2909
|
const paths = [
|
|
2730
2910
|
join(__dirname, "../package.json"),
|
|
@@ -2732,7 +2912,7 @@ function findPackageJson() {
|
|
|
2732
2912
|
join(process.cwd(), "package.json")
|
|
2733
2913
|
];
|
|
2734
2914
|
for (const p of paths) {
|
|
2735
|
-
if (
|
|
2915
|
+
if (existsSync3(p)) {
|
|
2736
2916
|
return p;
|
|
2737
2917
|
}
|
|
2738
2918
|
}
|
|
@@ -2750,7 +2930,7 @@ function createCli() {
|
|
|
2750
2930
|
config.set("pkg", packageJson);
|
|
2751
2931
|
EnvironmentRecognizer.configure(config, log);
|
|
2752
2932
|
const completion = setupCompletion(config);
|
|
2753
|
-
program2.name("workon").description("Work on something great!").version(packageJson.version).argument("[project]", "Project name to open (supports project:command syntax)").option("-d, --debug", "Enable debug logging").option("--completion", "Setup shell tab completion").option("--shell", "Output shell commands for evaluation").option("--init", "Generate shell integration function").hook("preAction", async (thisCommand) => {
|
|
2933
|
+
program2.name("workon").description("Work on something great!").version(packageJson.version).enablePositionalOptions().argument("[project]", "Project name to open (supports project:command syntax)").option("-d, --debug", "Enable debug logging").option("--completion", "Setup shell tab completion").option("--shell", "Output shell commands for evaluation").option("--init", "Generate shell integration function").hook("preAction", async (thisCommand) => {
|
|
2754
2934
|
const opts = thisCommand.opts();
|
|
2755
2935
|
if (opts.debug) {
|
|
2756
2936
|
log.setLogLevel("debug");
|
|
@@ -2772,10 +2952,8 @@ function createCli() {
|
|
|
2772
2952
|
return;
|
|
2773
2953
|
}
|
|
2774
2954
|
if (project) {
|
|
2775
|
-
const
|
|
2776
|
-
|
|
2777
|
-
if (options.debug) args.push("--debug");
|
|
2778
|
-
await program2.parseAsync(["node", "workon", ...args]);
|
|
2955
|
+
const { runOpen: runOpen2 } = await Promise.resolve().then(() => (init_open(), open_exports));
|
|
2956
|
+
await runOpen2(project, { debug: options.debug, shell: options.shell }, { config, log });
|
|
2779
2957
|
return;
|
|
2780
2958
|
}
|
|
2781
2959
|
const environment = await EnvironmentRecognizer.recognize(File7.cwd());
|
|
@@ -2874,6 +3052,15 @@ workon() {
|
|
|
2874
3052
|
}
|
|
2875
3053
|
|
|
2876
3054
|
// src/cli.ts
|
|
3055
|
+
if (process.stdout.isTTY) {
|
|
3056
|
+
process.stdout.write("");
|
|
3057
|
+
}
|
|
2877
3058
|
var program = createCli();
|
|
2878
|
-
program.
|
|
3059
|
+
program.parseAsync().catch((error) => {
|
|
3060
|
+
if (error?.name === "ExitPromptError" || error?.message?.includes("SIGINT")) {
|
|
3061
|
+
process.exit(130);
|
|
3062
|
+
}
|
|
3063
|
+
console.error(error);
|
|
3064
|
+
process.exit(1);
|
|
3065
|
+
});
|
|
2879
3066
|
//# sourceMappingURL=cli.js.map
|