@staticpayload/zai-code 1.2.14 → 1.4.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/README.md +85 -154
- package/dist/apply.d.ts.map +1 -1
- package/dist/apply.js +43 -16
- package/dist/apply.js.map +1 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +5 -1
- package/dist/auth.js.map +1 -1
- package/dist/commands.d.ts.map +1 -1
- package/dist/commands.js +1021 -47
- package/dist/commands.js.map +1 -1
- package/dist/context/context_builder.d.ts.map +1 -1
- package/dist/context/context_builder.js +18 -3
- package/dist/context/context_builder.js.map +1 -1
- package/dist/context/project_memory.d.ts +2 -2
- package/dist/context/project_memory.d.ts.map +1 -1
- package/dist/context/project_memory.js +14 -4
- package/dist/context/project_memory.js.map +1 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +51 -7
- package/dist/doctor.js.map +1 -1
- package/dist/history.d.ts.map +1 -1
- package/dist/history.js +13 -8
- package/dist/history.js.map +1 -1
- package/dist/mode_prompts.d.ts.map +1 -1
- package/dist/mode_prompts.js +25 -22
- package/dist/mode_prompts.js.map +1 -1
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +107 -39
- package/dist/orchestrator.js.map +1 -1
- package/dist/planner.js +18 -18
- package/dist/planner.js.map +1 -1
- package/dist/rollback.d.ts +1 -0
- package/dist/rollback.d.ts.map +1 -1
- package/dist/rollback.js +28 -4
- package/dist/rollback.js.map +1 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +22 -9
- package/dist/runtime.js.map +1 -1
- package/dist/session.d.ts +1 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +8 -1
- package/dist/session.js.map +1 -1
- package/dist/settings.d.ts +1 -0
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +8 -3
- package/dist/settings.js.map +1 -1
- package/dist/shell.d.ts.map +1 -1
- package/dist/shell.js +85 -14
- package/dist/shell.js.map +1 -1
- package/dist/task_runner.d.ts.map +1 -1
- package/dist/task_runner.js +20 -2
- package/dist/task_runner.js.map +1 -1
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +566 -88
- package/dist/tui.js.map +1 -1
- package/dist/ui.d.ts +7 -0
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +113 -11
- package/dist/ui.js.map +1 -1
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +6 -0
- package/dist/workspace.js.map +1 -1
- package/dist/workspace_model.d.ts +1 -1
- package/dist/workspace_model.d.ts.map +1 -1
- package/dist/workspace_model.js +8 -1
- package/dist/workspace_model.js.map +1 -1
- package/package.json +4 -2
package/dist/commands.js
CHANGED
|
@@ -44,12 +44,17 @@ const history_1 = require("./history");
|
|
|
44
44
|
const settings_menu_1 = require("./settings_menu");
|
|
45
45
|
const shell_1 = require("./shell");
|
|
46
46
|
const workspace_model_1 = require("./workspace_model");
|
|
47
|
+
const auth_1 = require("./auth");
|
|
48
|
+
const runtime_1 = require("./runtime");
|
|
49
|
+
const workspace_1 = require("./workspace");
|
|
47
50
|
const apply_1 = require("./apply");
|
|
48
51
|
const ui_1 = require("./ui");
|
|
49
52
|
const planner_1 = require("./planner");
|
|
50
53
|
const task_runner_1 = require("./task_runner");
|
|
51
54
|
const rollback_1 = require("./rollback");
|
|
55
|
+
const mode_prompts_1 = require("./mode_prompts");
|
|
52
56
|
const path = __importStar(require("path"));
|
|
57
|
+
const fs = __importStar(require("fs"));
|
|
53
58
|
// Parse input to detect slash commands
|
|
54
59
|
function parseInput(input) {
|
|
55
60
|
const trimmed = input.trim();
|
|
@@ -68,19 +73,541 @@ function parseInput(input) {
|
|
|
68
73
|
}
|
|
69
74
|
// Stub handlers for all commands
|
|
70
75
|
const handlers = {
|
|
71
|
-
help: () => {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
76
|
+
help: (ctx) => {
|
|
77
|
+
const topic = ctx.args[0]?.toLowerCase();
|
|
78
|
+
if (topic === 'modes') {
|
|
79
|
+
console.log('');
|
|
80
|
+
console.log((0, ui_1.info)('Available Modes:'));
|
|
81
|
+
console.log('');
|
|
82
|
+
console.log(' auto ⚡ YOLO mode: execute tasks directly without confirmation');
|
|
83
|
+
console.log(' edit ✏️ Default: plan → generate → review → apply workflow');
|
|
84
|
+
console.log(' ask ❓ Read-only: answer questions, no file changes');
|
|
85
|
+
console.log(' explain 📖 Read-only: explain code concepts');
|
|
86
|
+
console.log(' review 👁 Read-only: code review and analysis');
|
|
87
|
+
console.log(' debug 🔧 Investigate and fix issues');
|
|
88
|
+
console.log('');
|
|
89
|
+
console.log((0, ui_1.hint)('/mode <name> to switch'));
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (topic === 'workflow') {
|
|
93
|
+
console.log('');
|
|
94
|
+
console.log((0, ui_1.info)('Standard Workflow:'));
|
|
95
|
+
console.log('');
|
|
96
|
+
console.log(' 1. Type a task (e.g., "add error handling to auth.ts")');
|
|
97
|
+
console.log(' 2. /plan - Generate execution plan');
|
|
98
|
+
console.log(' 3. /generate - Create file changes');
|
|
99
|
+
console.log(' 4. /diff - Review changes');
|
|
100
|
+
console.log(' 5. /apply - Apply changes');
|
|
101
|
+
console.log(' 6. /undo - Rollback if needed');
|
|
102
|
+
console.log('');
|
|
103
|
+
console.log((0, ui_1.info)('Quick Workflow:'));
|
|
104
|
+
console.log('');
|
|
105
|
+
console.log(' /do <task> - Plan + generate in one step');
|
|
106
|
+
console.log(' /run <task> - Plan + generate + apply (YOLO)');
|
|
107
|
+
console.log('');
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (topic === 'shortcuts') {
|
|
111
|
+
console.log('');
|
|
112
|
+
console.log((0, ui_1.info)('Keyboard Shortcuts:'));
|
|
113
|
+
console.log('');
|
|
114
|
+
console.log(' Ctrl+D /do <task> - Quick execute');
|
|
115
|
+
console.log(' Ctrl+R /run <task> - Auto mode run');
|
|
116
|
+
console.log(' Ctrl+P /plan - Generate plan');
|
|
117
|
+
console.log(' Ctrl+G /generate - Create changes');
|
|
118
|
+
console.log(' Ctrl+Z /undo - Rollback');
|
|
119
|
+
console.log(' Ctrl+A /ask <q> - Quick question');
|
|
120
|
+
console.log(' Ctrl+F /fix <desc> - Quick debug');
|
|
121
|
+
console.log(' Ctrl+C Exit');
|
|
122
|
+
console.log('');
|
|
123
|
+
console.log((0, ui_1.info)('Command Aliases:'));
|
|
124
|
+
console.log('');
|
|
125
|
+
console.log(' /h = /help /p = /plan /g = /generate');
|
|
126
|
+
console.log(' /d = /diff /a = /apply /u = /undo');
|
|
127
|
+
console.log(' /s = /status /c = /context /f = /files');
|
|
128
|
+
console.log('');
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (topic === 'quick') {
|
|
132
|
+
console.log('');
|
|
133
|
+
console.log((0, ui_1.info)('Quick Commands:'));
|
|
134
|
+
console.log('');
|
|
135
|
+
console.log(' /do <task> Plan + generate in one step');
|
|
136
|
+
console.log(' Example: /do add input validation to login form');
|
|
137
|
+
console.log('');
|
|
138
|
+
console.log(' /run <task> Plan + generate + apply automatically');
|
|
139
|
+
console.log(' Example: /run fix the typo in README.md');
|
|
140
|
+
console.log('');
|
|
141
|
+
console.log(' /ask <q> Quick question without changing mode');
|
|
142
|
+
console.log(' Example: /ask what does this function do?');
|
|
143
|
+
console.log('');
|
|
144
|
+
console.log(' /fix <desc> Quick debug mode task');
|
|
145
|
+
console.log(' Example: /fix login button not working');
|
|
146
|
+
console.log('');
|
|
147
|
+
console.log(' /commit Generate AI commit message');
|
|
148
|
+
console.log(' Example: /commit (auto) or /commit feat: add login');
|
|
149
|
+
console.log('');
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
// Default help
|
|
153
|
+
console.log('');
|
|
154
|
+
console.log((0, ui_1.info)('zai·code Commands'));
|
|
155
|
+
console.log('');
|
|
156
|
+
console.log((0, ui_1.dim)('Quick Actions:'));
|
|
157
|
+
console.log(' /do <task> Plan + generate in one step');
|
|
158
|
+
console.log(' /run <task> Plan + generate + apply (YOLO)');
|
|
159
|
+
console.log(' /ask <q> Quick question');
|
|
160
|
+
console.log(' /fix <desc> Quick debug task');
|
|
161
|
+
console.log('');
|
|
162
|
+
console.log((0, ui_1.dim)('Workflow:'));
|
|
163
|
+
console.log(' /plan Generate execution plan');
|
|
164
|
+
console.log(' /generate Create file changes');
|
|
165
|
+
console.log(' /diff [full] Review pending changes');
|
|
166
|
+
console.log(' /apply [-f] Apply changes');
|
|
167
|
+
console.log(' /undo [n] Rollback operations');
|
|
168
|
+
console.log(' /retry Retry last failed operation');
|
|
169
|
+
console.log(' /clear Clear current task');
|
|
170
|
+
console.log('');
|
|
171
|
+
console.log((0, ui_1.dim)('Files:'));
|
|
172
|
+
console.log(' /open <path> Add file to context');
|
|
173
|
+
console.log(' /close <path> Remove from context');
|
|
174
|
+
console.log(' /files List open files');
|
|
175
|
+
console.log(' /search <q> Search files');
|
|
176
|
+
console.log(' /read <path> View file contents');
|
|
177
|
+
console.log(' /tree [depth] Show file tree');
|
|
178
|
+
console.log('');
|
|
179
|
+
console.log((0, ui_1.dim)('Modes & Settings:'));
|
|
180
|
+
console.log(' /mode [name] Set mode (edit/ask/auto/debug/review/explain)');
|
|
181
|
+
console.log(' /model [id] Select AI model');
|
|
182
|
+
console.log(' /settings Open settings menu');
|
|
183
|
+
console.log(' /dry-run Toggle dry-run mode');
|
|
184
|
+
console.log('');
|
|
185
|
+
console.log((0, ui_1.dim)('Git:'));
|
|
186
|
+
console.log(' /git [cmd] Git operations (status/log/diff/stash/pop)');
|
|
187
|
+
console.log(' /commit [msg] AI-powered commit');
|
|
188
|
+
console.log('');
|
|
189
|
+
console.log((0, ui_1.dim)('System:'));
|
|
190
|
+
console.log(' /status Show session status');
|
|
191
|
+
console.log(' /doctor System health check');
|
|
192
|
+
console.log(' /version Show version');
|
|
193
|
+
console.log(' /reset Reset session');
|
|
194
|
+
console.log(' /exit Exit zcode');
|
|
195
|
+
console.log('');
|
|
196
|
+
console.log((0, ui_1.hint)('/help <topic> for details: modes, workflow, shortcuts, quick'));
|
|
197
|
+
},
|
|
198
|
+
// Quick execute - plan + generate in one step
|
|
199
|
+
do: async (ctx) => {
|
|
200
|
+
const task = ctx.args.join(' ');
|
|
201
|
+
if (!task) {
|
|
202
|
+
console.log('Usage: /do <task>');
|
|
203
|
+
console.log('Example: /do add input validation to login form');
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
// Set intent
|
|
207
|
+
(0, session_1.setIntent)(task);
|
|
208
|
+
console.log((0, ui_1.info)(`Task: ${task}`));
|
|
209
|
+
// Plan
|
|
210
|
+
console.log((0, ui_1.dim)('Planning...'));
|
|
211
|
+
const planResult = await (0, planner_1.runPlannerLoop)();
|
|
212
|
+
if (!planResult.success) {
|
|
213
|
+
console.log((0, ui_1.error)(planResult.message));
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
console.log((0, ui_1.success)(`Plan: ${planResult.plan?.length || 0} steps`));
|
|
217
|
+
// Generate
|
|
218
|
+
console.log((0, ui_1.dim)('Generating...'));
|
|
219
|
+
const genResult = await (0, planner_1.runGenerateLoop)();
|
|
220
|
+
if (!genResult.success) {
|
|
221
|
+
console.log((0, ui_1.error)(genResult.message));
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
const fileCount = (genResult.changes?.files?.length || 0) + (genResult.changes?.diffs?.length || 0);
|
|
225
|
+
console.log((0, ui_1.success)(`Generated ${fileCount} file change(s)`));
|
|
226
|
+
console.log((0, ui_1.hint)('/diff to review, /apply to execute'));
|
|
227
|
+
},
|
|
228
|
+
// Full auto run - plan + generate + apply
|
|
229
|
+
run: async (ctx) => {
|
|
230
|
+
const task = ctx.args.join(' ');
|
|
231
|
+
if (!task) {
|
|
232
|
+
console.log('Usage: /run <task>');
|
|
233
|
+
console.log('Warning: This will apply changes automatically!');
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
const session = (0, session_1.getSession)();
|
|
237
|
+
if (session.dryRun) {
|
|
238
|
+
console.log((0, ui_1.error)('Dry-run mode enabled. Use /dry-run off first.'));
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
// Set intent
|
|
242
|
+
(0, session_1.setIntent)(task);
|
|
243
|
+
console.log((0, ui_1.info)(`Task: ${task}`));
|
|
244
|
+
// Plan
|
|
245
|
+
console.log((0, ui_1.dim)('Planning...'));
|
|
246
|
+
const planResult = await (0, planner_1.runPlannerLoop)();
|
|
247
|
+
if (!planResult.success) {
|
|
248
|
+
console.log((0, ui_1.error)(planResult.message));
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
console.log((0, ui_1.success)(`Plan: ${planResult.plan?.length || 0} steps`));
|
|
252
|
+
// Generate
|
|
253
|
+
console.log((0, ui_1.dim)('Generating...'));
|
|
254
|
+
const genResult = await (0, planner_1.runGenerateLoop)();
|
|
255
|
+
if (!genResult.success) {
|
|
256
|
+
console.log((0, ui_1.error)(genResult.message));
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
// Apply
|
|
260
|
+
console.log((0, ui_1.dim)('Applying...'));
|
|
261
|
+
const actions = session.pendingActions;
|
|
262
|
+
if (!actions || ((!actions.files || actions.files.length === 0) && (!actions.diffs || actions.diffs.length === 0))) {
|
|
263
|
+
console.log('No changes to apply.');
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
const result = (0, apply_1.applyResponse)(actions, {
|
|
267
|
+
basePath: session.workingDirectory,
|
|
268
|
+
dryRun: false,
|
|
269
|
+
});
|
|
270
|
+
if (!result.success) {
|
|
271
|
+
for (const failed of result.failed) {
|
|
272
|
+
console.log((0, ui_1.error)(`Failed: ${failed.path}: ${failed.error}`));
|
|
273
|
+
}
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
(0, session_1.setPendingActions)(null);
|
|
277
|
+
console.log((0, ui_1.success)(`Applied ${result.applied.length} change(s)`));
|
|
278
|
+
console.log((0, ui_1.hint)('/undo to rollback'));
|
|
279
|
+
},
|
|
280
|
+
// Retry last failed operation
|
|
281
|
+
retry: async () => {
|
|
282
|
+
const session = (0, session_1.getSession)();
|
|
283
|
+
if (session.lastPlan && session.lastPlan.length > 0 && !session.lastDiff) {
|
|
284
|
+
// Have plan but no diff - retry generate
|
|
285
|
+
console.log((0, ui_1.dim)('Retrying generation...'));
|
|
286
|
+
const result = await (0, planner_1.runGenerateLoop)();
|
|
287
|
+
if (result.success) {
|
|
288
|
+
console.log((0, ui_1.success)('Generation succeeded on retry'));
|
|
289
|
+
console.log((0, ui_1.hint)('/diff to review'));
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
console.log((0, ui_1.error)(result.message));
|
|
293
|
+
}
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if ((0, session_1.getIntent)() && (!session.lastPlan || session.lastPlan.length === 0)) {
|
|
297
|
+
// Have intent but no plan - retry plan
|
|
298
|
+
console.log((0, ui_1.dim)('Retrying planning...'));
|
|
299
|
+
const result = await (0, planner_1.runPlannerLoop)();
|
|
300
|
+
if (result.success) {
|
|
301
|
+
console.log((0, ui_1.success)('Planning succeeded on retry'));
|
|
302
|
+
console.log((0, ui_1.hint)('/generate to create changes'));
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
console.log((0, ui_1.error)(result.message));
|
|
306
|
+
}
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
console.log('Nothing to retry.');
|
|
310
|
+
},
|
|
311
|
+
// Clear current task
|
|
312
|
+
clear: () => {
|
|
313
|
+
(0, session_1.clearIntent)();
|
|
314
|
+
(0, session_1.setLastPlan)(null);
|
|
315
|
+
(0, session_1.setLastDiff)(null);
|
|
316
|
+
(0, session_1.setPendingActions)(null);
|
|
317
|
+
console.log('Task cleared.');
|
|
318
|
+
},
|
|
319
|
+
// Close/remove file from context
|
|
320
|
+
close: (ctx) => {
|
|
321
|
+
const filePath = ctx.args[0];
|
|
322
|
+
if (!filePath) {
|
|
323
|
+
console.log('Usage: /close <path> or /close all');
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
if (filePath === 'all') {
|
|
327
|
+
(0, session_1.clearOpenFiles)();
|
|
328
|
+
console.log('All files removed from context.');
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
(0, session_1.removeOpenFile)(filePath);
|
|
332
|
+
console.log(`Removed: ${filePath}`);
|
|
333
|
+
},
|
|
334
|
+
// Search files in workspace
|
|
335
|
+
search: (ctx) => {
|
|
336
|
+
const query = ctx.args.join(' ');
|
|
337
|
+
if (!query) {
|
|
338
|
+
console.log('Usage: /search <pattern>');
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
const session = (0, session_1.getSession)();
|
|
342
|
+
const ws = (0, workspace_model_1.getWorkspace)(session.workingDirectory);
|
|
343
|
+
ws.indexFileTree();
|
|
344
|
+
const files = ws.getFileIndex();
|
|
345
|
+
const pattern = query.toLowerCase();
|
|
346
|
+
const matches = files.filter(f => f.path.toLowerCase().includes(pattern));
|
|
347
|
+
if (matches.length === 0) {
|
|
348
|
+
console.log(`No files matching "${query}"`);
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
console.log(`Found ${matches.length} file(s):`);
|
|
352
|
+
for (const m of matches.slice(0, 20)) {
|
|
353
|
+
console.log(` ${m.path}`);
|
|
354
|
+
}
|
|
355
|
+
if (matches.length > 20) {
|
|
356
|
+
console.log((0, ui_1.dim)(` ... and ${matches.length - 20} more`));
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
// Quick ask without changing mode
|
|
360
|
+
'ask-quick': async (ctx) => {
|
|
361
|
+
const question = ctx.args.join(' ');
|
|
362
|
+
if (!question) {
|
|
363
|
+
console.log('Usage: /ask <question>');
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
let apiKey;
|
|
367
|
+
try {
|
|
368
|
+
apiKey = await (0, auth_1.ensureAuthenticated)();
|
|
369
|
+
if (!apiKey) {
|
|
370
|
+
console.log((0, ui_1.error)('No API key configured.'));
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
catch (e) {
|
|
375
|
+
console.log((0, ui_1.error)(`Auth failed: ${e?.message}`));
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
const session = (0, session_1.getSession)();
|
|
379
|
+
const modePrompt = (0, mode_prompts_1.buildSystemPrompt)('ask', session.workingDirectory);
|
|
380
|
+
console.log((0, ui_1.dim)('Thinking...'));
|
|
381
|
+
const result = await (0, runtime_1.execute)({
|
|
382
|
+
instruction: `${modePrompt}\n\nQuestion: ${question}\n\nAnswer concisely.`,
|
|
383
|
+
enforceSchema: false,
|
|
384
|
+
}, apiKey);
|
|
385
|
+
if (result.success && result.output) {
|
|
386
|
+
const text = typeof result.output === 'string' ? result.output : JSON.stringify(result.output, null, 2);
|
|
387
|
+
console.log(text);
|
|
388
|
+
}
|
|
389
|
+
else {
|
|
390
|
+
console.log((0, ui_1.error)(result.error || 'Failed'));
|
|
391
|
+
}
|
|
392
|
+
},
|
|
393
|
+
// Quick fix/debug
|
|
394
|
+
fix: async (ctx) => {
|
|
395
|
+
const description = ctx.args.join(' ');
|
|
396
|
+
if (!description) {
|
|
397
|
+
console.log('Usage: /fix <problem description>');
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
(0, session_1.setMode)('debug');
|
|
401
|
+
(0, session_1.setIntent)(`Fix: ${description}`);
|
|
402
|
+
console.log((0, ui_1.info)(`Debug task: ${description}`));
|
|
403
|
+
console.log((0, ui_1.hint)('/plan to start debugging'));
|
|
404
|
+
},
|
|
405
|
+
// Save session to disk
|
|
406
|
+
save: () => {
|
|
407
|
+
const session = (0, session_1.getSession)();
|
|
408
|
+
const ws = (0, workspace_model_1.getWorkspace)(session.workingDirectory);
|
|
409
|
+
const saved = ws.saveState();
|
|
410
|
+
if (saved) {
|
|
411
|
+
console.log((0, ui_1.success)('Session saved to .zai/workspace.json'));
|
|
412
|
+
}
|
|
413
|
+
else {
|
|
414
|
+
console.log((0, ui_1.error)('Failed to save session'));
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
// Load session from disk
|
|
418
|
+
load: () => {
|
|
419
|
+
const session = (0, session_1.getSession)();
|
|
420
|
+
const ws = (0, workspace_model_1.getWorkspace)(session.workingDirectory);
|
|
421
|
+
const restored = ws.restoreState();
|
|
422
|
+
if (restored) {
|
|
423
|
+
console.log((0, ui_1.success)('Session restored'));
|
|
424
|
+
console.log(`Mode: ${session.mode}`);
|
|
425
|
+
console.log(`Intent: ${session.currentIntent || 'none'}`);
|
|
426
|
+
console.log(`Files: ${session.openFiles.length}`);
|
|
427
|
+
}
|
|
428
|
+
else {
|
|
429
|
+
console.log('No saved session found.');
|
|
430
|
+
}
|
|
431
|
+
},
|
|
432
|
+
// Git commit helper
|
|
433
|
+
commit: async (ctx) => {
|
|
434
|
+
const message = ctx.args.join(' ');
|
|
435
|
+
const session = (0, session_1.getSession)();
|
|
436
|
+
const gitInfo = (0, git_1.getGitInfo)(session.workingDirectory);
|
|
437
|
+
if (!gitInfo.isRepo) {
|
|
438
|
+
console.log((0, ui_1.error)('Not a git repository.'));
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
if (!gitInfo.isDirty) {
|
|
442
|
+
console.log('Nothing to commit (working tree clean).');
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
if (!message) {
|
|
446
|
+
// Generate commit message
|
|
447
|
+
let apiKey;
|
|
448
|
+
try {
|
|
449
|
+
apiKey = await (0, auth_1.ensureAuthenticated)();
|
|
450
|
+
if (!apiKey) {
|
|
451
|
+
console.log('Usage: /commit <message>');
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
catch {
|
|
456
|
+
console.log('Usage: /commit <message>');
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
console.log((0, ui_1.dim)('Generating commit message...'));
|
|
460
|
+
// Get git diff
|
|
461
|
+
const { execSync } = require('child_process');
|
|
462
|
+
let diff = '';
|
|
463
|
+
try {
|
|
464
|
+
diff = execSync('git diff --staged', { cwd: session.workingDirectory, encoding: 'utf-8', maxBuffer: 50000 });
|
|
465
|
+
if (!diff) {
|
|
466
|
+
diff = execSync('git diff', { cwd: session.workingDirectory, encoding: 'utf-8', maxBuffer: 50000 });
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
catch {
|
|
470
|
+
console.log((0, ui_1.error)('Failed to get git diff'));
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
if (!diff) {
|
|
474
|
+
console.log('No changes to commit.');
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
const result = await (0, runtime_1.execute)({
|
|
478
|
+
instruction: `Generate a concise git commit message for these changes. Use conventional commit format (feat/fix/docs/refactor/etc). Output ONLY the commit message, nothing else.\n\nDiff:\n${diff.substring(0, 5000)}`,
|
|
479
|
+
enforceSchema: false,
|
|
480
|
+
}, apiKey);
|
|
481
|
+
if (result.success && result.output) {
|
|
482
|
+
const msg = typeof result.output === 'string' ? result.output.trim() : String(result.output);
|
|
483
|
+
console.log(`Suggested: ${msg}`);
|
|
484
|
+
console.log((0, ui_1.hint)(`/commit ${msg}`));
|
|
485
|
+
}
|
|
486
|
+
else {
|
|
487
|
+
console.log('Usage: /commit <message>');
|
|
488
|
+
}
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
// Execute commit
|
|
492
|
+
const { execSync } = require('child_process');
|
|
493
|
+
try {
|
|
494
|
+
execSync('git add -A', { cwd: session.workingDirectory, stdio: 'pipe' });
|
|
495
|
+
execSync(`git commit -m "${message.replace(/"/g, '\\"')}"`, { cwd: session.workingDirectory, stdio: 'pipe' });
|
|
496
|
+
console.log((0, ui_1.success)(`Committed: ${message}`));
|
|
497
|
+
}
|
|
498
|
+
catch (e) {
|
|
499
|
+
console.log((0, ui_1.error)(`Commit failed: ${e?.message || e}`));
|
|
500
|
+
}
|
|
501
|
+
},
|
|
502
|
+
// Version info
|
|
503
|
+
version: () => {
|
|
504
|
+
try {
|
|
505
|
+
const pkg = require('../package.json');
|
|
506
|
+
console.log(`zai-code v${pkg.version}`);
|
|
507
|
+
console.log(`Node.js ${process.version}`);
|
|
508
|
+
console.log(`Platform: ${process.platform} ${process.arch}`);
|
|
509
|
+
}
|
|
510
|
+
catch {
|
|
511
|
+
console.log('zai-code');
|
|
512
|
+
}
|
|
513
|
+
},
|
|
514
|
+
// Status - comprehensive overview
|
|
515
|
+
status: () => {
|
|
516
|
+
const session = (0, session_1.getSession)();
|
|
517
|
+
const gitInfo = (0, git_1.getGitInfo)(session.workingDirectory);
|
|
518
|
+
const undoCount = (0, rollback_1.getUndoCount)();
|
|
519
|
+
console.log('');
|
|
520
|
+
console.log((0, ui_1.info)('Session Status'));
|
|
521
|
+
console.log('');
|
|
522
|
+
// Mode with icon
|
|
523
|
+
const modeIcons = {
|
|
524
|
+
'auto': '⚡', 'edit': '✏️', 'ask': '❓', 'debug': '🔧', 'review': '👁', 'explain': '📖'
|
|
525
|
+
};
|
|
526
|
+
const modeIcon = modeIcons[session.mode] || '';
|
|
527
|
+
console.log(` Mode: ${modeIcon} ${session.mode}${session.dryRun ? ' (dry-run)' : ''}`);
|
|
528
|
+
console.log(` Model: ${(0, settings_1.getModel)()}`);
|
|
529
|
+
console.log(` Directory: ${session.workingDirectory}`);
|
|
530
|
+
console.log('');
|
|
531
|
+
// Git status
|
|
532
|
+
if (gitInfo.isRepo) {
|
|
533
|
+
const status = gitInfo.isDirty ? `${gitInfo.uncommittedFiles} uncommitted` : 'clean';
|
|
534
|
+
console.log(` Git: ${gitInfo.branch}${gitInfo.isDirty ? '*' : ''} (${status})`);
|
|
535
|
+
}
|
|
536
|
+
else {
|
|
537
|
+
console.log(' Git: not a repository');
|
|
538
|
+
}
|
|
539
|
+
console.log('');
|
|
540
|
+
// Session state
|
|
541
|
+
console.log(` Files: ${session.openFiles.length} in context`);
|
|
542
|
+
// Current task
|
|
543
|
+
if (session.currentIntent) {
|
|
544
|
+
const truncated = session.currentIntent.length > 50
|
|
545
|
+
? session.currentIntent.substring(0, 50) + '...'
|
|
546
|
+
: session.currentIntent;
|
|
547
|
+
console.log(` Task: ${truncated}`);
|
|
548
|
+
}
|
|
549
|
+
else {
|
|
550
|
+
console.log(` Task: none`);
|
|
551
|
+
}
|
|
552
|
+
// Plan status
|
|
553
|
+
if (session.lastPlan && session.lastPlan.length > 0) {
|
|
554
|
+
console.log(` Plan: ${session.lastPlan.length} step(s)`);
|
|
555
|
+
}
|
|
556
|
+
else {
|
|
557
|
+
console.log(` Plan: none`);
|
|
558
|
+
}
|
|
559
|
+
// Pending changes
|
|
560
|
+
if (session.pendingActions) {
|
|
561
|
+
const fileCount = (session.pendingActions.files?.length || 0);
|
|
562
|
+
const diffCount = (session.pendingActions.diffs?.length || 0);
|
|
563
|
+
console.log(` Pending: ${fileCount + diffCount} change(s)`);
|
|
564
|
+
}
|
|
565
|
+
else {
|
|
566
|
+
console.log(` Pending: none`);
|
|
567
|
+
}
|
|
568
|
+
console.log(` Undo: ${undoCount} operation(s) in stack`);
|
|
569
|
+
console.log('');
|
|
570
|
+
// Suggestions based on state
|
|
571
|
+
if (session.pendingActions || session.lastDiff) {
|
|
572
|
+
console.log((0, ui_1.hint)('/diff to review, /apply to execute'));
|
|
573
|
+
}
|
|
574
|
+
else if (session.lastPlan && session.lastPlan.length > 0) {
|
|
575
|
+
console.log((0, ui_1.hint)('/generate to create changes'));
|
|
576
|
+
}
|
|
577
|
+
else if (session.currentIntent) {
|
|
578
|
+
console.log((0, ui_1.hint)('/plan to create execution plan'));
|
|
579
|
+
}
|
|
580
|
+
else {
|
|
581
|
+
console.log((0, ui_1.hint)('Type a task to get started'));
|
|
582
|
+
}
|
|
583
|
+
},
|
|
584
|
+
// YOLO mode shortcut
|
|
585
|
+
yolo: () => {
|
|
586
|
+
(0, session_1.setMode)('auto');
|
|
587
|
+
console.log((0, ui_1.success)('⚡ YOLO mode activated!'));
|
|
588
|
+
console.log((0, ui_1.dim)('Tasks will execute directly without confirmation.'));
|
|
589
|
+
console.log((0, ui_1.hint)('Type a task or use /run <task>'));
|
|
590
|
+
},
|
|
591
|
+
// Quick mode switches
|
|
592
|
+
edit: () => {
|
|
593
|
+
(0, session_1.setMode)('edit');
|
|
594
|
+
console.log((0, ui_1.success)('Edit mode activated'));
|
|
595
|
+
console.log((0, ui_1.hint)('Type a task, then /plan → /generate → /diff → /apply'));
|
|
596
|
+
},
|
|
597
|
+
debug: () => {
|
|
598
|
+
(0, session_1.setMode)('debug');
|
|
599
|
+
console.log((0, ui_1.success)('🔧 Debug mode activated'));
|
|
600
|
+
console.log((0, ui_1.hint)('Describe the bug or use /fix <description>'));
|
|
601
|
+
},
|
|
602
|
+
review: () => {
|
|
603
|
+
(0, session_1.setMode)('review');
|
|
604
|
+
console.log((0, ui_1.success)('👁 Review mode activated'));
|
|
605
|
+
console.log((0, ui_1.hint)('Ask for code review or analysis'));
|
|
606
|
+
},
|
|
607
|
+
explain: () => {
|
|
608
|
+
(0, session_1.setMode)('explain');
|
|
609
|
+
console.log((0, ui_1.success)('📖 Explain mode activated'));
|
|
610
|
+
console.log((0, ui_1.hint)('Ask about code concepts'));
|
|
84
611
|
},
|
|
85
612
|
reset: () => {
|
|
86
613
|
(0, session_1.resetSession)();
|
|
@@ -142,13 +669,68 @@ const handlers = {
|
|
|
142
669
|
}
|
|
143
670
|
},
|
|
144
671
|
open: (ctx) => {
|
|
145
|
-
const
|
|
146
|
-
if (!
|
|
672
|
+
const filePath = ctx.args[0];
|
|
673
|
+
if (!filePath) {
|
|
147
674
|
console.log('Usage: /open <path>');
|
|
148
675
|
return;
|
|
149
676
|
}
|
|
150
|
-
(0, session_1.
|
|
151
|
-
|
|
677
|
+
const session = (0, session_1.getSession)();
|
|
678
|
+
const fullPath = path.isAbsolute(filePath) ? filePath : path.join(session.workingDirectory, filePath);
|
|
679
|
+
if (!fs.existsSync(fullPath)) {
|
|
680
|
+
console.log((0, ui_1.error)(`File not found: ${filePath}`));
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
(0, session_1.addOpenFile)(filePath);
|
|
684
|
+
console.log((0, ui_1.success)(`Added to context: ${filePath}`));
|
|
685
|
+
},
|
|
686
|
+
// Read/view file contents
|
|
687
|
+
read: (ctx) => {
|
|
688
|
+
const filePath = ctx.args[0];
|
|
689
|
+
if (!filePath) {
|
|
690
|
+
console.log('Usage: /read <path> [lines]');
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
const session = (0, session_1.getSession)();
|
|
694
|
+
const fullPath = path.isAbsolute(filePath) ? filePath : path.join(session.workingDirectory, filePath);
|
|
695
|
+
if (!fs.existsSync(fullPath)) {
|
|
696
|
+
console.log((0, ui_1.error)(`File not found: ${filePath}`));
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
const maxLines = ctx.args[1] ? parseInt(ctx.args[1], 10) : 50;
|
|
700
|
+
const content = (0, workspace_1.getFileContent)(fullPath, 100000);
|
|
701
|
+
if (!content) {
|
|
702
|
+
console.log((0, ui_1.error)('File too large or unreadable'));
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
const lines = content.split('\n');
|
|
706
|
+
const displayLines = lines.slice(0, maxLines);
|
|
707
|
+
console.log((0, ui_1.dim)(`--- ${filePath} (${lines.length} lines) ---`));
|
|
708
|
+
displayLines.forEach((line, i) => {
|
|
709
|
+
console.log(`${String(i + 1).padStart(4)} | ${line}`);
|
|
710
|
+
});
|
|
711
|
+
if (lines.length > maxLines) {
|
|
712
|
+
console.log((0, ui_1.dim)(`... ${lines.length - maxLines} more lines`));
|
|
713
|
+
}
|
|
714
|
+
},
|
|
715
|
+
// Cat file (alias for read, full content)
|
|
716
|
+
cat: (ctx) => {
|
|
717
|
+
const filePath = ctx.args[0];
|
|
718
|
+
if (!filePath) {
|
|
719
|
+
console.log('Usage: /cat <path>');
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
const session = (0, session_1.getSession)();
|
|
723
|
+
const fullPath = path.isAbsolute(filePath) ? filePath : path.join(session.workingDirectory, filePath);
|
|
724
|
+
if (!fs.existsSync(fullPath)) {
|
|
725
|
+
console.log((0, ui_1.error)(`File not found: ${filePath}`));
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
const content = (0, workspace_1.getFileContent)(fullPath, 500000);
|
|
729
|
+
if (!content) {
|
|
730
|
+
console.log((0, ui_1.error)('File too large or unreadable'));
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
console.log(content);
|
|
152
734
|
},
|
|
153
735
|
mode: (ctx) => {
|
|
154
736
|
const newMode = ctx.args[0]?.toLowerCase();
|
|
@@ -272,7 +854,7 @@ const handlers = {
|
|
|
272
854
|
console.log((0, ui_1.error)(`Generation failed: ${e?.message || e}`));
|
|
273
855
|
}
|
|
274
856
|
},
|
|
275
|
-
diff: () => {
|
|
857
|
+
diff: (ctx) => {
|
|
276
858
|
const session = (0, session_1.getSession)();
|
|
277
859
|
// Check if there's a diff to display
|
|
278
860
|
if (!session.lastDiff) {
|
|
@@ -280,12 +862,25 @@ const handlers = {
|
|
|
280
862
|
return;
|
|
281
863
|
}
|
|
282
864
|
const response = session.lastDiff;
|
|
865
|
+
const showFull = ctx.args[0] === 'full';
|
|
283
866
|
// Display file operations if present
|
|
284
867
|
if (response.files && response.files.length > 0) {
|
|
285
868
|
for (const file of response.files) {
|
|
286
|
-
|
|
869
|
+
const opColor = file.operation === 'create' ? '\x1b[32m' :
|
|
870
|
+
file.operation === 'delete' ? '\x1b[31m' : '\x1b[33m';
|
|
871
|
+
const reset = '\x1b[0m';
|
|
872
|
+
console.log(`${opColor}--- ${file.operation.toUpperCase()}: ${file.path} ---${reset}`);
|
|
287
873
|
if (file.content && file.operation !== 'delete') {
|
|
288
|
-
|
|
874
|
+
const lines = file.content.split('\n');
|
|
875
|
+
const maxLines = showFull ? lines.length : 50;
|
|
876
|
+
for (let i = 0; i < Math.min(lines.length, maxLines); i++) {
|
|
877
|
+
const lineNum = String(i + 1).padStart(4);
|
|
878
|
+
const prefix = file.operation === 'create' ? '\x1b[32m+' : ' ';
|
|
879
|
+
console.log(`${prefix}${lineNum} | ${lines[i]}${reset}`);
|
|
880
|
+
}
|
|
881
|
+
if (lines.length > maxLines) {
|
|
882
|
+
console.log((0, ui_1.dim)(`... ${lines.length - maxLines} more lines (use /diff full to see all)`));
|
|
883
|
+
}
|
|
289
884
|
}
|
|
290
885
|
console.log('');
|
|
291
886
|
}
|
|
@@ -293,33 +888,49 @@ const handlers = {
|
|
|
293
888
|
// Display diffs if present
|
|
294
889
|
if (response.diffs && response.diffs.length > 0) {
|
|
295
890
|
for (const diff of response.diffs) {
|
|
296
|
-
console.log(
|
|
891
|
+
console.log(`\x1b[33m--- MODIFY: ${diff.file} ---\x1b[0m`);
|
|
297
892
|
for (const hunk of diff.hunks) {
|
|
298
|
-
console.log(
|
|
299
|
-
|
|
893
|
+
console.log(`\x1b[36m@@ lines ${hunk.start}-${hunk.end} @@\x1b[0m`);
|
|
894
|
+
const lines = hunk.content.split('\n');
|
|
895
|
+
for (const line of lines) {
|
|
896
|
+
if (line.startsWith('+')) {
|
|
897
|
+
console.log(`\x1b[32m${line}\x1b[0m`);
|
|
898
|
+
}
|
|
899
|
+
else if (line.startsWith('-')) {
|
|
900
|
+
console.log(`\x1b[31m${line}\x1b[0m`);
|
|
901
|
+
}
|
|
902
|
+
else {
|
|
903
|
+
console.log(line);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
300
906
|
}
|
|
301
907
|
console.log('');
|
|
302
908
|
}
|
|
303
909
|
}
|
|
304
|
-
//
|
|
305
|
-
|
|
306
|
-
|
|
910
|
+
// Summary
|
|
911
|
+
const fileCount = (response.files?.length || 0);
|
|
912
|
+
const diffCount = (response.diffs?.length || 0);
|
|
913
|
+
if (fileCount === 0 && diffCount === 0) {
|
|
307
914
|
console.log('No file changes in last response.');
|
|
308
915
|
return;
|
|
309
916
|
}
|
|
917
|
+
console.log((0, ui_1.dim)(`Total: ${fileCount} file operation(s), ${diffCount} diff(s)`));
|
|
918
|
+
console.log((0, ui_1.hint)('/apply to execute changes'));
|
|
310
919
|
},
|
|
311
|
-
apply: () => {
|
|
920
|
+
apply: (ctx) => {
|
|
312
921
|
const session = (0, session_1.getSession)();
|
|
922
|
+
const force = ctx.args[0] === '--force' || ctx.args[0] === '-f';
|
|
313
923
|
// Check dry run mode
|
|
314
|
-
if (session.dryRun) {
|
|
924
|
+
if (session.dryRun && !force) {
|
|
315
925
|
console.log((0, ui_1.error)('Dry-run mode. Apply blocked.'));
|
|
316
|
-
console.log((0, ui_1.hint)('/dry-run off'));
|
|
926
|
+
console.log((0, ui_1.hint)('/dry-run off or /apply --force'));
|
|
317
927
|
return;
|
|
318
928
|
}
|
|
319
929
|
// Warn on dirty git
|
|
320
930
|
const gitInfo = (0, git_1.getGitInfo)(session.workingDirectory);
|
|
321
|
-
if (gitInfo.isRepo && gitInfo.isDirty) {
|
|
322
|
-
console.log((0, ui_1.info)('Warning: uncommitted changes exist.'));
|
|
931
|
+
if (gitInfo.isRepo && gitInfo.isDirty && !force) {
|
|
932
|
+
console.log((0, ui_1.info)('Warning: uncommitted git changes exist.'));
|
|
933
|
+
console.log((0, ui_1.hint)('Consider committing first, or use /apply --force'));
|
|
323
934
|
}
|
|
324
935
|
// Check if there are pending actions
|
|
325
936
|
if (!session.pendingActions) {
|
|
@@ -333,26 +944,49 @@ const handlers = {
|
|
|
333
944
|
}
|
|
334
945
|
const actions = session.pendingActions;
|
|
335
946
|
// Check if there are actual file operations
|
|
336
|
-
|
|
337
|
-
|
|
947
|
+
const fileCount = (actions.files?.length || 0);
|
|
948
|
+
const diffCount = (actions.diffs?.length || 0);
|
|
949
|
+
if (fileCount === 0 && diffCount === 0) {
|
|
338
950
|
console.log('No file changes to apply.');
|
|
339
951
|
return;
|
|
340
952
|
}
|
|
953
|
+
console.log((0, ui_1.info)(`Applying ${fileCount + diffCount} change(s)...`));
|
|
341
954
|
// Apply using the apply engine
|
|
342
955
|
const result = (0, apply_1.applyResponse)(actions, {
|
|
343
956
|
basePath: session.workingDirectory,
|
|
344
957
|
dryRun: false,
|
|
345
958
|
});
|
|
959
|
+
// Show results
|
|
960
|
+
for (const applied of result.applied) {
|
|
961
|
+
console.log((0, ui_1.success)(` ${applied}`));
|
|
962
|
+
}
|
|
963
|
+
for (const failed of result.failed) {
|
|
964
|
+
console.log((0, ui_1.error)(` Failed: ${failed.path}: ${failed.error}`));
|
|
965
|
+
}
|
|
346
966
|
if (!result.success) {
|
|
347
|
-
|
|
348
|
-
console.log((0, ui_1.error)(`Failed: ${failed.path}: ${failed.error}`));
|
|
349
|
-
}
|
|
967
|
+
console.log((0, ui_1.error)(`\nSome operations failed. Use /undo to rollback.`));
|
|
350
968
|
return;
|
|
351
969
|
}
|
|
352
970
|
// Clear pending actions after success
|
|
353
971
|
(0, session_1.setPendingActions)(null);
|
|
354
|
-
|
|
355
|
-
|
|
972
|
+
(0, session_1.setLastDiff)(null);
|
|
973
|
+
(0, session_1.clearIntent)();
|
|
974
|
+
(0, session_1.setLastPlan)(null);
|
|
975
|
+
console.log('');
|
|
976
|
+
console.log((0, ui_1.success)(`Applied ${result.applied.length} change(s) successfully.`));
|
|
977
|
+
console.log((0, ui_1.hint)('/undo to rollback'));
|
|
978
|
+
// Log to history
|
|
979
|
+
if (session.currentIntent) {
|
|
980
|
+
(0, history_1.logTask)({
|
|
981
|
+
timestamp: new Date().toISOString(),
|
|
982
|
+
intent: session.currentIntent,
|
|
983
|
+
intentType: session.intentType || 'COMMAND',
|
|
984
|
+
mode: session.mode,
|
|
985
|
+
model: (0, settings_1.getModel)(),
|
|
986
|
+
filesCount: result.applied.length,
|
|
987
|
+
outcome: 'success',
|
|
988
|
+
});
|
|
989
|
+
}
|
|
356
990
|
},
|
|
357
991
|
workspace: () => {
|
|
358
992
|
const ws = (0, workspace_model_1.getWorkspace)();
|
|
@@ -360,6 +994,54 @@ const handlers = {
|
|
|
360
994
|
console.log(ws.printTreeSummary());
|
|
361
995
|
console.log(`Root: ${ws.getRoot()}`);
|
|
362
996
|
},
|
|
997
|
+
// Tree view of workspace
|
|
998
|
+
tree: (ctx) => {
|
|
999
|
+
const maxDepth = ctx.args[0] ? parseInt(ctx.args[0], 10) : 3;
|
|
1000
|
+
const session = (0, session_1.getSession)();
|
|
1001
|
+
const ws = (0, workspace_model_1.getWorkspace)(session.workingDirectory);
|
|
1002
|
+
const tree = ws.indexFileTree(maxDepth);
|
|
1003
|
+
function printTree(node, prefix = '', isLast = true) {
|
|
1004
|
+
const connector = isLast ? '└── ' : '├── ';
|
|
1005
|
+
const extension = isLast ? ' ' : '│ ';
|
|
1006
|
+
if (node.path !== '.') {
|
|
1007
|
+
const icon = node.type === 'directory' ? '📁' : '📄';
|
|
1008
|
+
console.log(`${prefix}${connector}${icon} ${node.name}`);
|
|
1009
|
+
}
|
|
1010
|
+
if (node.children) {
|
|
1011
|
+
const children = node.children;
|
|
1012
|
+
children.forEach((child, index) => {
|
|
1013
|
+
const childIsLast = index === children.length - 1;
|
|
1014
|
+
const newPrefix = node.path === '.' ? '' : prefix + extension;
|
|
1015
|
+
printTree(child, newPrefix, childIsLast);
|
|
1016
|
+
});
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
console.log((0, ui_1.dim)(`${session.workingDirectory}`));
|
|
1020
|
+
printTree(tree);
|
|
1021
|
+
},
|
|
1022
|
+
// List files matching pattern
|
|
1023
|
+
ls: (ctx) => {
|
|
1024
|
+
const pattern = ctx.args[0] || '.';
|
|
1025
|
+
const session = (0, session_1.getSession)();
|
|
1026
|
+
const targetPath = path.isAbsolute(pattern) ? pattern : path.join(session.workingDirectory, pattern);
|
|
1027
|
+
try {
|
|
1028
|
+
const stats = fs.statSync(targetPath);
|
|
1029
|
+
if (stats.isDirectory()) {
|
|
1030
|
+
const entries = fs.readdirSync(targetPath, { withFileTypes: true });
|
|
1031
|
+
for (const entry of entries) {
|
|
1032
|
+
const icon = entry.isDirectory() ? '📁' : '📄';
|
|
1033
|
+
const size = entry.isFile() ? ` (${fs.statSync(path.join(targetPath, entry.name)).size} bytes)` : '';
|
|
1034
|
+
console.log(`${icon} ${entry.name}${size}`);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
else {
|
|
1038
|
+
console.log(`${pattern}: ${stats.size} bytes`);
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
catch (e) {
|
|
1042
|
+
console.log((0, ui_1.error)(`Cannot access: ${pattern}`));
|
|
1043
|
+
}
|
|
1044
|
+
},
|
|
363
1045
|
doctor: async () => {
|
|
364
1046
|
const { runDiagnostics, formatDiagnostics } = await Promise.resolve().then(() => __importStar(require('./doctor')));
|
|
365
1047
|
const results = await runDiagnostics();
|
|
@@ -573,29 +1255,321 @@ const handlers = {
|
|
|
573
1255
|
console.log('Usage: /dry-run [on | off]');
|
|
574
1256
|
}
|
|
575
1257
|
},
|
|
576
|
-
git: () => {
|
|
1258
|
+
git: (ctx) => {
|
|
577
1259
|
const session = (0, session_1.getSession)();
|
|
578
|
-
const
|
|
579
|
-
|
|
1260
|
+
const gitInfo = (0, git_1.getGitInfo)(session.workingDirectory);
|
|
1261
|
+
const subcommand = ctx.args[0];
|
|
1262
|
+
if (!gitInfo.isRepo) {
|
|
580
1263
|
console.log('Not a git repository.');
|
|
581
1264
|
return;
|
|
582
1265
|
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
console.log(`
|
|
1266
|
+
// /git status (default)
|
|
1267
|
+
if (!subcommand || subcommand === 'status') {
|
|
1268
|
+
console.log(`Repository: ${gitInfo.repoName}`);
|
|
1269
|
+
console.log(`Branch: ${gitInfo.branch}`);
|
|
1270
|
+
console.log(`Status: ${gitInfo.isDirty ? 'dirty' : 'clean'}`);
|
|
1271
|
+
if (gitInfo.uncommittedFiles > 0) {
|
|
1272
|
+
console.log(`Uncommitted files: ${gitInfo.uncommittedFiles}`);
|
|
1273
|
+
// Show changed files
|
|
1274
|
+
try {
|
|
1275
|
+
const { execSync } = require('child_process');
|
|
1276
|
+
const status = execSync('git status --porcelain', {
|
|
1277
|
+
cwd: session.workingDirectory,
|
|
1278
|
+
encoding: 'utf-8',
|
|
1279
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
1280
|
+
});
|
|
1281
|
+
const lines = status.trim().split('\n').filter((l) => l);
|
|
1282
|
+
for (const line of lines.slice(0, 10)) {
|
|
1283
|
+
const status = line.substring(0, 2);
|
|
1284
|
+
const file = line.substring(3);
|
|
1285
|
+
const statusColor = status.includes('M') ? '\x1b[33m' :
|
|
1286
|
+
status.includes('A') ? '\x1b[32m' :
|
|
1287
|
+
status.includes('D') ? '\x1b[31m' : '\x1b[36m';
|
|
1288
|
+
console.log(` ${statusColor}${status}\x1b[0m ${file}`);
|
|
1289
|
+
}
|
|
1290
|
+
if (lines.length > 10) {
|
|
1291
|
+
console.log((0, ui_1.dim)(` ... and ${lines.length - 10} more`));
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
catch {
|
|
1295
|
+
// Ignore
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
return;
|
|
1299
|
+
}
|
|
1300
|
+
// /git log
|
|
1301
|
+
if (subcommand === 'log') {
|
|
1302
|
+
try {
|
|
1303
|
+
const { execSync } = require('child_process');
|
|
1304
|
+
const log = execSync('git log --oneline -10', {
|
|
1305
|
+
cwd: session.workingDirectory,
|
|
1306
|
+
encoding: 'utf-8'
|
|
1307
|
+
});
|
|
1308
|
+
console.log('Recent commits:');
|
|
1309
|
+
console.log(log);
|
|
1310
|
+
}
|
|
1311
|
+
catch (e) {
|
|
1312
|
+
console.log((0, ui_1.error)('Failed to get git log'));
|
|
1313
|
+
}
|
|
1314
|
+
return;
|
|
1315
|
+
}
|
|
1316
|
+
// /git diff
|
|
1317
|
+
if (subcommand === 'diff') {
|
|
1318
|
+
try {
|
|
1319
|
+
const { execSync } = require('child_process');
|
|
1320
|
+
const diff = execSync('git diff --stat', {
|
|
1321
|
+
cwd: session.workingDirectory,
|
|
1322
|
+
encoding: 'utf-8'
|
|
1323
|
+
});
|
|
1324
|
+
if (diff.trim()) {
|
|
1325
|
+
console.log(diff);
|
|
1326
|
+
}
|
|
1327
|
+
else {
|
|
1328
|
+
console.log('No unstaged changes.');
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
catch (e) {
|
|
1332
|
+
console.log((0, ui_1.error)('Failed to get git diff'));
|
|
1333
|
+
}
|
|
1334
|
+
return;
|
|
1335
|
+
}
|
|
1336
|
+
// /git stash
|
|
1337
|
+
if (subcommand === 'stash') {
|
|
1338
|
+
try {
|
|
1339
|
+
const { execSync } = require('child_process');
|
|
1340
|
+
execSync('git stash', { cwd: session.workingDirectory, stdio: 'pipe' });
|
|
1341
|
+
console.log((0, ui_1.success)('Changes stashed'));
|
|
1342
|
+
}
|
|
1343
|
+
catch (e) {
|
|
1344
|
+
console.log((0, ui_1.error)('Failed to stash'));
|
|
1345
|
+
}
|
|
1346
|
+
return;
|
|
1347
|
+
}
|
|
1348
|
+
// /git pop
|
|
1349
|
+
if (subcommand === 'pop') {
|
|
1350
|
+
try {
|
|
1351
|
+
const { execSync } = require('child_process');
|
|
1352
|
+
execSync('git stash pop', { cwd: session.workingDirectory, stdio: 'pipe' });
|
|
1353
|
+
console.log((0, ui_1.success)('Stash popped'));
|
|
1354
|
+
}
|
|
1355
|
+
catch (e) {
|
|
1356
|
+
console.log((0, ui_1.error)('Failed to pop stash'));
|
|
1357
|
+
}
|
|
1358
|
+
return;
|
|
1359
|
+
}
|
|
1360
|
+
console.log('Usage: /git [status|log|diff|stash|pop]');
|
|
1361
|
+
},
|
|
1362
|
+
// Create new file
|
|
1363
|
+
touch: (ctx) => {
|
|
1364
|
+
const filePath = ctx.args[0];
|
|
1365
|
+
if (!filePath) {
|
|
1366
|
+
console.log('Usage: /touch <path>');
|
|
1367
|
+
return;
|
|
1368
|
+
}
|
|
1369
|
+
const session = (0, session_1.getSession)();
|
|
1370
|
+
const fullPath = path.isAbsolute(filePath) ? filePath : path.join(session.workingDirectory, filePath);
|
|
1371
|
+
if (fs.existsSync(fullPath)) {
|
|
1372
|
+
console.log((0, ui_1.error)(`File already exists: ${filePath}`));
|
|
1373
|
+
return;
|
|
1374
|
+
}
|
|
1375
|
+
try {
|
|
1376
|
+
const dir = path.dirname(fullPath);
|
|
1377
|
+
if (!fs.existsSync(dir)) {
|
|
1378
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1379
|
+
}
|
|
1380
|
+
fs.writeFileSync(fullPath, '', 'utf-8');
|
|
1381
|
+
console.log((0, ui_1.success)(`Created: ${filePath}`));
|
|
1382
|
+
(0, session_1.addOpenFile)(filePath);
|
|
1383
|
+
}
|
|
1384
|
+
catch (e) {
|
|
1385
|
+
console.log((0, ui_1.error)(`Failed to create: ${e?.message}`));
|
|
1386
|
+
}
|
|
1387
|
+
},
|
|
1388
|
+
// Make directory
|
|
1389
|
+
mkdir: (ctx) => {
|
|
1390
|
+
const dirPath = ctx.args[0];
|
|
1391
|
+
if (!dirPath) {
|
|
1392
|
+
console.log('Usage: /mkdir <path>');
|
|
1393
|
+
return;
|
|
1394
|
+
}
|
|
1395
|
+
const session = (0, session_1.getSession)();
|
|
1396
|
+
const fullPath = path.isAbsolute(dirPath) ? dirPath : path.join(session.workingDirectory, dirPath);
|
|
1397
|
+
if (fs.existsSync(fullPath)) {
|
|
1398
|
+
console.log((0, ui_1.error)(`Already exists: ${dirPath}`));
|
|
1399
|
+
return;
|
|
1400
|
+
}
|
|
1401
|
+
try {
|
|
1402
|
+
fs.mkdirSync(fullPath, { recursive: true });
|
|
1403
|
+
console.log((0, ui_1.success)(`Created directory: ${dirPath}`));
|
|
1404
|
+
}
|
|
1405
|
+
catch (e) {
|
|
1406
|
+
console.log((0, ui_1.error)(`Failed to create: ${e?.message}`));
|
|
1407
|
+
}
|
|
1408
|
+
},
|
|
1409
|
+
// What now - contextual suggestions
|
|
1410
|
+
whatnow: () => {
|
|
1411
|
+
const session = (0, session_1.getSession)();
|
|
1412
|
+
const gitInfo = (0, git_1.getGitInfo)(session.workingDirectory);
|
|
1413
|
+
console.log('');
|
|
1414
|
+
console.log((0, ui_1.info)('What to do next:'));
|
|
1415
|
+
console.log('');
|
|
1416
|
+
// Based on current state
|
|
1417
|
+
if (session.pendingActions || session.lastDiff) {
|
|
1418
|
+
const count = (session.pendingActions?.files?.length || 0) + (session.pendingActions?.diffs?.length || 0);
|
|
1419
|
+
console.log(` You have ${count} pending change(s).`);
|
|
1420
|
+
console.log('');
|
|
1421
|
+
console.log(' /diff Review the changes');
|
|
1422
|
+
console.log(' /diff full See full file contents');
|
|
1423
|
+
console.log(' /apply Apply the changes');
|
|
1424
|
+
console.log(' /apply -f Force apply (skip warnings)');
|
|
1425
|
+
console.log(' /clear Discard and start over');
|
|
1426
|
+
console.log('');
|
|
1427
|
+
}
|
|
1428
|
+
else if (session.lastPlan && session.lastPlan.length > 0) {
|
|
1429
|
+
console.log(` You have a plan with ${session.lastPlan.length} step(s).`);
|
|
1430
|
+
console.log('');
|
|
1431
|
+
console.log(' /generate Create file changes from plan');
|
|
1432
|
+
console.log(' /plan Regenerate the plan');
|
|
1433
|
+
console.log(' /clear Discard and start over');
|
|
1434
|
+
console.log('');
|
|
1435
|
+
}
|
|
1436
|
+
else if (session.currentIntent) {
|
|
1437
|
+
console.log(` Task: "${session.currentIntent.substring(0, 50)}..."`);
|
|
1438
|
+
console.log('');
|
|
1439
|
+
console.log(' /plan Create execution plan');
|
|
1440
|
+
console.log(' /do Plan + generate in one step');
|
|
1441
|
+
console.log(' /run Plan + generate + apply (YOLO)');
|
|
1442
|
+
console.log(' /clear Clear task and start over');
|
|
1443
|
+
console.log('');
|
|
588
1444
|
}
|
|
1445
|
+
else {
|
|
1446
|
+
console.log(' No active task. Here are some ideas:');
|
|
1447
|
+
console.log('');
|
|
1448
|
+
console.log(' Type a task naturally:');
|
|
1449
|
+
console.log(' "add error handling to auth.ts"');
|
|
1450
|
+
console.log(' "create a new React component for user profile"');
|
|
1451
|
+
console.log(' "fix the bug in the login function"');
|
|
1452
|
+
console.log('');
|
|
1453
|
+
console.log(' Or use quick commands:');
|
|
1454
|
+
console.log(' /do <task> Plan + generate');
|
|
1455
|
+
console.log(' /run <task> Full auto execution');
|
|
1456
|
+
console.log(' /ask <q> Ask a question');
|
|
1457
|
+
console.log(' /fix <desc> Debug an issue');
|
|
1458
|
+
console.log('');
|
|
1459
|
+
}
|
|
1460
|
+
// Git suggestions
|
|
1461
|
+
if (gitInfo.isRepo && gitInfo.isDirty) {
|
|
1462
|
+
console.log((0, ui_1.dim)(` Git: ${gitInfo.uncommittedFiles} uncommitted file(s)`));
|
|
1463
|
+
console.log(' /commit Generate AI commit message');
|
|
1464
|
+
console.log(' /git stash Stash changes');
|
|
1465
|
+
console.log('');
|
|
1466
|
+
}
|
|
1467
|
+
// Mode suggestion
|
|
1468
|
+
if (session.mode === 'edit') {
|
|
1469
|
+
console.log((0, ui_1.dim)(' Tip: Use /yolo for autonomous execution'));
|
|
1470
|
+
}
|
|
1471
|
+
},
|
|
1472
|
+
// Alias for whatnow
|
|
1473
|
+
whatsnext: () => {
|
|
1474
|
+
handlers.whatnow({ args: [], rawInput: '/whatnow' });
|
|
589
1475
|
},
|
|
1476
|
+
// Quick examples
|
|
1477
|
+
examples: () => {
|
|
1478
|
+
console.log('');
|
|
1479
|
+
console.log((0, ui_1.info)('Example Tasks:'));
|
|
1480
|
+
console.log('');
|
|
1481
|
+
console.log((0, ui_1.dim)('Code Generation:'));
|
|
1482
|
+
console.log(' "create a REST API endpoint for user registration"');
|
|
1483
|
+
console.log(' "add a new React component for displaying user cards"');
|
|
1484
|
+
console.log(' "implement a binary search function in TypeScript"');
|
|
1485
|
+
console.log('');
|
|
1486
|
+
console.log((0, ui_1.dim)('Bug Fixes:'));
|
|
1487
|
+
console.log(' "fix the null pointer exception in auth.ts line 42"');
|
|
1488
|
+
console.log(' "the login button doesn\'t work, fix it"');
|
|
1489
|
+
console.log(' "users can\'t logout, investigate and fix"');
|
|
1490
|
+
console.log('');
|
|
1491
|
+
console.log((0, ui_1.dim)('Refactoring:'));
|
|
1492
|
+
console.log(' "refactor the database module to use async/await"');
|
|
1493
|
+
console.log(' "split the UserService into smaller modules"');
|
|
1494
|
+
console.log(' "add TypeScript types to the utils folder"');
|
|
1495
|
+
console.log('');
|
|
1496
|
+
console.log((0, ui_1.dim)('Documentation:'));
|
|
1497
|
+
console.log(' "add JSDoc comments to all exported functions"');
|
|
1498
|
+
console.log(' "create a README for the project"');
|
|
1499
|
+
console.log(' "document the API endpoints"');
|
|
1500
|
+
console.log('');
|
|
1501
|
+
console.log((0, ui_1.hint)('Just type naturally - zai·code understands context'));
|
|
1502
|
+
},
|
|
1503
|
+
};
|
|
1504
|
+
// Command aliases for convenience
|
|
1505
|
+
const COMMAND_ALIASES = {
|
|
1506
|
+
// Single letter shortcuts
|
|
1507
|
+
'h': 'help',
|
|
1508
|
+
'?': 'help',
|
|
1509
|
+
'q': 'exit',
|
|
1510
|
+
'quit': 'exit',
|
|
1511
|
+
's': 'status',
|
|
1512
|
+
'p': 'plan',
|
|
1513
|
+
'g': 'generate',
|
|
1514
|
+
'd': 'diff',
|
|
1515
|
+
'a': 'apply',
|
|
1516
|
+
'u': 'undo',
|
|
1517
|
+
'c': 'context',
|
|
1518
|
+
'f': 'files',
|
|
1519
|
+
'm': 'mode',
|
|
1520
|
+
'r': 'run',
|
|
1521
|
+
'x': 'exec',
|
|
1522
|
+
'o': 'open',
|
|
1523
|
+
't': 'tree',
|
|
1524
|
+
'v': 'version',
|
|
1525
|
+
// Word aliases
|
|
1526
|
+
'gen': 'generate',
|
|
1527
|
+
'show': 'diff',
|
|
1528
|
+
'view': 'read',
|
|
1529
|
+
'cat': 'read',
|
|
1530
|
+
'ls': 'tree',
|
|
1531
|
+
'list': 'files',
|
|
1532
|
+
'add': 'open',
|
|
1533
|
+
'rm': 'close',
|
|
1534
|
+
'remove': 'close',
|
|
1535
|
+
'find': 'search',
|
|
1536
|
+
'grep': 'search',
|
|
1537
|
+
'auto': 'yolo',
|
|
1538
|
+
'quick': 'do',
|
|
1539
|
+
'execute': 'run',
|
|
1540
|
+
'rollback': 'undo',
|
|
1541
|
+
'revert': 'undo',
|
|
1542
|
+
'info': 'status',
|
|
1543
|
+
'state': 'status',
|
|
1544
|
+
'check': 'doctor',
|
|
1545
|
+
'health': 'doctor',
|
|
1546
|
+
'cfg': 'settings',
|
|
1547
|
+
'config': 'settings',
|
|
1548
|
+
'prefs': 'settings',
|
|
1549
|
+
'preferences': 'settings',
|
|
590
1550
|
};
|
|
591
1551
|
// Execute a parsed slash command
|
|
592
1552
|
async function executeCommand(parsed) {
|
|
593
1553
|
if (!parsed.isSlashCommand || !parsed.command) {
|
|
594
1554
|
return false;
|
|
595
1555
|
}
|
|
596
|
-
|
|
1556
|
+
// Resolve alias
|
|
1557
|
+
const command = COMMAND_ALIASES[parsed.command] || parsed.command;
|
|
1558
|
+
const handler = handlers[command];
|
|
597
1559
|
if (!handler) {
|
|
1560
|
+
// Check for partial match
|
|
1561
|
+
const matches = Object.keys(handlers).filter(h => h.startsWith(parsed.command));
|
|
1562
|
+
if (matches.length === 1) {
|
|
1563
|
+
await handlers[matches[0]]({ args: parsed.args || [], rawInput: parsed.rawInput });
|
|
1564
|
+
return true;
|
|
1565
|
+
}
|
|
1566
|
+
else if (matches.length > 1) {
|
|
1567
|
+
console.log(`Ambiguous command: /${parsed.command}`);
|
|
1568
|
+
console.log(`Did you mean: ${matches.map(m => '/' + m).join(', ')}?`);
|
|
1569
|
+
return true;
|
|
1570
|
+
}
|
|
598
1571
|
console.log(`Unknown command: /${parsed.command}`);
|
|
1572
|
+
console.log((0, ui_1.dim)('Run /help for available commands'));
|
|
599
1573
|
return true;
|
|
600
1574
|
}
|
|
601
1575
|
await handler({ args: parsed.args || [], rawInput: parsed.rawInput });
|