@zenobius/pi-worktrees 0.2.0 → 0.3.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 CHANGED
@@ -1,5 +1,8 @@
1
1
  # `@zenobius/pi-worktrees`
2
2
 
3
+ <img width="1531" height="1172" alt="image" src="https://github.com/user-attachments/assets/33fe4c01-4d9b-41ec-a326-116db6e750df" />
4
+
5
+
3
6
  Git worktree management for [Pi Coding Agent](https://github.com/badlogic/pi-mono) with a clean `/worktree` command surface.
4
7
 
5
8
  This extension helps you spin up isolated feature workspaces quickly, with safety checks and optional post-create automation.
@@ -44,6 +47,28 @@ If Pi is already running, use `/reload` to load newly installed extensions.
44
47
 
45
48
  ---
46
49
 
50
+ ## Getting started in 2 minutes
51
+
52
+ 1. Install the extension:
53
+
54
+ ```bash
55
+ pi install npm:@zenobius/pi-worktrees
56
+ ```
57
+
58
+ 2. Open a git repo in Pi and run:
59
+
60
+ ```text
61
+ /worktree init
62
+ /worktree create auth-refactor
63
+ /worktree list
64
+ ```
65
+
66
+ 3. Optional: jump into it from your shell using the printed path:
67
+
68
+ ```text
69
+ /worktree cd auth-refactor
70
+ ```
71
+
47
72
  ## Quick start
48
73
 
49
74
  In Pi:
@@ -58,11 +83,9 @@ In Pi:
58
83
  /worktree prune
59
84
  ```
60
85
 
61
- ### How I use `/worktrees`
62
-
63
- Since I use nvim and zellij, i want the worktrees I create to be clones of the workspace
64
- I have for the current one, so to this end, my `onCreate` looks like:
86
+ ### Example: How I use `/worktree`
65
87
 
88
+ I use Neovim and Zellij, and I want each new worktree to boot a ready-to-code workspace. My `onCreate` looks like:
66
89
  ```json
67
90
  {
68
91
  "worktrees": {
@@ -78,12 +101,9 @@ I have for the current one, so to this end, my `onCreate` looks like:
78
101
  }
79
102
  }
80
103
  }
81
-
82
104
  ```
83
105
 
84
- This means when ever i create a new worktree, it creates a new zellij tab with nvim and pi running
85
- in it on the new worktree path.
86
-
106
+ This creates a new Zellij tab with Neovim and Pi running in the new worktree path.
87
107
  ---
88
108
 
89
109
  ## Command reference
@@ -92,7 +112,7 @@ in it on the new worktree path.
92
112
  |---|---|
93
113
  | `/worktree init` | Interactive setup for extension settings |
94
114
  | `/worktree settings` | Show all current settings |
95
- | `/worktree settings <key>` | Get one setting (`worktreeRoot`, `onCreate`) |
115
+ | `/worktree settings <key>` | Get one setting (`worktreeRoot`, `parentDir` alias, `onCreate`) |
96
116
  | `/worktree settings <key> <value>` | Set one setting |
97
117
  | `/worktree create <feature-name>` | Create a new worktree + branch `feature/<feature-name>` |
98
118
  | `/worktree list` | List all worktrees (`/worktree ls` alias) |
@@ -121,6 +141,13 @@ Settings live in `~/.pi/agent/pi-worktrees-settings.json`.
121
141
  }
122
142
  },
123
143
  "matchingStrategy": "fail-on-tie",
144
+ "onCreateDisplayOutputMaxLines": 5,
145
+ "onCreateCmdDisplayPending": "[ ] {{cmd}}",
146
+ "onCreateCmdDisplaySuccess": "[x] {{cmd}}",
147
+ "onCreateCmdDisplayError": "[ ] {{cmd}} [ERROR]",
148
+ "onCreateCmdDisplayPendingColor": "dim",
149
+ "onCreateCmdDisplaySuccessColor": "success",
150
+ "onCreateCmdDisplayErrorColor": "error",
124
151
  "worktree": {
125
152
  "worktreeRoot": "~/.local/share/worktrees/{{project}}",
126
153
  "onCreate": "mise setup"
@@ -128,6 +155,21 @@ Settings live in `~/.pi/agent/pi-worktrees-settings.json`.
128
155
  }
129
156
  ```
130
157
 
158
+ ### Configuration reference
159
+
160
+ | Key | Type | Default | Description |
161
+ |---|---|---|---|
162
+ | `worktrees` | `Record<string, WorktreeSettings>` | `{}` | Pattern-matched settings by repo URL or glob. |
163
+ | `matchingStrategy` | `'fail-on-tie' \| 'first-wins' \| 'last-wins'` | `fail-on-tie` | Tie-break behavior for equally specific patterns. |
164
+ | `onCreateDisplayOutputMaxLines` | `number` (integer, `>= 0`) | `5` | Number of latest stdout/stderr lines shown in live UI updates during `onCreate`. |
165
+ | `onCreateCmdDisplayPending` | `string` | `[ ] {{cmd}}` | Template for pending/running command display lines. |
166
+ | `onCreateCmdDisplaySuccess` | `string` | `[x] {{cmd}}` | Template for successful command display lines. |
167
+ | `onCreateCmdDisplayError` | `string` | `[ ] {{cmd}} [ERROR]` | Template for failed command display lines. |
168
+ | `onCreateCmdDisplayPendingColor` | `string` | `dim` | Pi theme color name for pending/running command lines. |
169
+ | `onCreateCmdDisplaySuccessColor` | `string` | `success` | Pi theme color name for successful command lines. |
170
+ | `onCreateCmdDisplayErrorColor` | `string` | `error` | Pi theme color name for failed command lines. |
171
+ | `worktree` (legacy) | `WorktreeSettings` | n/a | Legacy fallback shape; migrated automatically. |
172
+
131
173
  ### Matching model
132
174
 
133
175
  For the current repository, settings are resolved in this order:
@@ -151,6 +193,34 @@ For the current repository, settings are resolved in this order:
151
193
 
152
194
  When an array is used, commands run sequentially and stop on first failure.
153
195
 
196
+ ### `onCreateDisplayOutputMaxLines`
197
+
198
+ Controls only live UI output verbosity for `onCreate` command execution.
199
+ - **Default**: `5`
200
+ - **Scope**: display only
201
+ - **Does not affect**: logfile contents (full stdout/stderr is still logged)
202
+
203
+ ### `onCreate` command line display templates
204
+
205
+ These templates control how each command line is rendered in the live progress list.
206
+
207
+ - `onCreateCmdDisplayPending` (default: `[ ] {{cmd}}`)
208
+ - `onCreateCmdDisplaySuccess` (default: `[x] {{cmd}}`)
209
+ - `onCreateCmdDisplayError` (default: `[ ] {{cmd}} [ERROR]`)
210
+
211
+ Supported token:
212
+ - `{{cmd}}` (or `{cmd}`) → expanded command string
213
+
214
+ ### `onCreate` command line display colors
215
+
216
+ These settings use Pi theme color names:
217
+
218
+ - `onCreateCmdDisplayPendingColor` (default: `dim`)
219
+ - `onCreateCmdDisplaySuccessColor` (default: `success`)
220
+ - `onCreateCmdDisplayErrorColor` (default: `error`)
221
+
222
+ Supported color names in this extension: `dim`, `accent`, `info`, `success`, `warning`, `error`.
223
+
154
224
  ### `worktreeRoot`
155
225
 
156
226
  Where new worktrees are created.
@@ -11,6 +11,13 @@ export interface OnCreateResult {
11
11
  }
12
12
  export interface OnCreateHookOptions {
13
13
  logPath?: string;
14
+ displayOutputMaxLines?: number;
15
+ cmdDisplayPending?: string;
16
+ cmdDisplaySuccess?: string;
17
+ cmdDisplayError?: string;
18
+ cmdDisplayPendingColor?: string;
19
+ cmdDisplaySuccessColor?: string;
20
+ cmdDisplayErrorColor?: string;
14
21
  }
15
22
  /**
16
23
  * Runs post-create hooks sequentially.
package/dist/index.js CHANGED
@@ -21,7 +21,8 @@ import {
21
21
  Optional,
22
22
  Record as TypeRecord,
23
23
  String as TypeString,
24
- Union
24
+ Union,
25
+ Integer as TypeInteger
25
26
  } from "typebox";
26
27
  var OnCreateSchema = Union([TypeString(), TypeArray(TypeString())]);
27
28
  var WorktreeSettingsSchema = TypeObject({
@@ -40,10 +41,24 @@ var MatchingStrategySchema = Union([
40
41
  var MatchStrategyResultSchema = Union([Literal("exact"), Literal("unmatched")]);
41
42
  var WorktreesMapSchema = TypeRecord(TypeString(), WorktreeSettingsSchema);
42
43
  var LogfileSchema = TypeString();
44
+ var OnCreateDisplayOutputMaxLinesSchema = TypeInteger({ minimum: 0 });
45
+ var OnCreateCmdDisplayPendingSchema = TypeString();
46
+ var OnCreateCmdDisplaySuccessSchema = TypeString();
47
+ var OnCreateCmdDisplayErrorSchema = TypeString();
48
+ var OnCreateCmdDisplayPendingColorSchema = TypeString();
49
+ var OnCreateCmdDisplaySuccessColorSchema = TypeString();
50
+ var OnCreateCmdDisplayErrorColorSchema = TypeString();
43
51
  var PiWorktreeConfigSchema = TypeObject({
44
52
  worktrees: Optional(WorktreesMapSchema),
45
53
  matchingStrategy: Optional(MatchingStrategySchema),
46
- logfile: Optional(LogfileSchema)
54
+ logfile: Optional(LogfileSchema),
55
+ onCreateDisplayOutputMaxLines: Optional(OnCreateDisplayOutputMaxLinesSchema),
56
+ onCreateCmdDisplayPending: Optional(OnCreateCmdDisplayPendingSchema),
57
+ onCreateCmdDisplaySuccess: Optional(OnCreateCmdDisplaySuccessSchema),
58
+ onCreateCmdDisplayError: Optional(OnCreateCmdDisplayErrorSchema),
59
+ onCreateCmdDisplayPendingColor: Optional(OnCreateCmdDisplayPendingColorSchema),
60
+ onCreateCmdDisplaySuccessColor: Optional(OnCreateCmdDisplaySuccessColorSchema),
61
+ onCreateCmdDisplayErrorColor: Optional(OnCreateCmdDisplayErrorColorSchema)
47
62
  }, {
48
63
  $id: "UnresolvedConfig",
49
64
  additionalProperties: true
@@ -268,8 +283,94 @@ var migration3 = {
268
283
  }
269
284
  };
270
285
 
286
+ // src/services/config/migrations/04-oncreate-display-output-max-lines.ts
287
+ var DEFAULT_ONCREATE_DISPLAY_OUTPUT_MAX_LINES = 5;
288
+ function toRecord4(value) {
289
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
290
+ return {};
291
+ }
292
+ return { ...value };
293
+ }
294
+ var migration4 = {
295
+ id: "oncreate-display-output-max-lines-default",
296
+ up(config) {
297
+ const record = toRecord4(config);
298
+ if (record.onCreateDisplayOutputMaxLines !== undefined) {
299
+ return record;
300
+ }
301
+ return {
302
+ ...record,
303
+ onCreateDisplayOutputMaxLines: DEFAULT_ONCREATE_DISPLAY_OUTPUT_MAX_LINES
304
+ };
305
+ },
306
+ down(config) {
307
+ const record = toRecord4(config);
308
+ const next = { ...record };
309
+ delete next.onCreateDisplayOutputMaxLines;
310
+ return next;
311
+ }
312
+ };
313
+
314
+ // src/services/config/migrations/05-oncreate-command-display-format.ts
315
+ var DEFAULT_ONCREATE_CMD_DISPLAY_PENDING = "[ ] {{cmd}}";
316
+ var DEFAULT_ONCREATE_CMD_DISPLAY_SUCCESS = "[x] {{cmd}}";
317
+ var DEFAULT_ONCREATE_CMD_DISPLAY_ERROR = "[ ] {{cmd}} [ERROR]";
318
+ var DEFAULT_ONCREATE_CMD_DISPLAY_PENDING_COLOR = "dim";
319
+ var DEFAULT_ONCREATE_CMD_DISPLAY_SUCCESS_COLOR = "success";
320
+ var DEFAULT_ONCREATE_CMD_DISPLAY_ERROR_COLOR = "error";
321
+ function toRecord5(value) {
322
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
323
+ return {};
324
+ }
325
+ return { ...value };
326
+ }
327
+ var migration5 = {
328
+ id: "oncreate-command-display-format-defaults",
329
+ up(config) {
330
+ const record = toRecord5(config);
331
+ const next = { ...record };
332
+ if (next.onCreateCmdDisplayPending === undefined) {
333
+ next.onCreateCmdDisplayPending = DEFAULT_ONCREATE_CMD_DISPLAY_PENDING;
334
+ }
335
+ if (next.onCreateCmdDisplaySuccess === undefined) {
336
+ next.onCreateCmdDisplaySuccess = DEFAULT_ONCREATE_CMD_DISPLAY_SUCCESS;
337
+ }
338
+ if (next.onCreateCmdDisplayError === undefined) {
339
+ next.onCreateCmdDisplayError = DEFAULT_ONCREATE_CMD_DISPLAY_ERROR;
340
+ }
341
+ if (next.onCreateCmdDisplayPendingColor === undefined) {
342
+ next.onCreateCmdDisplayPendingColor = DEFAULT_ONCREATE_CMD_DISPLAY_PENDING_COLOR;
343
+ }
344
+ if (next.onCreateCmdDisplaySuccessColor === undefined) {
345
+ next.onCreateCmdDisplaySuccessColor = DEFAULT_ONCREATE_CMD_DISPLAY_SUCCESS_COLOR;
346
+ }
347
+ if (next.onCreateCmdDisplayErrorColor === undefined) {
348
+ next.onCreateCmdDisplayErrorColor = DEFAULT_ONCREATE_CMD_DISPLAY_ERROR_COLOR;
349
+ }
350
+ return next;
351
+ },
352
+ down(config) {
353
+ const record = toRecord5(config);
354
+ const next = { ...record };
355
+ delete next.onCreateCmdDisplayPending;
356
+ delete next.onCreateCmdDisplaySuccess;
357
+ delete next.onCreateCmdDisplayError;
358
+ delete next.onCreateCmdDisplayPendingColor;
359
+ delete next.onCreateCmdDisplaySuccessColor;
360
+ delete next.onCreateCmdDisplayErrorColor;
361
+ return next;
362
+ }
363
+ };
364
+
271
365
  // src/services/config/config.ts
272
366
  var DEFAULT_LOGFILE_TEMPLATE = "/tmp/pi-worktree-{sessionId}-{name}.log";
367
+ var DEFAULT_ONCREATE_DISPLAY_OUTPUT_MAX_LINES2 = 5;
368
+ var DEFAULT_ONCREATE_CMD_DISPLAY_PENDING2 = "[ ] {{cmd}}";
369
+ var DEFAULT_ONCREATE_CMD_DISPLAY_SUCCESS2 = "[x] {{cmd}}";
370
+ var DEFAULT_ONCREATE_CMD_DISPLAY_ERROR2 = "[ ] {{cmd}} [ERROR]";
371
+ var DEFAULT_ONCREATE_CMD_DISPLAY_PENDING_COLOR2 = "dim";
372
+ var DEFAULT_ONCREATE_CMD_DISPLAY_SUCCESS_COLOR2 = "success";
373
+ var DEFAULT_ONCREATE_CMD_DISPLAY_ERROR_COLOR2 = "error";
273
374
  async function createPiWorktreeConfigService() {
274
375
  const parse = (value) => {
275
376
  return Parse2(PiWorktreeConfigSchema, value);
@@ -277,7 +378,7 @@ async function createPiWorktreeConfigService() {
277
378
  const store = await createConfigService("pi-worktrees", {
278
379
  defaults: {},
279
380
  parse,
280
- migrations: [migration, migration2, migration3]
381
+ migrations: [migration, migration2, migration3, migration4, migration5]
281
382
  });
282
383
  await store.reload();
283
384
  const save = async (data) => {
@@ -290,6 +391,27 @@ async function createPiWorktreeConfigService() {
290
391
  if (data.logfile !== undefined) {
291
392
  await store.set("logfile", data.logfile, "home");
292
393
  }
394
+ if (data.onCreateDisplayOutputMaxLines !== undefined) {
395
+ await store.set("onCreateDisplayOutputMaxLines", data.onCreateDisplayOutputMaxLines, "home");
396
+ }
397
+ if (data.onCreateCmdDisplayPending !== undefined) {
398
+ await store.set("onCreateCmdDisplayPending", data.onCreateCmdDisplayPending, "home");
399
+ }
400
+ if (data.onCreateCmdDisplaySuccess !== undefined) {
401
+ await store.set("onCreateCmdDisplaySuccess", data.onCreateCmdDisplaySuccess, "home");
402
+ }
403
+ if (data.onCreateCmdDisplayError !== undefined) {
404
+ await store.set("onCreateCmdDisplayError", data.onCreateCmdDisplayError, "home");
405
+ }
406
+ if (data.onCreateCmdDisplayPendingColor !== undefined) {
407
+ await store.set("onCreateCmdDisplayPendingColor", data.onCreateCmdDisplayPendingColor, "home");
408
+ }
409
+ if (data.onCreateCmdDisplaySuccessColor !== undefined) {
410
+ await store.set("onCreateCmdDisplaySuccessColor", data.onCreateCmdDisplaySuccessColor, "home");
411
+ }
412
+ if (data.onCreateCmdDisplayErrorColor !== undefined) {
413
+ await store.set("onCreateCmdDisplayErrorColor", data.onCreateCmdDisplayErrorColor, "home");
414
+ }
293
415
  await store.save("home");
294
416
  };
295
417
  const worktrees = new Map(Object.entries(store.config.worktrees || {}));
@@ -310,6 +432,13 @@ async function createPiWorktreeConfigService() {
310
432
  mainWorktree,
311
433
  parentDir,
312
434
  logfile: store.config.logfile ?? DEFAULT_LOGFILE_TEMPLATE,
435
+ onCreateDisplayOutputMaxLines: store.config.onCreateDisplayOutputMaxLines ?? DEFAULT_ONCREATE_DISPLAY_OUTPUT_MAX_LINES2,
436
+ onCreateCmdDisplayPending: store.config.onCreateCmdDisplayPending ?? DEFAULT_ONCREATE_CMD_DISPLAY_PENDING2,
437
+ onCreateCmdDisplaySuccess: store.config.onCreateCmdDisplaySuccess ?? DEFAULT_ONCREATE_CMD_DISPLAY_SUCCESS2,
438
+ onCreateCmdDisplayError: store.config.onCreateCmdDisplayError ?? DEFAULT_ONCREATE_CMD_DISPLAY_ERROR2,
439
+ onCreateCmdDisplayPendingColor: store.config.onCreateCmdDisplayPendingColor ?? DEFAULT_ONCREATE_CMD_DISPLAY_PENDING_COLOR2,
440
+ onCreateCmdDisplaySuccessColor: store.config.onCreateCmdDisplaySuccessColor ?? DEFAULT_ONCREATE_CMD_DISPLAY_SUCCESS_COLOR2,
441
+ onCreateCmdDisplayErrorColor: store.config.onCreateCmdDisplayErrorColor ?? DEFAULT_ONCREATE_CMD_DISPLAY_ERROR_COLOR2,
313
442
  matchedPattern: resolution.matchedPattern
314
443
  };
315
444
  };
@@ -622,20 +751,45 @@ var ANSI = {
622
751
  gray: "\x1B[90m",
623
752
  blue: "\x1B[34m",
624
753
  green: "\x1B[32m",
625
- red: "\x1B[31m"
754
+ red: "\x1B[31m",
755
+ yellow: "\x1B[33m"
626
756
  };
627
- function formatCommandLine(index, command, state) {
628
- const number = String(index + 1).padStart(2, "0");
629
- if (state === "pending") {
630
- return `${ANSI.gray}\u25CB [${number}] ${command}${ANSI.reset}`;
757
+ function applyCommandTemplate(template, command) {
758
+ return template.replace(/\{\{cmd\}\}|\{cmd\}/g, command);
759
+ }
760
+ function resolveAnsiColor(colorName) {
761
+ if (colorName === "dim") {
762
+ return ANSI.gray;
631
763
  }
632
- if (state === "running") {
633
- return `${ANSI.blue}\uD83D\uDEA7 [${number}] ${command}${ANSI.reset}`;
764
+ if (colorName === "accent" || colorName === "info") {
765
+ return ANSI.blue;
766
+ }
767
+ if (colorName === "success") {
768
+ return ANSI.green;
769
+ }
770
+ if (colorName === "error") {
771
+ return ANSI.red;
772
+ }
773
+ if (colorName === "warning") {
774
+ return ANSI.yellow;
775
+ }
776
+ return "";
777
+ }
778
+ function colorize(text, colorName) {
779
+ const ansi = resolveAnsiColor(colorName);
780
+ if (!ansi) {
781
+ return text;
634
782
  }
783
+ return `${ansi}${text}${ANSI.reset}`;
784
+ }
785
+ function formatCommandLine(command, state, config) {
635
786
  if (state === "success") {
636
- return `${ANSI.green}\u2705 [${number}] ${command}${ANSI.reset}`;
787
+ return colorize(applyCommandTemplate(config.successTemplate, command), config.successColor);
788
+ }
789
+ if (state === "failed") {
790
+ return colorize(applyCommandTemplate(config.errorTemplate, command), config.errorColor);
637
791
  }
638
- return `${ANSI.red}\u274C [${number}] ${command}${ANSI.reset}`;
792
+ return colorize(applyCommandTemplate(config.pendingTemplate, command), config.pendingColor);
639
793
  }
640
794
  function toLines(text) {
641
795
  return text.replace(/\r/g, "").split(`
@@ -648,15 +802,25 @@ function formatOutputLine(stream, line, state) {
648
802
  }
649
803
  return `${ANSI.gray} ${prefix} ${line}${ANSI.reset}`;
650
804
  }
651
- function formatCommandList(commands, states, outputs, logPath) {
805
+ function getDisplayLines(text, maxLines) {
806
+ const lines = toLines(text);
807
+ if (maxLines < 0) {
808
+ return lines;
809
+ }
810
+ if (maxLines === 0) {
811
+ return [];
812
+ }
813
+ return lines.slice(-maxLines);
814
+ }
815
+ function formatCommandList(commands, states, outputs, commandDisplay, logPath, displayOutputMaxLines = 5) {
652
816
  const lines = ["onCreate steps:"];
653
817
  for (const [index, command] of commands.entries()) {
654
818
  const state = states[index];
655
- lines.push(formatCommandLine(index, command, state));
656
- for (const line of toLines(outputs[index].stdout)) {
819
+ lines.push(formatCommandLine(command, state, commandDisplay));
820
+ for (const line of getDisplayLines(outputs[index].stdout, displayOutputMaxLines)) {
657
821
  lines.push(formatOutputLine("stdout", line, state));
658
822
  }
659
- for (const line of toLines(outputs[index].stderr)) {
823
+ for (const line of getDisplayLines(outputs[index].stderr, displayOutputMaxLines)) {
660
824
  lines.push(formatOutputLine("stderr", line, state));
661
825
  }
662
826
  }
@@ -738,13 +902,22 @@ async function runOnCreateHook(createdCtx, settings, notify, options) {
738
902
  ].join(`
739
903
  `));
740
904
  }
741
- notify(formatCommandList(commands, commandStates, commandOutputs), "info");
905
+ const displayOutputMaxLines = options?.displayOutputMaxLines ?? 5;
906
+ const commandDisplay = {
907
+ pendingTemplate: options?.cmdDisplayPending ?? "[ ] {{cmd}}",
908
+ successTemplate: options?.cmdDisplaySuccess ?? "[x] {{cmd}}",
909
+ errorTemplate: options?.cmdDisplayError ?? "[ ] {{cmd}} [ERROR]",
910
+ pendingColor: options?.cmdDisplayPendingColor ?? "dim",
911
+ successColor: options?.cmdDisplaySuccessColor ?? "success",
912
+ errorColor: options?.cmdDisplayErrorColor ?? "error"
913
+ };
914
+ notify(formatCommandList(commands, commandStates, commandOutputs, commandDisplay, undefined, displayOutputMaxLines), "info");
742
915
  for (const [index, command] of commands.entries()) {
743
916
  commandStates[index] = "running";
744
- notify(formatCommandList(commands, commandStates, commandOutputs), "info");
917
+ notify(formatCommandList(commands, commandStates, commandOutputs, commandDisplay, undefined, displayOutputMaxLines), "info");
745
918
  const result = await runCommand(command, createdCtx.path, (stream, chunk) => {
746
919
  commandOutputs[index][stream] += chunk;
747
- notify(formatCommandList(commands, commandStates, commandOutputs), "info");
920
+ notify(formatCommandList(commands, commandStates, commandOutputs, commandDisplay, undefined, displayOutputMaxLines), "info");
748
921
  });
749
922
  if (options?.logPath) {
750
923
  appendCommandLog(options.logPath, command, result);
@@ -752,7 +925,7 @@ async function runOnCreateHook(createdCtx, settings, notify, options) {
752
925
  executed.push(command);
753
926
  if (!result.success) {
754
927
  commandStates[index] = "failed";
755
- notify(formatCommandList(commands, commandStates, commandOutputs, options?.logPath), "error");
928
+ notify(formatCommandList(commands, commandStates, commandOutputs, commandDisplay, options?.logPath, displayOutputMaxLines), "error");
756
929
  notify(`onCreate failed (exit ${result.code}): ${result.stderr.slice(0, 200)}${options?.logPath ? `
757
930
  log: ${options.logPath}` : ""}`, "error");
758
931
  return {
@@ -766,9 +939,9 @@ log: ${options.logPath}` : ""}`, "error");
766
939
  };
767
940
  }
768
941
  commandStates[index] = "success";
769
- notify(formatCommandList(commands, commandStates, commandOutputs), "info");
942
+ notify(formatCommandList(commands, commandStates, commandOutputs, commandDisplay, undefined, displayOutputMaxLines), "info");
770
943
  }
771
- notify(formatCommandList(commands, commandStates, commandOutputs, options?.logPath), "info");
944
+ notify(formatCommandList(commands, commandStates, commandOutputs, commandDisplay, options?.logPath, displayOutputMaxLines), "info");
772
945
  return { success: true, executed };
773
946
  }
774
947
 
@@ -824,7 +997,16 @@ async function cmdCreate(args, ctx, deps) {
824
997
  name: safeName,
825
998
  timestamp
826
999
  });
827
- await runOnCreateHook(createdCtx, current, ctx.ui.notify.bind(ctx.ui), { logPath });
1000
+ await runOnCreateHook(createdCtx, current, ctx.ui.notify.bind(ctx.ui), {
1001
+ logPath,
1002
+ displayOutputMaxLines: current.onCreateDisplayOutputMaxLines,
1003
+ cmdDisplayPending: current.onCreateCmdDisplayPending,
1004
+ cmdDisplaySuccess: current.onCreateCmdDisplaySuccess,
1005
+ cmdDisplayError: current.onCreateCmdDisplayError,
1006
+ cmdDisplayPendingColor: current.onCreateCmdDisplayPendingColor,
1007
+ cmdDisplaySuccessColor: current.onCreateCmdDisplaySuccessColor,
1008
+ cmdDisplayErrorColor: current.onCreateCmdDisplayErrorColor
1009
+ });
828
1010
  }
829
1011
 
830
1012
  // src/cmds/cmdInit.ts
@@ -1237,6 +1419,27 @@ async function cmdTemplates(_args, ctx, deps) {
1237
1419
  `), "info");
1238
1420
  }
1239
1421
 
1422
+ // src/services/completions.ts
1423
+ function toItem(command) {
1424
+ return {
1425
+ value: command,
1426
+ label: command
1427
+ };
1428
+ }
1429
+ function createCompletionFactory(commands) {
1430
+ const commandNames = Object.keys(commands).sort();
1431
+ return (argumentPrefix) => {
1432
+ const prefix = argumentPrefix.trimStart();
1433
+ if (prefix.includes(" ")) {
1434
+ return null;
1435
+ }
1436
+ if (!prefix) {
1437
+ return commandNames.map(toItem);
1438
+ }
1439
+ return commandNames.filter((command) => command.startsWith(prefix)).map(toItem);
1440
+ };
1441
+ }
1442
+
1240
1443
  // src/index.ts
1241
1444
  var HELP_TEXT = `
1242
1445
  /worktree - Git worktree management
@@ -1266,6 +1469,13 @@ Configuration (~/.pi/agent/pi-worktrees-settings.json):
1266
1469
  },
1267
1470
  "matchingStrategy": "fail-on-tie",
1268
1471
  "logfile": "/tmp/pi-worktree-{sessionId}-{name}.log",
1472
+ "onCreateDisplayOutputMaxLines": 5,
1473
+ "onCreateCmdDisplayPending": "[ ] {{cmd}}",
1474
+ "onCreateCmdDisplaySuccess": "[x] {{cmd}}",
1475
+ "onCreateCmdDisplayError": "[ ] {{cmd}} [ERROR]",
1476
+ "onCreateCmdDisplayPendingColor": "dim",
1477
+ "onCreateCmdDisplaySuccessColor": "success",
1478
+ "onCreateCmdDisplayErrorColor": "error",
1269
1479
  "worktree": {
1270
1480
  "worktreeRoot": "~/.worktrees/{{project}}",
1271
1481
  "onCreate": "mise setup"
@@ -1320,8 +1530,12 @@ var PiWorktreeExtension = async function(pi) {
1320
1530
  ctx.ui.setStatus(`Worktrees`, notification.msg);
1321
1531
  }
1322
1532
  });
1533
+ const getSubcommandCompletions = createCompletionFactory(commands);
1323
1534
  pi.registerCommand("worktree", {
1324
1535
  description: "Git worktree management for isolated workspaces",
1536
+ getArgumentCompletions(argumentPrefix) {
1537
+ return getSubcommandCompletions(argumentPrefix);
1538
+ },
1325
1539
  handler: async (args, ctx) => {
1326
1540
  const [cmd, ...rest] = args.trim().split(/\s+/);
1327
1541
  const command = commands[cmd];
@@ -0,0 +1,4 @@
1
+ import type { RegisteredCommand } from '@mariozechner/pi-coding-agent';
2
+ type CommandMap = Record<string, unknown>;
3
+ export declare function createCompletionFactory(commands: CommandMap): NonNullable<RegisteredCommand['getArgumentCompletions']>;
4
+ export {};
@@ -13,6 +13,13 @@ export declare function createPiWorktreeConfigService(): Promise<{
13
13
  mainWorktree: string;
14
14
  parentDir: string;
15
15
  logfile: string;
16
+ onCreateDisplayOutputMaxLines: number;
17
+ onCreateCmdDisplayPending: string;
18
+ onCreateCmdDisplaySuccess: string;
19
+ onCreateCmdDisplayError: string;
20
+ onCreateCmdDisplayPendingColor: string;
21
+ onCreateCmdDisplaySuccessColor: string;
22
+ onCreateCmdDisplayErrorColor: string;
16
23
  matchedPattern: string | null;
17
24
  onCreate?: string | string[] | undefined;
18
25
  worktreeRoot?: string | undefined;
@@ -27,12 +34,26 @@ export declare function createPiWorktreeConfigService(): Promise<{
27
34
  }>>>;
28
35
  matchingStrategy: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TLiteral<"fail-on-tie">, import("typebox").TLiteral<"first-wins">, import("typebox").TLiteral<"last-wins">]>>;
29
36
  logfile: import("typebox").TOptional<import("typebox").TString>;
37
+ onCreateDisplayOutputMaxLines: import("typebox").TOptional<import("typebox").TInteger>;
38
+ onCreateCmdDisplayPending: import("typebox").TOptional<import("typebox").TString>;
39
+ onCreateCmdDisplaySuccess: import("typebox").TOptional<import("typebox").TString>;
40
+ onCreateCmdDisplayError: import("typebox").TOptional<import("typebox").TString>;
41
+ onCreateCmdDisplayPendingColor: import("typebox").TOptional<import("typebox").TString>;
42
+ onCreateCmdDisplaySuccessColor: import("typebox").TOptional<import("typebox").TString>;
43
+ onCreateCmdDisplayErrorColor: import("typebox").TOptional<import("typebox").TString>;
30
44
  }, "^.*$", import("typebox").TObject<{
31
45
  worktreeRoot: import("typebox").TOptional<import("typebox").TString>;
32
46
  parentDir: import("typebox").TOptional<import("typebox").TString>;
33
47
  onCreate: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TString, import("typebox").TArray<import("typebox").TString>]>>;
34
48
  }>> | undefined;
35
49
  logfile?: string | undefined;
50
+ onCreateDisplayOutputMaxLines?: number | undefined;
51
+ onCreateCmdDisplayPending?: string | undefined;
52
+ onCreateCmdDisplaySuccess?: string | undefined;
53
+ onCreateCmdDisplayError?: string | undefined;
54
+ onCreateCmdDisplayPendingColor?: string | undefined;
55
+ onCreateCmdDisplaySuccessColor?: string | undefined;
56
+ onCreateCmdDisplayErrorColor?: string | undefined;
36
57
  matchingStrategy?: "fail-on-tie" | "first-wins" | "last-wins" | undefined;
37
58
  };
38
59
  ready: Promise<void>;
@@ -47,16 +68,37 @@ export declare function createPiWorktreeConfigService(): Promise<{
47
68
  }>>>;
48
69
  matchingStrategy: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TLiteral<"fail-on-tie">, import("typebox").TLiteral<"first-wins">, import("typebox").TLiteral<"last-wins">]>>;
49
70
  logfile: import("typebox").TOptional<import("typebox").TString>;
71
+ onCreateDisplayOutputMaxLines: import("typebox").TOptional<import("typebox").TInteger>;
72
+ onCreateCmdDisplayPending: import("typebox").TOptional<import("typebox").TString>;
73
+ onCreateCmdDisplaySuccess: import("typebox").TOptional<import("typebox").TString>;
74
+ onCreateCmdDisplayError: import("typebox").TOptional<import("typebox").TString>;
75
+ onCreateCmdDisplayPendingColor: import("typebox").TOptional<import("typebox").TString>;
76
+ onCreateCmdDisplaySuccessColor: import("typebox").TOptional<import("typebox").TString>;
77
+ onCreateCmdDisplayErrorColor: import("typebox").TOptional<import("typebox").TString>;
50
78
  }, "^.*$", import("typebox").TObject<{
51
79
  worktreeRoot: import("typebox").TOptional<import("typebox").TString>;
52
80
  parentDir: import("typebox").TOptional<import("typebox").TString>;
53
81
  onCreate: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TString, import("typebox").TArray<import("typebox").TString>]>>;
54
82
  }>> | undefined;
55
83
  logfile?: string | undefined;
84
+ onCreateDisplayOutputMaxLines?: number | undefined;
85
+ onCreateCmdDisplayPending?: string | undefined;
86
+ onCreateCmdDisplaySuccess?: string | undefined;
87
+ onCreateCmdDisplayError?: string | undefined;
88
+ onCreateCmdDisplayPendingColor?: string | undefined;
89
+ onCreateCmdDisplaySuccessColor?: string | undefined;
90
+ onCreateCmdDisplayErrorColor?: string | undefined;
56
91
  matchingStrategy?: "fail-on-tie" | "first-wins" | "last-wins" | undefined;
57
92
  }>;
58
93
  }>;
59
94
  export declare const DefaultWorktreeSettings: WorktreeSettingsConfig;
60
95
  export declare const DefaultLogfileTemplate = "/tmp/pi-worktree-{sessionId}-{name}.log";
96
+ export declare const DefaultOnCreateDisplayOutputMaxLines = 5;
97
+ export declare const DefaultOnCreateCmdDisplayPending = "[ ] {{cmd}}";
98
+ export declare const DefaultOnCreateCmdDisplaySuccess = "[x] {{cmd}}";
99
+ export declare const DefaultOnCreateCmdDisplayError = "[ ] {{cmd}} [ERROR]";
100
+ export declare const DefaultOnCreateCmdDisplayPendingColor = "dim";
101
+ export declare const DefaultOnCreateCmdDisplaySuccessColor = "success";
102
+ export declare const DefaultOnCreateCmdDisplayErrorColor = "error";
61
103
  export type PiWorktreeConfigService = Awaited<ReturnType<typeof createPiWorktreeConfigService>>;
62
104
  export type PiWorktreeConfiguredWorktreeMap = PiWorktreeConfigService['worktrees'];
@@ -0,0 +1,2 @@
1
+ import { type Migration } from '@zenobius/pi-extension-config';
2
+ export declare const migration: Migration;
@@ -0,0 +1,2 @@
1
+ import { type Migration } from '@zenobius/pi-extension-config';
2
+ export declare const migration: Migration;
@@ -14,6 +14,13 @@ export declare const PiWorktreeConfigSchema: import("typebox").TObject<{
14
14
  }>>>;
15
15
  matchingStrategy: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TLiteral<"fail-on-tie">, import("typebox").TLiteral<"first-wins">, import("typebox").TLiteral<"last-wins">]>>;
16
16
  logfile: import("typebox").TOptional<import("typebox").TString>;
17
+ onCreateDisplayOutputMaxLines: import("typebox").TOptional<import("typebox").TInteger>;
18
+ onCreateCmdDisplayPending: import("typebox").TOptional<import("typebox").TString>;
19
+ onCreateCmdDisplaySuccess: import("typebox").TOptional<import("typebox").TString>;
20
+ onCreateCmdDisplayError: import("typebox").TOptional<import("typebox").TString>;
21
+ onCreateCmdDisplayPendingColor: import("typebox").TOptional<import("typebox").TString>;
22
+ onCreateCmdDisplaySuccessColor: import("typebox").TOptional<import("typebox").TString>;
23
+ onCreateCmdDisplayErrorColor: import("typebox").TOptional<import("typebox").TString>;
17
24
  }>;
18
25
  export type WorktreeSettingsConfig = Static<typeof WorktreeSettingsSchema>;
19
26
  export type MatchingStrategy = Static<typeof MatchingStrategySchema>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenobius/pi-worktrees",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Worktrees extension for Pi Coding Agent",
5
5
  "author": {
6
6
  "name": "Zenobius",