@nemus-cli/nemus 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -0
- package/README.md +23 -5
- package/bin/workspace.js +12 -0
- package/dist/commands/analyze-deps.js +5 -12
- package/dist/commands/archive.js +13 -18
- package/dist/commands/branch/create.js +5 -12
- package/dist/commands/branch/switch.js +14 -25
- package/dist/commands/cache/manager.js +13 -24
- package/dist/commands/cleanup.js +14 -26
- package/dist/commands/configure-claude.js +4 -8
- package/dist/commands/configure.js +38 -32
- package/dist/commands/dashboard/session-picker.js +19 -26
- package/dist/commands/dashboard/workspace-picker.js +19 -26
- package/dist/commands/delete.js +15 -30
- package/dist/commands/ghq-status.js +7 -12
- package/dist/commands/go.js +18 -27
- package/dist/commands/history.js +6 -13
- package/dist/commands/list.js +20 -29
- package/dist/commands/prune.js +196 -0
- package/dist/commands/remove-repo.js +21 -29
- package/dist/commands/save-context.js +5 -10
- package/dist/commands/sessions.js +19 -25
- package/dist/commands/suite/create.js +27 -53
- package/dist/commands/suite/delete.js +13 -25
- package/dist/commands/suite/export.js +20 -36
- package/dist/commands/suite/import.js +9 -20
- package/dist/commands/suite/use.js +9 -17
- package/dist/program.js +2 -0
- package/dist/utils/prompt.js +26 -0
- package/dist/utils/prompts.js +86 -118
- package/dist/utils/prune.js +70 -0
- package/package.json +3 -6
- package/scripts/release-notes.mjs +54 -0
- package/skills/config.md +17 -0
- package/skills/nemus/SKILL.md +7 -2
- package/skills/nemus/references/completion.md +22 -0
- package/skills/nemus/references/config.md +32 -0
- package/skills/nemus/references/prune.md +44 -0
- package/skills/nemus/references/reflect.md +43 -0
- package/skills/nemus/references/save-context.md +25 -0
- package/skills/prune-workspaces.md +23 -0
- package/skills/reflect.md +21 -0
- package/src/commands/analyze-deps.ts +5 -9
- package/src/commands/archive.ts +5 -7
- package/src/commands/branch/create.ts +5 -9
- package/src/commands/branch/switch.ts +14 -22
- package/src/commands/cache/manager.ts +13 -21
- package/src/commands/cleanup.ts +14 -23
- package/src/commands/configure-claude.ts +4 -5
- package/src/commands/configure.ts +35 -29
- package/src/commands/dashboard/session-picker.ts +6 -11
- package/src/commands/dashboard/workspace-picker.ts +6 -11
- package/src/commands/delete.test.ts +36 -42
- package/src/commands/delete.ts +15 -27
- package/src/commands/ghq-status.ts +5 -7
- package/src/commands/go.ts +18 -25
- package/src/commands/history.ts +6 -10
- package/src/commands/list.test.ts +19 -26
- package/src/commands/list.ts +24 -31
- package/src/commands/prune.ts +183 -0
- package/src/commands/remove-repo.ts +11 -17
- package/src/commands/save-context.ts +3 -5
- package/src/commands/sessions.ts +6 -10
- package/src/commands/suite/create.ts +28 -50
- package/src/commands/suite/delete.ts +14 -23
- package/src/commands/suite/export.ts +20 -33
- package/src/commands/suite/import.ts +9 -17
- package/src/commands/suite/use.ts +9 -14
- package/src/program.ts +2 -0
- package/src/utils/prompt.ts +16 -0
- package/src/utils/prompts.test.ts +70 -41
- package/src/utils/prompts.ts +98 -128
- package/src/utils/prune.test.ts +121 -0
- package/src/utils/prune.ts +109 -0
|
@@ -38,18 +38,13 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
38
38
|
return result;
|
|
39
39
|
};
|
|
40
40
|
})();
|
|
41
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
42
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
43
|
-
};
|
|
44
41
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
42
|
const claude_sessions_1 = require("../../utils/claude-sessions");
|
|
46
43
|
const launcher_1 = require("./launcher");
|
|
47
44
|
const agent_config_1 = require("../../utils/agent-config");
|
|
48
45
|
const colors_1 = require("../../utils/colors");
|
|
49
|
-
const
|
|
50
|
-
const inquirer_autocomplete_prompt_1 = __importDefault(require("inquirer-autocomplete-prompt"));
|
|
46
|
+
const prompt_1 = require("../../utils/prompt");
|
|
51
47
|
const fuzzy = __importStar(require("fuzzy"));
|
|
52
|
-
inquirer_1.default.registerPrompt('autocomplete', inquirer_autocomplete_prompt_1.default);
|
|
53
48
|
async function main() {
|
|
54
49
|
try {
|
|
55
50
|
const sessions = await (0, claude_sessions_1.getWorkspaceSessions)();
|
|
@@ -62,26 +57,24 @@ async function main() {
|
|
|
62
57
|
displayName: session.workspaceName,
|
|
63
58
|
timeLabel: session.lastActiveLabel,
|
|
64
59
|
}));
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
pageSize: 15,
|
|
84
|
-
}]);
|
|
60
|
+
const selected = await (0, prompt_1.search)({
|
|
61
|
+
message: 'Select session to resume:',
|
|
62
|
+
pageSize: 15,
|
|
63
|
+
source: async (term) => {
|
|
64
|
+
const searchInput = term || '';
|
|
65
|
+
const source = items.map(item => ({
|
|
66
|
+
name: `${item.displayName} ${(0, colors_1.colorize)(item.timeLabel, 'dim')}`,
|
|
67
|
+
value: item,
|
|
68
|
+
}));
|
|
69
|
+
if (!searchInput)
|
|
70
|
+
return source;
|
|
71
|
+
const results = fuzzy.filter(searchInput, items, { extract: (item) => item.displayName });
|
|
72
|
+
return results.map(result => ({
|
|
73
|
+
name: `${result.original.displayName} ${(0, colors_1.colorize)(result.original.timeLabel, 'dim')}`,
|
|
74
|
+
value: result.original,
|
|
75
|
+
}));
|
|
76
|
+
},
|
|
77
|
+
});
|
|
85
78
|
const item = selected;
|
|
86
79
|
const session = item.session;
|
|
87
80
|
const rawSessionId = session.sessionId;
|
|
@@ -38,17 +38,12 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
38
38
|
return result;
|
|
39
39
|
};
|
|
40
40
|
})();
|
|
41
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
42
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
43
|
-
};
|
|
44
41
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
42
|
const workspace_meta_1 = require("../../utils/workspace-meta");
|
|
46
43
|
const launcher_1 = require("./launcher");
|
|
47
44
|
const agent_config_1 = require("../../utils/agent-config");
|
|
48
|
-
const
|
|
49
|
-
const inquirer_autocomplete_prompt_1 = __importDefault(require("inquirer-autocomplete-prompt"));
|
|
45
|
+
const prompt_1 = require("../../utils/prompt");
|
|
50
46
|
const fuzzy = __importStar(require("fuzzy"));
|
|
51
|
-
inquirer_1.default.registerPrompt('autocomplete', inquirer_autocomplete_prompt_1.default);
|
|
52
47
|
async function main() {
|
|
53
48
|
try {
|
|
54
49
|
const workspaces = await (0, workspace_meta_1.listWorkspaces)();
|
|
@@ -61,26 +56,24 @@ async function main() {
|
|
|
61
56
|
path: ws.path,
|
|
62
57
|
repoCount: ws.metadata?.repositories?.length ?? 0,
|
|
63
58
|
}));
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
pageSize: 15,
|
|
83
|
-
}]);
|
|
59
|
+
const selected = await (0, prompt_1.search)({
|
|
60
|
+
message: 'Select workspace to launch agent in:',
|
|
61
|
+
pageSize: 15,
|
|
62
|
+
source: async (term) => {
|
|
63
|
+
const searchInput = term || '';
|
|
64
|
+
const source = items.map(item => ({
|
|
65
|
+
name: `${item.name} (${item.repoCount} repos)`,
|
|
66
|
+
value: item,
|
|
67
|
+
}));
|
|
68
|
+
if (!searchInput)
|
|
69
|
+
return source;
|
|
70
|
+
const results = fuzzy.filter(searchInput, items, { extract: (item) => item.name });
|
|
71
|
+
return results.map(result => ({
|
|
72
|
+
name: `${result.original.name} (${result.original.repoCount} repos)`,
|
|
73
|
+
value: result.original,
|
|
74
|
+
}));
|
|
75
|
+
},
|
|
76
|
+
});
|
|
84
77
|
const workspace = selected;
|
|
85
78
|
const agent = (0, agent_config_1.getPrimaryAgent)();
|
|
86
79
|
(0, launcher_1.launchAgentPane)(workspace.path, agent.launchCommand);
|
package/dist/commands/delete.js
CHANGED
|
@@ -32,9 +32,6 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
32
32
|
return result;
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
-
};
|
|
38
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
36
|
exports.registerDeleteCommand = registerDeleteCommand;
|
|
40
37
|
exports.main = main;
|
|
@@ -44,7 +41,7 @@ const workspace_meta_1 = require("../utils/workspace-meta");
|
|
|
44
41
|
const prompts_1 = require("../utils/prompts");
|
|
45
42
|
const logger_1 = require("../utils/logger");
|
|
46
43
|
const colors_1 = require("../utils/colors");
|
|
47
|
-
const
|
|
44
|
+
const prompt_1 = require("../utils/prompt");
|
|
48
45
|
const command_helpers_1 = require("../utils/command-helpers");
|
|
49
46
|
function registerDeleteCommand(parent) {
|
|
50
47
|
parent
|
|
@@ -105,16 +102,12 @@ async function handleDelete(opts) {
|
|
|
105
102
|
console.log('');
|
|
106
103
|
(0, logger_1.logWarning)('This will permanently delete all cloned repositories in the selected workspaces!');
|
|
107
104
|
if (!opts.yes) {
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
: `Delete these ${targets.length} workspaces?`,
|
|
115
|
-
default: true,
|
|
116
|
-
},
|
|
117
|
-
]);
|
|
105
|
+
const confirmed = await (0, prompt_1.confirm)({
|
|
106
|
+
message: targets.length === 1
|
|
107
|
+
? `Delete workspace ${targets[0].name}?`
|
|
108
|
+
: `Delete these ${targets.length} workspaces?`,
|
|
109
|
+
default: true,
|
|
110
|
+
});
|
|
118
111
|
if (!confirmed) {
|
|
119
112
|
(0, logger_1.logInfo)('Deletion cancelled');
|
|
120
113
|
process.exit(0);
|
|
@@ -171,14 +164,10 @@ async function handleDelete(opts) {
|
|
|
171
164
|
const confirmMessage = resolved.length === 1
|
|
172
165
|
? `Delete workspace ${resolved[0].name}?`
|
|
173
166
|
: `Delete these ${resolved.length} workspaces?`;
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
message: confirmMessage,
|
|
179
|
-
default: true,
|
|
180
|
-
},
|
|
181
|
-
]);
|
|
167
|
+
const confirmed = await (0, prompt_1.confirm)({
|
|
168
|
+
message: confirmMessage,
|
|
169
|
+
default: true,
|
|
170
|
+
});
|
|
182
171
|
if (confirmed) {
|
|
183
172
|
for (const { name, path: workspacePath } of resolved) {
|
|
184
173
|
try {
|
|
@@ -198,14 +187,10 @@ async function handleDelete(opts) {
|
|
|
198
187
|
(0, logger_1.logInfo)('No more workspaces remaining');
|
|
199
188
|
break;
|
|
200
189
|
}
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
message: 'Delete more workspaces?',
|
|
206
|
-
default: false,
|
|
207
|
-
},
|
|
208
|
-
]);
|
|
190
|
+
const deleteMore = await (0, prompt_1.confirm)({
|
|
191
|
+
message: 'Delete more workspaces?',
|
|
192
|
+
default: false,
|
|
193
|
+
});
|
|
209
194
|
if (!deleteMore) {
|
|
210
195
|
break;
|
|
211
196
|
}
|
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
3
|
exports.registerGhqStatusCommand = registerGhqStatusCommand;
|
|
7
4
|
const ghq_integration_1 = require("../utils/ghq-integration");
|
|
8
5
|
const logger_1 = require("../utils/logger");
|
|
9
6
|
const colors_1 = require("../utils/colors");
|
|
10
|
-
const
|
|
7
|
+
const prompt_1 = require("../utils/prompt");
|
|
11
8
|
function registerGhqStatusCommand(parent) {
|
|
12
9
|
parent
|
|
13
10
|
.command('ghq-status')
|
|
@@ -30,10 +27,9 @@ async function handleGhqStatus() {
|
|
|
30
27
|
console.log(` Managed repositories: ${(0, colors_1.colorize)(String(status.repoCount || 0), 'yellow')}`);
|
|
31
28
|
console.log('');
|
|
32
29
|
if (status.repoCount && status.repoCount > 0) {
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}]);
|
|
30
|
+
const showRepos = await (0, prompt_1.confirm)({
|
|
31
|
+
message: `View all ${status.repoCount} managed repositories?`, default: false,
|
|
32
|
+
});
|
|
37
33
|
if (showRepos) {
|
|
38
34
|
const repos = await (0, ghq_integration_1.ghqList)();
|
|
39
35
|
console.log('');
|
|
@@ -47,10 +43,9 @@ async function handleGhqStatus() {
|
|
|
47
43
|
(0, logger_1.logWarning)('ghq is not installed');
|
|
48
44
|
console.log('');
|
|
49
45
|
console.log('The workspace manager will use direct git clones.');
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}]);
|
|
46
|
+
const showInfo = await (0, prompt_1.confirm)({
|
|
47
|
+
message: 'Show ghq installation information?', default: true,
|
|
48
|
+
});
|
|
54
49
|
if (showInfo) {
|
|
55
50
|
(0, ghq_integration_1.displayGhqInfo)();
|
|
56
51
|
}
|
package/dist/commands/go.js
CHANGED
|
@@ -32,9 +32,6 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
32
32
|
return result;
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
-
};
|
|
38
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
36
|
exports.registerGoCommand = registerGoCommand;
|
|
40
37
|
const path = __importStar(require("path"));
|
|
@@ -45,10 +42,8 @@ const workspace_meta_1 = require("../utils/workspace-meta");
|
|
|
45
42
|
const claude_sessions_1 = require("../utils/claude-sessions");
|
|
46
43
|
const logger_1 = require("../utils/logger");
|
|
47
44
|
const colors_1 = require("../utils/colors");
|
|
48
|
-
const
|
|
49
|
-
const inquirer_autocomplete_prompt_1 = __importDefault(require("inquirer-autocomplete-prompt"));
|
|
45
|
+
const prompt_1 = require("../utils/prompt");
|
|
50
46
|
const fuzzy = __importStar(require("fuzzy"));
|
|
51
|
-
inquirer_1.default.registerPrompt('autocomplete', inquirer_autocomplete_prompt_1.default);
|
|
52
47
|
const TEMP_FILE = path.join(os.homedir(), '.workspace-last-go');
|
|
53
48
|
const RESUME_FLAG_FILE = path.join(os.homedir(), '.workspace-resume-session');
|
|
54
49
|
function registerGoCommand(parent) {
|
|
@@ -100,28 +95,24 @@ async function handleGo(workspaceArg) {
|
|
|
100
95
|
return 1;
|
|
101
96
|
return 0;
|
|
102
97
|
});
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
source
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
value: result.original.name,
|
|
120
|
-
}));
|
|
121
|
-
},
|
|
122
|
-
pageSize: 15,
|
|
98
|
+
const workspaceName = await (0, prompt_1.search)({
|
|
99
|
+
message: 'Select workspace to navigate to:',
|
|
100
|
+
pageSize: 15,
|
|
101
|
+
source: async (term) => {
|
|
102
|
+
const searchInput = term || '';
|
|
103
|
+
const source = items.map(item => ({
|
|
104
|
+
name: `${item.name} ${item.lastActiveLabel ? (0, colors_1.colorize)(item.lastActiveLabel, 'dim') : (0, colors_1.colorize)('no session', 'dim')} ${(0, colors_1.colorize)(`${item.repoCount} repos`, 'dim')}`,
|
|
105
|
+
value: item.name,
|
|
106
|
+
}));
|
|
107
|
+
if (!searchInput)
|
|
108
|
+
return source;
|
|
109
|
+
const results = fuzzy.filter(searchInput, items, { extract: (item) => item.name });
|
|
110
|
+
return results.map(result => ({
|
|
111
|
+
name: `${result.original.name} ${result.original.lastActiveLabel ? (0, colors_1.colorize)(result.original.lastActiveLabel, 'dim') : (0, colors_1.colorize)('no session', 'dim')} ${(0, colors_1.colorize)(`${result.original.repoCount} repos`, 'dim')}`,
|
|
112
|
+
value: result.original.name,
|
|
113
|
+
}));
|
|
123
114
|
},
|
|
124
|
-
|
|
115
|
+
});
|
|
125
116
|
selectedWorkspace = workspaceName;
|
|
126
117
|
}
|
|
127
118
|
const workspacePath = path.join(config_1.WORKSPACES_DIR, selectedWorkspace);
|
package/dist/commands/history.js
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
3
|
exports.registerHistoryCommand = registerHistoryCommand;
|
|
7
4
|
const history_1 = require("../utils/history");
|
|
8
5
|
const logger_1 = require("../utils/logger");
|
|
9
6
|
const colors_1 = require("../utils/colors");
|
|
10
|
-
const
|
|
7
|
+
const prompt_1 = require("../utils/prompt");
|
|
11
8
|
const command_helpers_1 = require("../utils/command-helpers");
|
|
12
9
|
const displayHistoryTable = (records) => {
|
|
13
10
|
if (records.length === 0) {
|
|
@@ -78,15 +75,11 @@ function registerHistoryCommand(parent) {
|
|
|
78
75
|
(0, logger_1.logSuccess)('Operation history cleared');
|
|
79
76
|
return;
|
|
80
77
|
}
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
default: false,
|
|
87
|
-
},
|
|
88
|
-
]);
|
|
89
|
-
if (confirm) {
|
|
78
|
+
const confirmed = await (0, prompt_1.confirm)({
|
|
79
|
+
message: 'Are you sure you want to clear operation history?',
|
|
80
|
+
default: false,
|
|
81
|
+
});
|
|
82
|
+
if (confirmed) {
|
|
90
83
|
await (0, history_1.clearHistory)();
|
|
91
84
|
(0, logger_1.logSuccess)('Operation history cleared');
|
|
92
85
|
}
|
package/dist/commands/list.js
CHANGED
|
@@ -32,9 +32,6 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
32
32
|
return result;
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
-
};
|
|
38
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
36
|
exports.registerListCommand = registerListCommand;
|
|
40
37
|
exports.main = main;
|
|
@@ -46,10 +43,8 @@ const claude_sessions_1 = require("../utils/claude-sessions");
|
|
|
46
43
|
const logger_1 = require("../utils/logger");
|
|
47
44
|
const output_1 = require("../utils/output");
|
|
48
45
|
const colors_1 = require("../utils/colors");
|
|
49
|
-
const
|
|
50
|
-
const inquirer_autocomplete_prompt_1 = __importDefault(require("inquirer-autocomplete-prompt"));
|
|
46
|
+
const prompt_1 = require("../utils/prompt");
|
|
51
47
|
const fuzzy = __importStar(require("fuzzy"));
|
|
52
|
-
inquirer_1.default.registerPrompt('autocomplete', inquirer_autocomplete_prompt_1.default);
|
|
53
48
|
const TEMP_FILE = path.join(os.homedir(), '.workspace-last-go');
|
|
54
49
|
const RESUME_FLAG_FILE = path.join(os.homedir(), '.workspace-resume-session');
|
|
55
50
|
function registerListCommand(parent) {
|
|
@@ -155,30 +150,26 @@ async function handleList(opts) {
|
|
|
155
150
|
}
|
|
156
151
|
console.log('');
|
|
157
152
|
if (items.length > 0 && (process.stdout.isTTY || process.env.VITEST === 'true')) {
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
source
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
value: result.original.name,
|
|
177
|
-
}));
|
|
178
|
-
},
|
|
179
|
-
pageSize: 15,
|
|
153
|
+
const selected = await (0, prompt_1.search)({
|
|
154
|
+
message: 'Select workspace to open:',
|
|
155
|
+
pageSize: 15,
|
|
156
|
+
source: async (term) => {
|
|
157
|
+
const searchInput = term || '';
|
|
158
|
+
const source = items.map(item => ({
|
|
159
|
+
name: `${item.name} ${item.lastActiveLabel ? (0, colors_1.colorize)(item.lastActiveLabel, 'dim') : (0, colors_1.colorize)('no session', 'dim')} ${(0, colors_1.colorize)(`${item.repoCount} repos`, 'dim')}`,
|
|
160
|
+
value: item.name,
|
|
161
|
+
}));
|
|
162
|
+
if (!searchInput)
|
|
163
|
+
return source;
|
|
164
|
+
const results = fuzzy.filter(searchInput, items, {
|
|
165
|
+
extract: (item) => item.name,
|
|
166
|
+
});
|
|
167
|
+
return results.map(result => ({
|
|
168
|
+
name: `${result.original.name} ${result.original.lastActiveLabel ? (0, colors_1.colorize)(result.original.lastActiveLabel, 'dim') : (0, colors_1.colorize)('no session', 'dim')} ${(0, colors_1.colorize)(`${result.original.repoCount} repos`, 'dim')}`,
|
|
169
|
+
value: result.original.name,
|
|
170
|
+
}));
|
|
180
171
|
},
|
|
181
|
-
|
|
172
|
+
});
|
|
182
173
|
const selectedItem = items.find(i => i.name === selected);
|
|
183
174
|
if (selectedItem) {
|
|
184
175
|
await fsPromises.writeFile(TEMP_FILE, selectedItem.wsPath, 'utf-8');
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.registerPruneCommand = registerPruneCommand;
|
|
37
|
+
exports.handlePrune = handlePrune;
|
|
38
|
+
const fs = __importStar(require("fs/promises"));
|
|
39
|
+
const validation_1 = require("../utils/validation");
|
|
40
|
+
const workspace_meta_1 = require("../utils/workspace-meta");
|
|
41
|
+
const claude_sessions_1 = require("../utils/claude-sessions");
|
|
42
|
+
const git_status_1 = require("../utils/git-status");
|
|
43
|
+
const logger_1 = require("../utils/logger");
|
|
44
|
+
const colors_1 = require("../utils/colors");
|
|
45
|
+
const prompt_1 = require("../utils/prompt");
|
|
46
|
+
const command_helpers_1 = require("../utils/command-helpers");
|
|
47
|
+
const output_1 = require("../utils/output");
|
|
48
|
+
const prune_1 = require("../utils/prune");
|
|
49
|
+
const DEFAULT_DAYS = 30;
|
|
50
|
+
function registerPruneCommand(parent) {
|
|
51
|
+
parent
|
|
52
|
+
.command('prune')
|
|
53
|
+
.description('Delete workspaces with no recent activity (safe by default)')
|
|
54
|
+
.option('-d, --days <n>', `Consider a workspace stale after N days of inactivity (default ${DEFAULT_DAYS})`)
|
|
55
|
+
.option('--include-dirty', 'Also prune workspaces with uncommitted/unpushed changes (default: protected)')
|
|
56
|
+
.option('--dry-run', 'Show what would be pruned without deleting anything')
|
|
57
|
+
.option('-y, --yes', 'Skip the confirmation prompt')
|
|
58
|
+
.option('--json', 'Output the prune plan as JSON (never deletes)')
|
|
59
|
+
.action(async (opts, cmd) => {
|
|
60
|
+
const globalOpts = (0, command_helpers_1.getGlobalOpts)(cmd);
|
|
61
|
+
await handlePrune({ ...opts, ...globalOpts });
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
function parseDays(raw) {
|
|
65
|
+
if (raw === undefined)
|
|
66
|
+
return DEFAULT_DAYS;
|
|
67
|
+
const n = Number(raw);
|
|
68
|
+
if (!Number.isFinite(n) || n < 0)
|
|
69
|
+
return null;
|
|
70
|
+
return Math.floor(n);
|
|
71
|
+
}
|
|
72
|
+
async function handlePrune(opts) {
|
|
73
|
+
const json = !!opts.json;
|
|
74
|
+
const days = parseDays(opts.days);
|
|
75
|
+
if (days === null) {
|
|
76
|
+
if (json)
|
|
77
|
+
(0, output_1.outputJsonError)('--days must be a non-negative number');
|
|
78
|
+
else
|
|
79
|
+
(0, logger_1.logError)('--days must be a non-negative number');
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
const [workspaces, sessions] = await Promise.all([(0, workspace_meta_1.listWorkspaces)(), (0, claude_sessions_1.getWorkspaceSessions)()]);
|
|
84
|
+
const sessionMap = new Map(sessions.map((s) => [s.workspaceName, s]));
|
|
85
|
+
const now = Date.now();
|
|
86
|
+
const candidates = workspaces.map((ws) => {
|
|
87
|
+
const session = sessionMap.get(ws.name);
|
|
88
|
+
const createdRaw = ws.metadata?.createdAt ? Date.parse(ws.metadata.createdAt) : NaN;
|
|
89
|
+
const forPrune = {
|
|
90
|
+
name: ws.name,
|
|
91
|
+
path: ws.path,
|
|
92
|
+
repoDirNames: (ws.metadata?.repositories ?? []).map((r) => r.directoryName),
|
|
93
|
+
lastActiveAt: session ? session.lastActiveAt.getTime() : 0,
|
|
94
|
+
createdAt: Number.isFinite(createdRaw) ? createdRaw : 0,
|
|
95
|
+
};
|
|
96
|
+
return (0, prune_1.toCandidate)(forPrune, now);
|
|
97
|
+
});
|
|
98
|
+
const stale = candidates.filter((c) => (0, prune_1.isStale)(c, days));
|
|
99
|
+
if (stale.length === 0) {
|
|
100
|
+
if (json) {
|
|
101
|
+
(0, output_1.outputJson)({ ok: true, days, prunable: [], protected: [], scanned: workspaces.length });
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
(0, logger_1.logInfo)(`No workspaces inactive for ${days}+ days (scanned ${workspaces.length}).`);
|
|
105
|
+
}
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
// Compute the plan. The git safety check only runs for stale workspaces.
|
|
109
|
+
const plan = await (0, prune_1.planPrune)(stale, (c) => (0, git_status_1.getAllReposStatus)(c.path, c.repoDirNames, 3), !!opts.includeDirty);
|
|
110
|
+
if (json) {
|
|
111
|
+
(0, output_1.outputJson)({
|
|
112
|
+
ok: true,
|
|
113
|
+
days,
|
|
114
|
+
scanned: workspaces.length,
|
|
115
|
+
prunable: plan.prunable.map((c) => ({ name: c.name, path: c.path, ageDays: c.ageDays, repos: c.repoDirNames.length })),
|
|
116
|
+
protected: plan.protected.map((p) => ({ name: p.candidate.name, ageDays: p.candidate.ageDays, reason: p.reason })),
|
|
117
|
+
});
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
// Human report.
|
|
121
|
+
console.log('\n' + '='.repeat(60));
|
|
122
|
+
console.log((0, colors_1.colorize)(`Prune — workspaces inactive for ${days}+ days`, 'bright'));
|
|
123
|
+
console.log('='.repeat(60) + '\n');
|
|
124
|
+
if (plan.protected.length > 0) {
|
|
125
|
+
(0, logger_1.logWarning)(`Protected (${plan.protected.length}) — skipped due to unsaved work:`);
|
|
126
|
+
for (const p of plan.protected) {
|
|
127
|
+
console.log(` ${(0, colors_1.colorize)('•', 'yellow')} ${(0, colors_1.colorize)(p.candidate.name, 'cyan')} — ${p.reason} ${(0, colors_1.colorize)(`(${ageLabel(p.candidate)})`, 'gray')}`);
|
|
128
|
+
}
|
|
129
|
+
console.log('');
|
|
130
|
+
}
|
|
131
|
+
if (plan.prunable.length === 0) {
|
|
132
|
+
(0, logger_1.logInfo)('Nothing safe to prune.');
|
|
133
|
+
if (plan.protected.length > 0)
|
|
134
|
+
(0, logger_1.logInfo)('Re-run with --include-dirty to include the protected ones (careful).');
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
console.log(`${(0, colors_1.colorize)('Prunable', 'bright')} (${plan.prunable.length}):`);
|
|
138
|
+
for (const c of plan.prunable) {
|
|
139
|
+
const repoLabel = c.repoDirNames.length === 1 ? '1 repo' : `${c.repoDirNames.length} repos`;
|
|
140
|
+
console.log(` ${(0, colors_1.colorize)('✗', 'red')} ${(0, colors_1.colorize)(c.name, 'cyan')} ${(0, colors_1.colorize)(`(${ageLabel(c)}, ${repoLabel})`, 'gray')}`);
|
|
141
|
+
}
|
|
142
|
+
console.log('');
|
|
143
|
+
if (opts.dryRun) {
|
|
144
|
+
(0, logger_1.logInfo)(`Dry run — nothing deleted. ${plan.prunable.length} workspace(s) would be pruned.`);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
(0, logger_1.logWarning)('This permanently deletes the selected workspaces and every cloned repo inside them!');
|
|
148
|
+
if (!opts.yes) {
|
|
149
|
+
const confirmed = await (0, prompt_1.confirm)({
|
|
150
|
+
message: plan.prunable.length === 1
|
|
151
|
+
? `Prune workspace ${plan.prunable[0].name}?`
|
|
152
|
+
: `Prune these ${plan.prunable.length} workspaces?`,
|
|
153
|
+
default: false,
|
|
154
|
+
});
|
|
155
|
+
if (!confirmed) {
|
|
156
|
+
(0, logger_1.logInfo)('Prune cancelled');
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
let deleted = 0;
|
|
161
|
+
for (const c of plan.prunable) {
|
|
162
|
+
let target;
|
|
163
|
+
try {
|
|
164
|
+
// Re-validate through the same choke point delete uses: enforces the
|
|
165
|
+
// name allowlist and pins the path inside WORKSPACES_DIR.
|
|
166
|
+
target = (0, validation_1.safeWorkspacePath)(c.name);
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
(0, logger_1.logError)(error instanceof Error ? error.message : `Invalid workspace name "${c.name}"`);
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
try {
|
|
173
|
+
await fs.rm(target, { recursive: true, force: true });
|
|
174
|
+
(0, logger_1.logSuccess)(`Pruned "${(0, colors_1.colorize)(c.name, 'cyan')}"`);
|
|
175
|
+
deleted++;
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
(0, logger_1.logError)(`Failed to prune "${c.name}"`);
|
|
179
|
+
if (error instanceof Error)
|
|
180
|
+
(0, logger_1.logError)(error.message);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
(0, logger_1.logStep)(`Pruned ${deleted} of ${plan.prunable.length} workspace(s).`);
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
if (json)
|
|
187
|
+
(0, output_1.outputJsonError)(error instanceof Error ? error.message : 'prune failed');
|
|
188
|
+
else
|
|
189
|
+
(0, logger_1.logError)(error instanceof Error ? error.message : 'prune failed');
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
function ageLabel(c) {
|
|
194
|
+
const base = c.ageDays === 1 ? '1 day' : `${c.ageDays} days`;
|
|
195
|
+
return c.fromSession ? `${base} since last session` : `${base} since created, no sessions`;
|
|
196
|
+
}
|