@zenobius/pi-worktrees 0.4.0-next.13 → 0.4.0-next.16
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/cmds/cmdList.d.ts +1 -1
- package/dist/cmds/cmdPrune.d.ts +2 -1
- package/dist/cmds/shared.d.ts +4 -1
- package/dist/index.js +290 -70
- package/dist/services/config/config.d.ts +12 -0
- package/dist/services/config/schema.d.ts +4 -0
- package/dist/types.d.ts +2 -0
- package/dist/ui/status.d.ts +27 -0
- package/package.json +1 -1
package/dist/cmds/cmdList.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { CmdHandler } from '../types.ts';
|
|
1
|
+
import type { CmdHandler } from '../types.ts';
|
|
2
2
|
export declare const cmdList: CmdHandler;
|
package/dist/cmds/cmdPrune.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
import type { ExtensionCommandContext } from '@mariozechner/pi-coding-agent';
|
|
2
|
-
|
|
2
|
+
import type { CommandDeps } from '../types.ts';
|
|
3
|
+
export declare function cmdPrune(_args: string, ctx: ExtensionCommandContext, deps: CommandDeps): Promise<void>;
|
package/dist/cmds/shared.d.ts
CHANGED
|
@@ -19,8 +19,11 @@ export interface OnCreateHookOptions {
|
|
|
19
19
|
cmdDisplaySuccessColor?: string;
|
|
20
20
|
cmdDisplayErrorColor?: string;
|
|
21
21
|
}
|
|
22
|
+
export declare function sanitizePathPart(value: string): string;
|
|
23
|
+
export declare function resolveLogfilePath(template: string, values: Record<'sessionId' | 'name' | 'timestamp', string>): string;
|
|
22
24
|
/**
|
|
23
|
-
* Runs
|
|
25
|
+
* Runs hook commands sequentially.
|
|
24
26
|
* Stops at first failure and reports the failing command.
|
|
25
27
|
*/
|
|
28
|
+
export declare function runHook(createdCtx: WorktreeCreatedContext, hookValue: WorktreeSettingsConfig['onCreate'] | undefined, hookName: 'onCreate' | 'onSwitch' | 'onBeforeRemove', notify: (msg: string, type: 'info' | 'error' | 'warning') => void, options?: OnCreateHookOptions): Promise<OnCreateResult>;
|
|
26
29
|
export declare function runOnCreateHook(createdCtx: WorktreeCreatedContext, settings: WorktreeSettingsConfig, notify: (msg: string, type: 'info' | 'error' | 'warning') => void, options?: OnCreateHookOptions): Promise<OnCreateResult>;
|
package/dist/index.js
CHANGED
|
@@ -24,11 +24,13 @@ import {
|
|
|
24
24
|
Union,
|
|
25
25
|
Integer as TypeInteger
|
|
26
26
|
} from "typebox";
|
|
27
|
-
var
|
|
27
|
+
var HookCommandsSchema = Union([TypeString(), TypeArray(TypeString())]);
|
|
28
28
|
var WorktreeSettingsSchema = TypeObject({
|
|
29
29
|
worktreeRoot: Optional(TypeString()),
|
|
30
30
|
parentDir: Optional(TypeString()),
|
|
31
|
-
onCreate: Optional(
|
|
31
|
+
onCreate: Optional(HookCommandsSchema),
|
|
32
|
+
onSwitch: Optional(HookCommandsSchema),
|
|
33
|
+
onBeforeRemove: Optional(HookCommandsSchema)
|
|
32
34
|
}, {
|
|
33
35
|
$id: "WorktreeSettingsConfig",
|
|
34
36
|
additionalProperties: false
|
|
@@ -73,7 +75,7 @@ function globToRegExp(pattern) {
|
|
|
73
75
|
const doubleStarReplaced = escaped.replace(/\*\*/g, "::DOUBLE_STAR::");
|
|
74
76
|
const singleStarReplaced = doubleStarReplaced.replace(/\*/g, "[^/]*");
|
|
75
77
|
const regexBody = singleStarReplaced.replace(/::DOUBLE_STAR::/g, ".*");
|
|
76
|
-
return new RegExp(
|
|
78
|
+
return new RegExp(regexBody, "i");
|
|
77
79
|
}
|
|
78
80
|
function globMatch(input, pattern) {
|
|
79
81
|
return globToRegExp(pattern).test(input);
|
|
@@ -340,11 +342,17 @@ async function cmdCd(args, ctx, deps) {
|
|
|
340
342
|
}
|
|
341
343
|
|
|
342
344
|
// src/cmds/cmdCreate.ts
|
|
343
|
-
import { join as join3 } from "path";
|
|
345
|
+
import { basename as basename3, join as join3 } from "path";
|
|
344
346
|
|
|
345
347
|
// src/cmds/shared.ts
|
|
346
348
|
import { appendFileSync as appendFileSync2, writeFileSync } from "fs";
|
|
347
349
|
import { spawn } from "child_process";
|
|
350
|
+
function sanitizePathPart(value) {
|
|
351
|
+
return value.replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
352
|
+
}
|
|
353
|
+
function resolveLogfilePath(template, values) {
|
|
354
|
+
return template.replace(/\{\{sessionId\}\}|\{sessionId\}/g, values.sessionId).replace(/\{\{name\}\}|\{name\}/g, values.name).replace(/\{\{timestamp\}\}|\{timestamp\}/g, values.timestamp);
|
|
355
|
+
}
|
|
348
356
|
var ANSI = {
|
|
349
357
|
reset: "\x1B[0m",
|
|
350
358
|
gray: "\x1B[90m",
|
|
@@ -411,8 +419,8 @@ function getDisplayLines(text, maxLines) {
|
|
|
411
419
|
}
|
|
412
420
|
return lines.slice(-maxLines);
|
|
413
421
|
}
|
|
414
|
-
function formatCommandList(commands, states, outputs, commandDisplay, logPath, displayOutputMaxLines = 5) {
|
|
415
|
-
const lines = [
|
|
422
|
+
function formatCommandList(commands, states, outputs, commandDisplay, hookName, logPath, displayOutputMaxLines = 5) {
|
|
423
|
+
const lines = [`${hookName} steps:`];
|
|
416
424
|
for (const [index, command] of commands.entries()) {
|
|
417
425
|
const state = states[index];
|
|
418
426
|
lines.push(formatCommandLine(command, state, commandDisplay));
|
|
@@ -483,18 +491,18 @@ function runCommand(command, cwd, onOutput) {
|
|
|
483
491
|
});
|
|
484
492
|
});
|
|
485
493
|
}
|
|
486
|
-
async function
|
|
487
|
-
if (!
|
|
494
|
+
async function runHook(createdCtx, hookValue, hookName, notify, options) {
|
|
495
|
+
if (!hookValue) {
|
|
488
496
|
return { success: true, executed: [] };
|
|
489
497
|
}
|
|
490
|
-
const commandTemplates = Array.isArray(
|
|
498
|
+
const commandTemplates = Array.isArray(hookValue) ? hookValue : [hookValue];
|
|
491
499
|
const commands = commandTemplates.map((template) => expandTemplate(template, createdCtx));
|
|
492
500
|
const executed = [];
|
|
493
501
|
const commandStates = commands.map(() => "pending");
|
|
494
502
|
const commandOutputs = commands.map(() => ({ stdout: "", stderr: "" }));
|
|
495
503
|
if (options?.logPath) {
|
|
496
504
|
writeFileSync(options.logPath, [
|
|
497
|
-
`# pi-worktree
|
|
505
|
+
`# pi-worktree ${hookName} log`,
|
|
498
506
|
`# worktree: ${createdCtx.path}`,
|
|
499
507
|
`# branch: ${createdCtx.branch}`,
|
|
500
508
|
""
|
|
@@ -510,13 +518,13 @@ async function runOnCreateHook(createdCtx, settings, notify, options) {
|
|
|
510
518
|
successColor: options?.cmdDisplaySuccessColor ?? "success",
|
|
511
519
|
errorColor: options?.cmdDisplayErrorColor ?? "error"
|
|
512
520
|
};
|
|
513
|
-
notify(formatCommandList(commands, commandStates, commandOutputs, commandDisplay, undefined, displayOutputMaxLines), "info");
|
|
521
|
+
notify(formatCommandList(commands, commandStates, commandOutputs, commandDisplay, hookName, undefined, displayOutputMaxLines), "info");
|
|
514
522
|
for (const [index, command] of commands.entries()) {
|
|
515
523
|
commandStates[index] = "running";
|
|
516
|
-
notify(formatCommandList(commands, commandStates, commandOutputs, commandDisplay, undefined, displayOutputMaxLines), "info");
|
|
524
|
+
notify(formatCommandList(commands, commandStates, commandOutputs, commandDisplay, hookName, undefined, displayOutputMaxLines), "info");
|
|
517
525
|
const result = await runCommand(command, createdCtx.path, (stream, chunk) => {
|
|
518
526
|
commandOutputs[index][stream] += chunk;
|
|
519
|
-
notify(formatCommandList(commands, commandStates, commandOutputs, commandDisplay, undefined, displayOutputMaxLines), "info");
|
|
527
|
+
notify(formatCommandList(commands, commandStates, commandOutputs, commandDisplay, hookName, undefined, displayOutputMaxLines), "info");
|
|
520
528
|
});
|
|
521
529
|
if (options?.logPath) {
|
|
522
530
|
appendCommandLog(options.logPath, command, result);
|
|
@@ -524,8 +532,8 @@ async function runOnCreateHook(createdCtx, settings, notify, options) {
|
|
|
524
532
|
executed.push(command);
|
|
525
533
|
if (!result.success) {
|
|
526
534
|
commandStates[index] = "failed";
|
|
527
|
-
notify(formatCommandList(commands, commandStates, commandOutputs, commandDisplay, options?.logPath, displayOutputMaxLines), "error");
|
|
528
|
-
notify(
|
|
535
|
+
notify(formatCommandList(commands, commandStates, commandOutputs, commandDisplay, hookName, options?.logPath, displayOutputMaxLines), "error");
|
|
536
|
+
notify(`${hookName} failed (exit ${result.code}): ${result.stderr.slice(0, 200)}${options?.logPath ? `
|
|
529
537
|
log: ${options.logPath}` : ""}`, "error");
|
|
530
538
|
return {
|
|
531
539
|
success: false,
|
|
@@ -538,11 +546,14 @@ log: ${options.logPath}` : ""}`, "error");
|
|
|
538
546
|
};
|
|
539
547
|
}
|
|
540
548
|
commandStates[index] = "success";
|
|
541
|
-
notify(formatCommandList(commands, commandStates, commandOutputs, commandDisplay, undefined, displayOutputMaxLines), "info");
|
|
549
|
+
notify(formatCommandList(commands, commandStates, commandOutputs, commandDisplay, hookName, undefined, displayOutputMaxLines), "info");
|
|
542
550
|
}
|
|
543
|
-
notify(formatCommandList(commands, commandStates, commandOutputs, commandDisplay, options?.logPath, displayOutputMaxLines), "info");
|
|
551
|
+
notify(formatCommandList(commands, commandStates, commandOutputs, commandDisplay, hookName, options?.logPath, displayOutputMaxLines), "info");
|
|
544
552
|
return { success: true, executed };
|
|
545
553
|
}
|
|
554
|
+
async function runOnCreateHook(createdCtx, settings, notify, options) {
|
|
555
|
+
return runHook(createdCtx, settings.onCreate, "onCreate", notify, options);
|
|
556
|
+
}
|
|
546
557
|
|
|
547
558
|
// src/services/config/config.ts
|
|
548
559
|
import { createConfigService } from "@zenobius/pi-extension-config";
|
|
@@ -950,12 +961,6 @@ var DefaultWorktreeSettings = {
|
|
|
950
961
|
var DefaultLogfileTemplate = DEFAULT_LOGFILE_TEMPLATE;
|
|
951
962
|
|
|
952
963
|
// src/cmds/cmdCreate.ts
|
|
953
|
-
function sanitizePathPart(value) {
|
|
954
|
-
return value.replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
955
|
-
}
|
|
956
|
-
function resolveLogfilePath(template, values) {
|
|
957
|
-
return template.replace(/\{\{sessionId\}\}|\{sessionId\}/g, values.sessionId).replace(/\{\{name\}\}|\{name\}/g, values.name).replace(/\{\{timestamp\}\}|\{timestamp\}/g, values.timestamp);
|
|
958
|
-
}
|
|
959
964
|
async function cmdCreate(args, ctx, deps) {
|
|
960
965
|
const featureName = args.trim();
|
|
961
966
|
if (!featureName) {
|
|
@@ -969,9 +974,53 @@ async function cmdCreate(args, ctx, deps) {
|
|
|
969
974
|
const current = deps.configService.current(ctx);
|
|
970
975
|
const worktreePath = join3(current.parentDir, featureName);
|
|
971
976
|
const branchName = `feature/${featureName}`;
|
|
972
|
-
const
|
|
973
|
-
if (
|
|
974
|
-
ctx.
|
|
977
|
+
const existingWorktree = listWorktrees(ctx.cwd).find((worktree) => worktree.path === worktreePath || basename3(worktree.path) === featureName || worktree.branch === branchName);
|
|
978
|
+
if (existingWorktree) {
|
|
979
|
+
if (!ctx.hasUI) {
|
|
980
|
+
ctx.ui.notify(`Worktree already exists at: ${worktreePath}`, "error");
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
983
|
+
const confirmMessage = current.onSwitch ? `Path: ${existingWorktree.path}
|
|
984
|
+
Branch: ${existingWorktree.branch}
|
|
985
|
+
|
|
986
|
+
Switch to this worktree and run onSwitch?` : `Path: ${existingWorktree.path}
|
|
987
|
+
Branch: ${existingWorktree.branch}
|
|
988
|
+
|
|
989
|
+
Switch to this worktree?`;
|
|
990
|
+
const shouldSwitch = await ctx.ui.confirm("Worktree already exists", confirmMessage);
|
|
991
|
+
if (!shouldSwitch) {
|
|
992
|
+
ctx.ui.notify("Cancelled", "info");
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
995
|
+
const existingCtx = {
|
|
996
|
+
path: existingWorktree.path,
|
|
997
|
+
name: basename3(existingWorktree.path),
|
|
998
|
+
branch: existingWorktree.branch,
|
|
999
|
+
...current
|
|
1000
|
+
};
|
|
1001
|
+
const sessionId2 = sanitizePathPart(ctx.sessionManager?.getSessionId?.() || "session");
|
|
1002
|
+
const safeName2 = sanitizePathPart(existingCtx.name);
|
|
1003
|
+
const timestamp2 = new Date().toISOString().replace(/[:.]/g, "-");
|
|
1004
|
+
const logPath2 = resolveLogfilePath(current.logfile ?? DefaultLogfileTemplate, {
|
|
1005
|
+
sessionId: sessionId2,
|
|
1006
|
+
name: safeName2,
|
|
1007
|
+
timestamp: timestamp2
|
|
1008
|
+
});
|
|
1009
|
+
const result = await runHook(existingCtx, current.onSwitch, "onSwitch", ctx.ui.notify.bind(ctx.ui), {
|
|
1010
|
+
logPath: logPath2,
|
|
1011
|
+
displayOutputMaxLines: current.onCreateDisplayOutputMaxLines,
|
|
1012
|
+
cmdDisplayPending: current.onCreateCmdDisplayPending,
|
|
1013
|
+
cmdDisplaySuccess: current.onCreateCmdDisplaySuccess,
|
|
1014
|
+
cmdDisplayError: current.onCreateCmdDisplayError,
|
|
1015
|
+
cmdDisplayPendingColor: current.onCreateCmdDisplayPendingColor,
|
|
1016
|
+
cmdDisplaySuccessColor: current.onCreateCmdDisplaySuccessColor,
|
|
1017
|
+
cmdDisplayErrorColor: current.onCreateCmdDisplayErrorColor
|
|
1018
|
+
});
|
|
1019
|
+
if (!result.success) {
|
|
1020
|
+
ctx.ui.notify("onSwitch failed", "error");
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
ctx.ui.notify(`Worktree path: ${existingWorktree.path}`, "info");
|
|
975
1024
|
return;
|
|
976
1025
|
}
|
|
977
1026
|
try {
|
|
@@ -980,10 +1029,14 @@ async function cmdCreate(args, ctx, deps) {
|
|
|
980
1029
|
return;
|
|
981
1030
|
} catch {}
|
|
982
1031
|
ensureExcluded(ctx.cwd, current.parentDir);
|
|
983
|
-
|
|
1032
|
+
const stopBusy = deps.statusService.busy(ctx, `Creating worktree: ${featureName}...`);
|
|
984
1033
|
try {
|
|
985
1034
|
git(["worktree", "add", "-b", branchName, worktreePath], current.mainWorktree);
|
|
1035
|
+
stopBusy();
|
|
1036
|
+
deps.statusService.positive(ctx, `Created: ${featureName}`);
|
|
986
1037
|
} catch (err) {
|
|
1038
|
+
stopBusy();
|
|
1039
|
+
deps.statusService.critical(ctx, `Failed to create worktree`);
|
|
987
1040
|
ctx.ui.notify(`Failed to create worktree: ${err.message}`, "error");
|
|
988
1041
|
return;
|
|
989
1042
|
}
|
|
@@ -1100,6 +1153,12 @@ ${finalConfig}`, "info");
|
|
|
1100
1153
|
}
|
|
1101
1154
|
|
|
1102
1155
|
// src/cmds/cmdList.ts
|
|
1156
|
+
import { basename as basename4 } from "path";
|
|
1157
|
+
function formatWorktreeOption(worktree) {
|
|
1158
|
+
const markers = [worktree.isMain ? "[main]" : "", worktree.isCurrent ? "[current]" : ""].filter(Boolean).join(" ");
|
|
1159
|
+
return `${worktree.branch}${markers ? " " + markers : ""}
|
|
1160
|
+
${worktree.path}`;
|
|
1161
|
+
}
|
|
1103
1162
|
var cmdList = async (_args, ctx, deps) => {
|
|
1104
1163
|
if (!isGitRepo(ctx.cwd)) {
|
|
1105
1164
|
ctx.ui.notify("Not in a git repository", "error");
|
|
@@ -1110,17 +1169,18 @@ var cmdList = async (_args, ctx, deps) => {
|
|
|
1110
1169
|
ctx.ui.notify("No worktrees found", "info");
|
|
1111
1170
|
return;
|
|
1112
1171
|
}
|
|
1113
|
-
|
|
1114
|
-
const
|
|
1115
|
-
|
|
1172
|
+
if (!ctx.hasUI) {
|
|
1173
|
+
const lines = worktrees.map((worktree) => {
|
|
1174
|
+
const markers = [worktree.isMain ? "[main]" : "", worktree.isCurrent ? "[current]" : ""].filter(Boolean).join(" ");
|
|
1175
|
+
return `${worktree.branch}${markers ? " " + markers : ""}
|
|
1116
1176
|
${worktree.path}`;
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1177
|
+
});
|
|
1178
|
+
const configured = Array.from(deps.configService.worktrees.entries()).map(([pattern, settings]) => {
|
|
1179
|
+
return `${pattern}
|
|
1120
1180
|
${settings.worktreeRoot ?? settings.parentDir}
|
|
1121
1181
|
${settings.onCreate}`;
|
|
1122
|
-
|
|
1123
|
-
|
|
1182
|
+
});
|
|
1183
|
+
ctx.ui.notify(`Worktrees:
|
|
1124
1184
|
|
|
1125
1185
|
${lines.join(`
|
|
1126
1186
|
|
|
@@ -1131,10 +1191,69 @@ Configured:
|
|
|
1131
1191
|
${configured.join(`
|
|
1132
1192
|
|
|
1133
1193
|
`)}`, "info");
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
const options = worktrees.map(formatWorktreeOption);
|
|
1197
|
+
const byOption = new Map(options.map((option, index) => [option, worktrees[index]]));
|
|
1198
|
+
const selected = await ctx.ui.select("Select worktree to switch to", options);
|
|
1199
|
+
if (selected === undefined) {
|
|
1200
|
+
ctx.ui.notify("Cancelled", "info");
|
|
1201
|
+
return;
|
|
1202
|
+
}
|
|
1203
|
+
const target = byOption.get(selected);
|
|
1204
|
+
if (!target) {
|
|
1205
|
+
ctx.ui.notify("Invalid selection", "error");
|
|
1206
|
+
return;
|
|
1207
|
+
}
|
|
1208
|
+
const current = deps.configService.current({ cwd: target.path });
|
|
1209
|
+
if (!current.onSwitch) {
|
|
1210
|
+
ctx.ui.notify(`No onSwitch configured for: ${target.path}`, "info");
|
|
1211
|
+
return;
|
|
1212
|
+
}
|
|
1213
|
+
const sessionId = sanitizePathPart(ctx.sessionManager?.getSessionId?.() || "session");
|
|
1214
|
+
const safeName = sanitizePathPart(basename4(target.path));
|
|
1215
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
1216
|
+
const logPath = resolveLogfilePath(current.logfile ?? DefaultLogfileTemplate, {
|
|
1217
|
+
sessionId,
|
|
1218
|
+
name: safeName,
|
|
1219
|
+
timestamp
|
|
1220
|
+
});
|
|
1221
|
+
const createdCtx = {
|
|
1222
|
+
path: target.path,
|
|
1223
|
+
name: basename4(target.path),
|
|
1224
|
+
branch: target.branch,
|
|
1225
|
+
project: current.project,
|
|
1226
|
+
mainWorktree: current.mainWorktree
|
|
1227
|
+
};
|
|
1228
|
+
const stopBusy = deps.statusService.busy(ctx, `Running onSwitch for ${target.branch}...`);
|
|
1229
|
+
try {
|
|
1230
|
+
const result = await runHook(createdCtx, current.onSwitch, "onSwitch", ctx.ui.notify.bind(ctx.ui), {
|
|
1231
|
+
logPath,
|
|
1232
|
+
displayOutputMaxLines: current.onCreateDisplayOutputMaxLines,
|
|
1233
|
+
cmdDisplayPending: current.onCreateCmdDisplayPending,
|
|
1234
|
+
cmdDisplaySuccess: current.onCreateCmdDisplaySuccess,
|
|
1235
|
+
cmdDisplayError: current.onCreateCmdDisplayError,
|
|
1236
|
+
cmdDisplayPendingColor: current.onCreateCmdDisplayPendingColor,
|
|
1237
|
+
cmdDisplaySuccessColor: current.onCreateCmdDisplaySuccessColor,
|
|
1238
|
+
cmdDisplayErrorColor: current.onCreateCmdDisplayErrorColor
|
|
1239
|
+
});
|
|
1240
|
+
if (!result.success) {
|
|
1241
|
+
stopBusy();
|
|
1242
|
+
deps.statusService.critical(ctx, `onSwitch failed`);
|
|
1243
|
+
ctx.ui.notify(`onSwitch failed`, "error");
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
1246
|
+
stopBusy();
|
|
1247
|
+
deps.statusService.positive(ctx, `onSwitch complete: ${target.branch}`);
|
|
1248
|
+
} catch (err) {
|
|
1249
|
+
stopBusy();
|
|
1250
|
+
deps.statusService.critical(ctx, `onSwitch failed`);
|
|
1251
|
+
ctx.ui.notify(`onSwitch failed: ${err.message}`, "error");
|
|
1252
|
+
}
|
|
1134
1253
|
};
|
|
1135
1254
|
|
|
1136
1255
|
// src/cmds/cmdPrune.ts
|
|
1137
|
-
async function cmdPrune(_args, ctx) {
|
|
1256
|
+
async function cmdPrune(_args, ctx, deps) {
|
|
1138
1257
|
if (!isGitRepo(ctx.cwd)) {
|
|
1139
1258
|
ctx.ui.notify("Not in a git repository", "error");
|
|
1140
1259
|
return;
|
|
@@ -1157,18 +1276,23 @@ ${dryRun}`);
|
|
|
1157
1276
|
ctx.ui.notify("Cancelled", "info");
|
|
1158
1277
|
return;
|
|
1159
1278
|
}
|
|
1279
|
+
const stopBusy = deps.statusService.busy(ctx, "Pruning stale worktrees...");
|
|
1160
1280
|
try {
|
|
1161
1281
|
git(["worktree", "prune"], ctx.cwd);
|
|
1282
|
+
stopBusy();
|
|
1283
|
+
deps.statusService.positive(ctx, "Pruned stale references");
|
|
1162
1284
|
ctx.ui.notify("\u2713 Stale worktree references pruned", "info");
|
|
1163
1285
|
} catch (err) {
|
|
1286
|
+
stopBusy();
|
|
1287
|
+
deps.statusService.critical(ctx, "Failed to prune");
|
|
1164
1288
|
ctx.ui.notify(`Failed to prune: ${err.message}`, "error");
|
|
1165
1289
|
}
|
|
1166
1290
|
}
|
|
1167
1291
|
|
|
1168
1292
|
// src/cmds/cmdRemove.ts
|
|
1169
|
-
import { basename as
|
|
1293
|
+
import { basename as basename5, join as join4 } from "path";
|
|
1170
1294
|
function findTarget(worktrees, worktreeName, parentDir) {
|
|
1171
|
-
return worktrees.find((worktree) =>
|
|
1295
|
+
return worktrees.find((worktree) => basename5(worktree.path) === worktreeName || worktree.path === worktreeName || worktree.path === join4(parentDir, worktreeName));
|
|
1172
1296
|
}
|
|
1173
1297
|
function isProtectedWorktree(worktree) {
|
|
1174
1298
|
return worktree.isMain || worktree.isCurrent;
|
|
@@ -1179,7 +1303,7 @@ async function pickWorktreeInteractively(ctx, worktrees) {
|
|
|
1179
1303
|
ctx.ui.notify("No removable worktrees found", "info");
|
|
1180
1304
|
return;
|
|
1181
1305
|
}
|
|
1182
|
-
const options = candidates.map((worktree) => `${
|
|
1306
|
+
const options = candidates.map((worktree) => `${basename5(worktree.path)} (${worktree.branch})
|
|
1183
1307
|
${worktree.path}`);
|
|
1184
1308
|
const byOption = new Map(options.map((option, index) => [option, candidates[index]]));
|
|
1185
1309
|
const selected = await ctx.ui.select("Select worktree to remove", options);
|
|
@@ -1189,7 +1313,7 @@ async function pickWorktreeInteractively(ctx, worktrees) {
|
|
|
1189
1313
|
}
|
|
1190
1314
|
return byOption.get(selected);
|
|
1191
1315
|
}
|
|
1192
|
-
async function removeWorktreeWithConfirm(ctx, cwd, target) {
|
|
1316
|
+
async function removeWorktreeWithConfirm(ctx, cwd, target, status, runBeforeRemove) {
|
|
1193
1317
|
const confirmed = await ctx.ui.confirm("Remove worktree?", `This will remove:
|
|
1194
1318
|
Path: ${target.path}
|
|
1195
1319
|
Branch: ${target.branch}
|
|
@@ -1199,19 +1323,34 @@ The branch will NOT be deleted.`);
|
|
|
1199
1323
|
ctx.ui.notify("Cancelled", "info");
|
|
1200
1324
|
return;
|
|
1201
1325
|
}
|
|
1326
|
+
if (runBeforeRemove) {
|
|
1327
|
+
const canContinue = await runBeforeRemove();
|
|
1328
|
+
if (!canContinue) {
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
const stopBusy = status.busy(ctx, "Removing worktree...");
|
|
1202
1333
|
try {
|
|
1203
1334
|
git(["worktree", "remove", target.path], cwd);
|
|
1335
|
+
stopBusy();
|
|
1336
|
+
status.positive(ctx, `Removed: ${target.path}`);
|
|
1204
1337
|
ctx.ui.notify(`\u2713 Worktree removed: ${target.path}`, "info");
|
|
1205
1338
|
} catch {
|
|
1339
|
+
stopBusy();
|
|
1206
1340
|
const forceConfirmed = await ctx.ui.confirm("Force remove?", "Worktree has uncommitted changes. Force remove anyway?");
|
|
1207
1341
|
if (!forceConfirmed) {
|
|
1208
1342
|
ctx.ui.notify("Cancelled", "info");
|
|
1209
1343
|
return;
|
|
1210
1344
|
}
|
|
1345
|
+
const stopForceBusy = status.busy(ctx, "Force removing worktree...");
|
|
1211
1346
|
try {
|
|
1212
1347
|
git(["worktree", "remove", "--force", target.path], cwd);
|
|
1348
|
+
stopForceBusy();
|
|
1349
|
+
status.positive(ctx, `Force removed: ${target.path}`);
|
|
1213
1350
|
ctx.ui.notify(`\u2713 Worktree force removed: ${target.path}`, "info");
|
|
1214
1351
|
} catch (forceErr) {
|
|
1352
|
+
stopForceBusy();
|
|
1353
|
+
status.critical(ctx, `Failed to remove`);
|
|
1215
1354
|
ctx.ui.notify(`Failed to remove: ${forceErr.message}`, "error");
|
|
1216
1355
|
}
|
|
1217
1356
|
}
|
|
@@ -1249,7 +1388,38 @@ async function cmdRemove(args, ctx, deps) {
|
|
|
1249
1388
|
return;
|
|
1250
1389
|
}
|
|
1251
1390
|
}
|
|
1252
|
-
|
|
1391
|
+
const current = deps.configService.current({ cwd: target.path });
|
|
1392
|
+
await removeWorktreeWithConfirm(ctx, ctx.cwd, target, deps.statusService, async () => {
|
|
1393
|
+
if (!current.onBeforeRemove) {
|
|
1394
|
+
return true;
|
|
1395
|
+
}
|
|
1396
|
+
const hookCtx = {
|
|
1397
|
+
path: target.path,
|
|
1398
|
+
name: basename5(target.path),
|
|
1399
|
+
branch: target.branch,
|
|
1400
|
+
project: current.project,
|
|
1401
|
+
mainWorktree: current.mainWorktree
|
|
1402
|
+
};
|
|
1403
|
+
const sessionId = sanitizePathPart(ctx.sessionManager?.getSessionId?.() || "session");
|
|
1404
|
+
const safeName = sanitizePathPart(hookCtx.name);
|
|
1405
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
1406
|
+
const logPath = resolveLogfilePath(current.logfile ?? DefaultLogfileTemplate, {
|
|
1407
|
+
sessionId,
|
|
1408
|
+
name: safeName,
|
|
1409
|
+
timestamp
|
|
1410
|
+
});
|
|
1411
|
+
const result = await runHook(hookCtx, current.onBeforeRemove, "onBeforeRemove", ctx.ui.notify.bind(ctx.ui), {
|
|
1412
|
+
logPath,
|
|
1413
|
+
displayOutputMaxLines: current.onCreateDisplayOutputMaxLines,
|
|
1414
|
+
cmdDisplayPending: current.onCreateCmdDisplayPending,
|
|
1415
|
+
cmdDisplaySuccess: current.onCreateCmdDisplaySuccess,
|
|
1416
|
+
cmdDisplayError: current.onCreateCmdDisplayError,
|
|
1417
|
+
cmdDisplayPendingColor: current.onCreateCmdDisplayPendingColor,
|
|
1418
|
+
cmdDisplaySuccessColor: current.onCreateCmdDisplaySuccessColor,
|
|
1419
|
+
cmdDisplayErrorColor: current.onCreateCmdDisplayErrorColor
|
|
1420
|
+
});
|
|
1421
|
+
return result.success;
|
|
1422
|
+
});
|
|
1253
1423
|
}
|
|
1254
1424
|
|
|
1255
1425
|
// src/cmds/cmdSettings.ts
|
|
@@ -1444,6 +1614,74 @@ function createCompletionFactory(commands) {
|
|
|
1444
1614
|
};
|
|
1445
1615
|
}
|
|
1446
1616
|
|
|
1617
|
+
// src/ui/status.ts
|
|
1618
|
+
class StatusIndicator {
|
|
1619
|
+
statusKey;
|
|
1620
|
+
busyStyle;
|
|
1621
|
+
busyFrames;
|
|
1622
|
+
progressStyle = "bars";
|
|
1623
|
+
progressFrames;
|
|
1624
|
+
constructor(statusKey, options = {
|
|
1625
|
+
busy: "dots",
|
|
1626
|
+
progress: "bars"
|
|
1627
|
+
}) {
|
|
1628
|
+
this.statusKey = statusKey;
|
|
1629
|
+
this.busyStyle = options.busy || "dots";
|
|
1630
|
+
this.busyFrames = StatusIndicator.busyStyles[this.busyStyle];
|
|
1631
|
+
this.progressStyle = options.progress || "bars";
|
|
1632
|
+
this.progressFrames = StatusIndicator.progressStyles[this.progressStyle];
|
|
1633
|
+
}
|
|
1634
|
+
busy(ctx, message) {
|
|
1635
|
+
if (typeof ctx.ui.setStatus !== "function") {
|
|
1636
|
+
return () => {};
|
|
1637
|
+
}
|
|
1638
|
+
let i = 0;
|
|
1639
|
+
ctx.ui.setStatus(this.statusKey, `${this.busyFrames[i]} ${message}`);
|
|
1640
|
+
const timer = globalThis.setInterval(() => {
|
|
1641
|
+
i = (i + 1) % this.busyFrames.length;
|
|
1642
|
+
ctx.ui.setStatus?.(this.statusKey, `${this.busyFrames[i]} ${message}`);
|
|
1643
|
+
}, 100);
|
|
1644
|
+
return () => {
|
|
1645
|
+
globalThis.clearInterval(timer);
|
|
1646
|
+
ctx.ui.setStatus?.(this.statusKey, undefined);
|
|
1647
|
+
};
|
|
1648
|
+
}
|
|
1649
|
+
cautious(ctx, message) {
|
|
1650
|
+
ctx.ui.setStatus?.(this.statusKey, `\u26A0\uFE0F ${message}`);
|
|
1651
|
+
}
|
|
1652
|
+
critical(ctx, message) {
|
|
1653
|
+
ctx.ui.setStatus?.(this.statusKey, `\u274C ${message}`);
|
|
1654
|
+
}
|
|
1655
|
+
positive(ctx, message) {
|
|
1656
|
+
ctx.ui.setStatus?.(this.statusKey, `\u2705 ${message}`);
|
|
1657
|
+
}
|
|
1658
|
+
informative(ctx, message) {
|
|
1659
|
+
ctx.ui.setStatus?.(this.statusKey, `\u2139\uFE0F ${message}`);
|
|
1660
|
+
}
|
|
1661
|
+
progress(ctx, message, percent) {
|
|
1662
|
+
const progressBar = this.progressFrames(percent);
|
|
1663
|
+
ctx.ui.setStatus?.(this.statusKey, `${progressBar} ${message}`);
|
|
1664
|
+
}
|
|
1665
|
+
static busyStyles = {
|
|
1666
|
+
dots: ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"]
|
|
1667
|
+
};
|
|
1668
|
+
static progressStyles = {
|
|
1669
|
+
bars: (percent) => {
|
|
1670
|
+
const clampedPercent = Math.max(0, Math.min(100, percent));
|
|
1671
|
+
const progressBarLength = 20;
|
|
1672
|
+
const filledLength = Math.round(clampedPercent / 100 * progressBarLength);
|
|
1673
|
+
const emptyLength = progressBarLength - filledLength;
|
|
1674
|
+
return "\u2588".repeat(filledLength) + "\u2591".repeat(emptyLength);
|
|
1675
|
+
},
|
|
1676
|
+
pie: (percent) => {
|
|
1677
|
+
const clampedPercent = Math.max(0, Math.min(100, percent));
|
|
1678
|
+
const pieFrames = ["\u25CB", "\u25D4", "\u25D1", "\u25D5", "\u25CF"];
|
|
1679
|
+
const frameIndex = Math.floor(clampedPercent / 100 * (pieFrames.length - 1));
|
|
1680
|
+
return pieFrames[frameIndex];
|
|
1681
|
+
}
|
|
1682
|
+
};
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1447
1685
|
// src/index.ts
|
|
1448
1686
|
var HELP_TEXT = `
|
|
1449
1687
|
/worktree - Git worktree management
|
|
@@ -1452,19 +1690,21 @@ Commands:
|
|
|
1452
1690
|
/worktree init Configure worktree settings interactively
|
|
1453
1691
|
/worktree settings [key] [val] Get/set individual settings
|
|
1454
1692
|
/worktree create <feature-name> Create new worktree with branch
|
|
1455
|
-
/worktree list List
|
|
1456
|
-
/worktree remove <name> Remove a worktree
|
|
1693
|
+
/worktree list List worktrees and run onSwitch for a selection
|
|
1694
|
+
/worktree remove <name> Remove a worktree (runs onBeforeRemove if set)
|
|
1457
1695
|
/worktree status Show current worktree info
|
|
1458
1696
|
/worktree cd <name> Print path to worktree
|
|
1459
1697
|
/worktree prune Clean up stale references
|
|
1460
1698
|
/worktree templates Show template variables preview
|
|
1461
1699
|
|
|
1462
|
-
Configuration (~/.pi/agent/pi-worktrees
|
|
1700
|
+
Configuration (~/.pi/agent/pi-worktrees.config.json):
|
|
1463
1701
|
{
|
|
1464
1702
|
"worktrees": {
|
|
1465
1703
|
"github.com/org/repo": {
|
|
1466
1704
|
"worktreeRoot": "~/work/org",
|
|
1467
|
-
"onCreate": ["mise install", "bun install"]
|
|
1705
|
+
"onCreate": ["mise install", "bun install"],
|
|
1706
|
+
"onSwitch": "mise run dev:resume",
|
|
1707
|
+
"onBeforeRemove": "bun test"
|
|
1468
1708
|
},
|
|
1469
1709
|
"github.com/org/*": {
|
|
1470
1710
|
"worktreeRoot": "~/work/org-other",
|
|
@@ -1490,7 +1730,8 @@ Pattern matching: exact URL > most-specific glob > fallback (worktree)
|
|
|
1490
1730
|
Matching strategies: fail-on-tie | first-wins | last-wins
|
|
1491
1731
|
|
|
1492
1732
|
Config note: parentDir is deprecated and supported as an alias for worktreeRoot.
|
|
1493
|
-
|
|
1733
|
+
Hook vars: {{path}}, {{name}}, {{branch}}, {{project}}, {{mainWorktree}}
|
|
1734
|
+
Hooks: onCreate (new), onSwitch (existing), onBeforeRemove (pre-delete, non-zero blocks)
|
|
1494
1735
|
Logfile vars: {sessionId} / {{sessionId}}, {name} / {{name}}, {timestamp} / {{timestamp}}
|
|
1495
1736
|
`.trim();
|
|
1496
1737
|
var commands = {
|
|
@@ -1511,29 +1752,7 @@ var commands = {
|
|
|
1511
1752
|
};
|
|
1512
1753
|
var PiWorktreeExtension = async function(pi) {
|
|
1513
1754
|
const configService = await createPiWorktreeConfigService();
|
|
1514
|
-
const
|
|
1515
|
-
configService.events.on("MigrationFailed", () => {
|
|
1516
|
-
queue.push({ type: "error", msg: "MigrationFailed" });
|
|
1517
|
-
});
|
|
1518
|
-
configService.events.on("MigrationApplied", () => {
|
|
1519
|
-
queue.push({ type: "info", msg: "MigrationApplied" });
|
|
1520
|
-
});
|
|
1521
|
-
configService.events.on("ConfigLoading", () => {
|
|
1522
|
-
queue.push({ type: "info", msg: "ConfigLoading" });
|
|
1523
|
-
});
|
|
1524
|
-
configService.events.on("ConfigLoaded", () => {
|
|
1525
|
-
queue.push({ type: "info", msg: "ConfigLoaded" });
|
|
1526
|
-
});
|
|
1527
|
-
pi.on("session_start", async (event, ctx) => {
|
|
1528
|
-
await configService.ready;
|
|
1529
|
-
while (queue.length > 0) {
|
|
1530
|
-
const notification = queue.shift();
|
|
1531
|
-
if (!notification) {
|
|
1532
|
-
return;
|
|
1533
|
-
}
|
|
1534
|
-
ctx.ui.setStatus(`Worktrees`, notification.msg);
|
|
1535
|
-
}
|
|
1536
|
-
});
|
|
1755
|
+
const statusService = new StatusIndicator("pi-worktree");
|
|
1537
1756
|
const getSubcommandCompletions = createCompletionFactory(commands);
|
|
1538
1757
|
pi.registerCommand("worktree", {
|
|
1539
1758
|
description: "Git worktree management for isolated workspaces",
|
|
@@ -1552,7 +1771,8 @@ var PiWorktreeExtension = async function(pi) {
|
|
|
1552
1771
|
const settings = configService.current(ctx);
|
|
1553
1772
|
await command(rest.join(" "), ctx, {
|
|
1554
1773
|
settings,
|
|
1555
|
-
configService
|
|
1774
|
+
configService,
|
|
1775
|
+
statusService
|
|
1556
1776
|
});
|
|
1557
1777
|
} catch (error) {
|
|
1558
1778
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -4,6 +4,8 @@ export declare function createPiWorktreeConfigService(): Promise<{
|
|
|
4
4
|
parentDir?: string | undefined;
|
|
5
5
|
onCreate?: string | string[] | undefined;
|
|
6
6
|
worktreeRoot?: string | undefined;
|
|
7
|
+
onSwitch?: string | string[] | undefined;
|
|
8
|
+
onBeforeRemove?: string | string[] | undefined;
|
|
7
9
|
}>;
|
|
8
10
|
current: (ctx: {
|
|
9
11
|
cwd: string;
|
|
@@ -23,6 +25,8 @@ export declare function createPiWorktreeConfigService(): Promise<{
|
|
|
23
25
|
matchedPattern: string | null;
|
|
24
26
|
onCreate?: string | string[] | undefined;
|
|
25
27
|
worktreeRoot?: string | undefined;
|
|
28
|
+
onSwitch?: string | string[] | undefined;
|
|
29
|
+
onBeforeRemove?: string | string[] | undefined;
|
|
26
30
|
};
|
|
27
31
|
save: (data: PiWorktreeConfig) => Promise<void>;
|
|
28
32
|
config: {
|
|
@@ -31,6 +35,8 @@ export declare function createPiWorktreeConfigService(): Promise<{
|
|
|
31
35
|
worktreeRoot: import("typebox").TOptional<import("typebox").TString>;
|
|
32
36
|
parentDir: import("typebox").TOptional<import("typebox").TString>;
|
|
33
37
|
onCreate: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TString, import("typebox").TArray<import("typebox").TString>]>>;
|
|
38
|
+
onSwitch: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TString, import("typebox").TArray<import("typebox").TString>]>>;
|
|
39
|
+
onBeforeRemove: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TString, import("typebox").TArray<import("typebox").TString>]>>;
|
|
34
40
|
}>>>;
|
|
35
41
|
matchingStrategy: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TLiteral<"fail-on-tie">, import("typebox").TLiteral<"first-wins">, import("typebox").TLiteral<"last-wins">]>>;
|
|
36
42
|
logfile: import("typebox").TOptional<import("typebox").TString>;
|
|
@@ -45,6 +51,8 @@ export declare function createPiWorktreeConfigService(): Promise<{
|
|
|
45
51
|
worktreeRoot: import("typebox").TOptional<import("typebox").TString>;
|
|
46
52
|
parentDir: import("typebox").TOptional<import("typebox").TString>;
|
|
47
53
|
onCreate: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TString, import("typebox").TArray<import("typebox").TString>]>>;
|
|
54
|
+
onSwitch: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TString, import("typebox").TArray<import("typebox").TString>]>>;
|
|
55
|
+
onBeforeRemove: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TString, import("typebox").TArray<import("typebox").TString>]>>;
|
|
48
56
|
}>> | undefined;
|
|
49
57
|
logfile?: string | undefined;
|
|
50
58
|
onCreateDisplayOutputMaxLines?: number | undefined;
|
|
@@ -65,6 +73,8 @@ export declare function createPiWorktreeConfigService(): Promise<{
|
|
|
65
73
|
worktreeRoot: import("typebox").TOptional<import("typebox").TString>;
|
|
66
74
|
parentDir: import("typebox").TOptional<import("typebox").TString>;
|
|
67
75
|
onCreate: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TString, import("typebox").TArray<import("typebox").TString>]>>;
|
|
76
|
+
onSwitch: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TString, import("typebox").TArray<import("typebox").TString>]>>;
|
|
77
|
+
onBeforeRemove: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TString, import("typebox").TArray<import("typebox").TString>]>>;
|
|
68
78
|
}>>>;
|
|
69
79
|
matchingStrategy: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TLiteral<"fail-on-tie">, import("typebox").TLiteral<"first-wins">, import("typebox").TLiteral<"last-wins">]>>;
|
|
70
80
|
logfile: import("typebox").TOptional<import("typebox").TString>;
|
|
@@ -79,6 +89,8 @@ export declare function createPiWorktreeConfigService(): Promise<{
|
|
|
79
89
|
worktreeRoot: import("typebox").TOptional<import("typebox").TString>;
|
|
80
90
|
parentDir: import("typebox").TOptional<import("typebox").TString>;
|
|
81
91
|
onCreate: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TString, import("typebox").TArray<import("typebox").TString>]>>;
|
|
92
|
+
onSwitch: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TString, import("typebox").TArray<import("typebox").TString>]>>;
|
|
93
|
+
onBeforeRemove: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TString, import("typebox").TArray<import("typebox").TString>]>>;
|
|
82
94
|
}>> | undefined;
|
|
83
95
|
logfile?: string | undefined;
|
|
84
96
|
onCreateDisplayOutputMaxLines?: number | undefined;
|
|
@@ -3,6 +3,8 @@ declare const WorktreeSettingsSchema: import("typebox").TObject<{
|
|
|
3
3
|
worktreeRoot: import("typebox").TOptional<import("typebox").TString>;
|
|
4
4
|
parentDir: import("typebox").TOptional<import("typebox").TString>;
|
|
5
5
|
onCreate: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TString, import("typebox").TArray<import("typebox").TString>]>>;
|
|
6
|
+
onSwitch: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TString, import("typebox").TArray<import("typebox").TString>]>>;
|
|
7
|
+
onBeforeRemove: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TString, import("typebox").TArray<import("typebox").TString>]>>;
|
|
6
8
|
}>;
|
|
7
9
|
declare const MatchingStrategySchema: import("typebox").TUnion<[import("typebox").TLiteral<"fail-on-tie">, import("typebox").TLiteral<"first-wins">, import("typebox").TLiteral<"last-wins">]>;
|
|
8
10
|
declare const MatchStrategyResultSchema: import("typebox").TUnion<[import("typebox").TLiteral<"exact">, import("typebox").TLiteral<"unmatched">]>;
|
|
@@ -11,6 +13,8 @@ export declare const PiWorktreeConfigSchema: import("typebox").TObject<{
|
|
|
11
13
|
worktreeRoot: import("typebox").TOptional<import("typebox").TString>;
|
|
12
14
|
parentDir: import("typebox").TOptional<import("typebox").TString>;
|
|
13
15
|
onCreate: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TString, import("typebox").TArray<import("typebox").TString>]>>;
|
|
16
|
+
onSwitch: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TString, import("typebox").TArray<import("typebox").TString>]>>;
|
|
17
|
+
onBeforeRemove: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TString, import("typebox").TArray<import("typebox").TString>]>>;
|
|
14
18
|
}>>>;
|
|
15
19
|
matchingStrategy: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TLiteral<"fail-on-tie">, import("typebox").TLiteral<"first-wins">, import("typebox").TLiteral<"last-wins">]>>;
|
|
16
20
|
logfile: import("typebox").TOptional<import("typebox").TString>;
|
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ExtensionCommandContext } from '@mariozechner/pi-coding-agent';
|
|
2
2
|
import type { PiWorktreeConfigService } from './services/config/config.ts';
|
|
3
3
|
import { WorktreeSettingsConfig } from './services/config/schema.ts';
|
|
4
|
+
import { StatusIndicator } from './ui/status.ts';
|
|
4
5
|
export interface WorktreeCreatedContext {
|
|
5
6
|
path: string;
|
|
6
7
|
name: string;
|
|
@@ -11,5 +12,6 @@ export interface WorktreeCreatedContext {
|
|
|
11
12
|
export interface CommandDeps {
|
|
12
13
|
settings: WorktreeSettingsConfig;
|
|
13
14
|
configService: PiWorktreeConfigService;
|
|
15
|
+
statusService: StatusIndicator;
|
|
14
16
|
}
|
|
15
17
|
export type CmdHandler = (...args: [string, ExtensionCommandContext, CommandDeps]) => Promise<void>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ExtensionCommandContext } from '@mariozechner/pi-coding-agent';
|
|
2
|
+
type StatusOptions = {
|
|
3
|
+
busy?: keyof typeof StatusIndicator.busyStyles;
|
|
4
|
+
progress?: keyof typeof StatusIndicator.progressStyles;
|
|
5
|
+
};
|
|
6
|
+
export declare class StatusIndicator {
|
|
7
|
+
statusKey: string;
|
|
8
|
+
busyStyle: keyof typeof StatusIndicator.busyStyles;
|
|
9
|
+
private busyFrames;
|
|
10
|
+
private progressStyle;
|
|
11
|
+
private progressFrames;
|
|
12
|
+
constructor(statusKey: string, options?: StatusOptions);
|
|
13
|
+
busy(ctx: ExtensionCommandContext, message: string): () => void;
|
|
14
|
+
cautious(ctx: ExtensionCommandContext, message: string): void;
|
|
15
|
+
critical(ctx: ExtensionCommandContext, message: string): void;
|
|
16
|
+
positive(ctx: ExtensionCommandContext, message: string): void;
|
|
17
|
+
informative(ctx: ExtensionCommandContext, message: string): void;
|
|
18
|
+
progress(ctx: ExtensionCommandContext, message: string, percent: number): void;
|
|
19
|
+
static busyStyles: {
|
|
20
|
+
dots: string[];
|
|
21
|
+
};
|
|
22
|
+
static progressStyles: {
|
|
23
|
+
bars: (percent: number) => string;
|
|
24
|
+
pie: (percent: number) => string;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export {};
|