@solaqua/gji 0.5.0 → 0.6.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/dist/warp.js ADDED
@@ -0,0 +1,196 @@
1
+ import { isCancel, select } from '@clack/prompts';
2
+ import { readWorktreeHealth } from './git.js';
3
+ import { isHeadless } from './headless.js';
4
+ import { appendHistory } from './history.js';
5
+ import { runNewCommand } from './new.js';
6
+ import { loadRegistry } from './repo-registry.js';
7
+ import { listWorktrees } from './repo.js';
8
+ import { writeShellOutput } from './shell-handoff.js';
9
+ const WARP_OUTPUT_FILE_ENV = 'GJI_WARP_OUTPUT_FILE';
10
+ export async function runWarpCommand(options) {
11
+ if (options.newWorktree) {
12
+ const registry = await loadRegistry();
13
+ if (registry.length === 0) {
14
+ options.stderr('gji warp: no repos registered yet.\n' +
15
+ 'Use any gji command in a repository to register it automatically.\n');
16
+ return 1;
17
+ }
18
+ return runWarpNew(options, registry);
19
+ }
20
+ return runWarpNavigate(options);
21
+ }
22
+ async function runWarpNavigate(options) {
23
+ if ((isHeadless() || options.json) && !options.branch) {
24
+ const message = 'branch argument is required';
25
+ if (options.json) {
26
+ options.stderr(`${JSON.stringify({ error: message }, null, 2)}\n`);
27
+ }
28
+ else {
29
+ options.stderr('gji warp: branch argument is required in non-interactive mode (GJI_NO_TUI=1)\n');
30
+ }
31
+ return 1;
32
+ }
33
+ const target = await resolveWarpTarget({ ...options, commandName: 'gji warp', json: options.json });
34
+ if (!target)
35
+ return 1;
36
+ if (options.json) {
37
+ // json callers use the output programmatically; skip history and shell handoff.
38
+ options.stdout(`${JSON.stringify({ branch: target.branch, path: target.path }, null, 2)}\n`);
39
+ return 0;
40
+ }
41
+ appendHistory(target.path, target.branch).catch(() => undefined);
42
+ await writeShellOutput(WARP_OUTPUT_FILE_ENV, target.path, options.stdout);
43
+ return 0;
44
+ }
45
+ async function runWarpNew(options, registry) {
46
+ let targetRepoRoot;
47
+ if (registry.length === 1) {
48
+ targetRepoRoot = registry[0].path;
49
+ }
50
+ else {
51
+ if (isHeadless()) {
52
+ options.stderr('gji warp: repo argument is required in non-interactive mode (GJI_NO_TUI=1)\n');
53
+ return 1;
54
+ }
55
+ const choice = await select({
56
+ message: 'Create worktree in which repo?',
57
+ options: registry.map((entry) => ({
58
+ value: entry.path,
59
+ label: entry.name,
60
+ hint: entry.path,
61
+ })),
62
+ });
63
+ if (isCancel(choice)) {
64
+ options.stderr('Aborted\n');
65
+ return 1;
66
+ }
67
+ targetRepoRoot = choice;
68
+ }
69
+ if (options.json) {
70
+ return runNewCommand({
71
+ branch: options.branch,
72
+ cwd: targetRepoRoot,
73
+ json: true,
74
+ stderr: options.stderr,
75
+ stdout: options.stdout,
76
+ });
77
+ }
78
+ // runNewCommand writes the created path to options.stdout via writeShellOutput.
79
+ // Since GJI_NEW_OUTPUT_FILE is not set in the warp shell context, it falls
80
+ // through to our captured stdout, giving us the path to hand off.
81
+ let capturedPath = '';
82
+ const captureStdout = (chunk) => {
83
+ capturedPath = chunk.trim();
84
+ };
85
+ const exitCode = await runNewCommand({
86
+ branch: options.branch,
87
+ cwd: targetRepoRoot,
88
+ stderr: options.stderr,
89
+ stdout: captureStdout,
90
+ });
91
+ if (exitCode !== 0) {
92
+ return exitCode;
93
+ }
94
+ if (!capturedPath) {
95
+ options.stderr('gji warp: could not determine new worktree path\n');
96
+ return 1;
97
+ }
98
+ await writeShellOutput(WARP_OUTPUT_FILE_ENV, capturedPath, options.stdout);
99
+ return 0;
100
+ }
101
+ function findByQuery(items, query) {
102
+ const slashIdx = query.indexOf('/');
103
+ if (slashIdx !== -1) {
104
+ const repoQuery = query.slice(0, slashIdx);
105
+ const branchQuery = query.slice(slashIdx + 1);
106
+ const match = items.find((item) => item.repoName === repoQuery && item.worktree.branch === branchQuery);
107
+ if (match)
108
+ return match;
109
+ }
110
+ return items.find((item) => item.worktree.branch === query) ?? null;
111
+ }
112
+ export async function resolveWarpTarget(options) {
113
+ const cmd = options.commandName ?? 'gji';
114
+ const emitError = (message, hint) => {
115
+ if (options.json) {
116
+ options.stderr(`${JSON.stringify({ error: message }, null, 2)}\n`);
117
+ }
118
+ else {
119
+ options.stderr(`${cmd}: ${message}\n`);
120
+ if (hint)
121
+ options.stderr(hint);
122
+ }
123
+ };
124
+ const registry = await loadRegistry();
125
+ if (registry.length === 0) {
126
+ emitError('not in a git repository and no repos registered yet.', 'Use any gji command inside a repository to register it.\n');
127
+ return null;
128
+ }
129
+ const results = await Promise.allSettled(registry.map(async (entry) => {
130
+ const worktrees = await listWorktrees(entry.path);
131
+ return { repoName: entry.name, worktrees };
132
+ }));
133
+ const allItems = [];
134
+ for (const result of results) {
135
+ if (result.status === 'rejected')
136
+ continue;
137
+ const { repoName, worktrees } = result.value;
138
+ for (const worktree of worktrees) {
139
+ allItems.push({ repoName, worktree });
140
+ }
141
+ }
142
+ if (allItems.length === 0) {
143
+ emitError('no accessible worktrees found in any registered repo.');
144
+ return null;
145
+ }
146
+ if (options.branch) {
147
+ const match = findByQuery(allItems, options.branch);
148
+ if (!match) {
149
+ emitError(`no worktree found matching: ${options.branch}`);
150
+ return null;
151
+ }
152
+ return { branch: match.worktree.branch, path: match.worktree.path };
153
+ }
154
+ const path = await promptForWarpTarget(allItems);
155
+ if (!path) {
156
+ options.stderr('Aborted\n');
157
+ return null;
158
+ }
159
+ const chosen = allItems.find((item) => item.worktree.path === path);
160
+ return { branch: chosen?.worktree.branch ?? null, path };
161
+ }
162
+ async function promptForWarpTarget(items) {
163
+ const healthResults = await Promise.allSettled(items.map((item) => readWorktreeHealth(item.worktree.path)));
164
+ const choice = await select({
165
+ message: 'Warp to a worktree',
166
+ options: items.map((item, i) => {
167
+ const health = healthResults[i].status === 'fulfilled' ? healthResults[i].value : null;
168
+ const upstream = health ? formatHint(item.worktree.branch, health) : null;
169
+ const label = `${item.repoName} › ${item.worktree.branch ?? '(detached)'}`;
170
+ const pathHint = item.worktree.isCurrent
171
+ ? `${item.worktree.path} (current)`
172
+ : item.worktree.path;
173
+ const hint = upstream ? `${upstream} · ${pathHint}` : pathHint;
174
+ return { hint, label, value: item.worktree.path };
175
+ }),
176
+ });
177
+ if (isCancel(choice)) {
178
+ return null;
179
+ }
180
+ return choice;
181
+ }
182
+ function formatHint(branch, health) {
183
+ if (branch === null)
184
+ return null;
185
+ if (!health.hasUpstream)
186
+ return 'no upstream';
187
+ if (health.upstreamGone)
188
+ return 'upstream gone';
189
+ if (health.ahead === 0 && health.behind === 0)
190
+ return 'up to date';
191
+ if (health.ahead === 0)
192
+ return `behind ${health.behind}`;
193
+ if (health.behind === 0)
194
+ return `ahead ${health.ahead}`;
195
+ return `ahead ${health.ahead}, behind ${health.behind}`;
196
+ }
@@ -1,4 +1,4 @@
1
- .TH GJI\-BACK 1 "May 2026" "gji 0.5.0" "User Commands"
1
+ .TH GJI\-BACK 1 "May 2026" "gji 0.6.0" "User Commands"
2
2
  .SH NAME
3
3
  gji\-back \- navigate to the previously visited worktree, optionally N steps back
4
4
  .SH SYNOPSIS
@@ -1,4 +1,4 @@
1
- .TH GJI\-CLEAN 1 "May 2026" "gji 0.5.0" "User Commands"
1
+ .TH GJI\-CLEAN 1 "May 2026" "gji 0.6.0" "User Commands"
2
2
  .SH NAME
3
3
  gji\-clean \- interactively prune linked worktrees
4
4
  .SH SYNOPSIS
@@ -1,4 +1,4 @@
1
- .TH GJI\-COMPLETION 1 "May 2026" "gji 0.5.0" "User Commands"
1
+ .TH GJI\-COMPLETION 1 "May 2026" "gji 0.6.0" "User Commands"
2
2
  .SH NAME
3
3
  gji\-completion \- print shell completion definitions
4
4
  .SH SYNOPSIS
@@ -1,4 +1,4 @@
1
- .TH GJI\-CONFIG 1 "May 2026" "gji 0.5.0" "User Commands"
1
+ .TH GJI\-CONFIG 1 "May 2026" "gji 0.6.0" "User Commands"
2
2
  .SH NAME
3
3
  gji\-config \- manage global config defaults
4
4
  .SH SYNOPSIS
package/man/man1/gji-go.1 CHANGED
@@ -1,4 +1,4 @@
1
- .TH GJI\-GO 1 "May 2026" "gji 0.5.0" "User Commands"
1
+ .TH GJI\-GO 1 "May 2026" "gji 0.6.0" "User Commands"
2
2
  .SH NAME
3
3
  gji\-go \- print or select a worktree path
4
4
  .SH SYNOPSIS
@@ -1,4 +1,4 @@
1
- .TH GJI\-HISTORY 1 "May 2026" "gji 0.5.0" "User Commands"
1
+ .TH GJI\-HISTORY 1 "May 2026" "gji 0.6.0" "User Commands"
2
2
  .SH NAME
3
3
  gji\-history \- show navigation history
4
4
  .SH SYNOPSIS
@@ -1,4 +1,4 @@
1
- .TH GJI\-INIT 1 "May 2026" "gji 0.5.0" "User Commands"
1
+ .TH GJI\-INIT 1 "May 2026" "gji 0.6.0" "User Commands"
2
2
  .SH NAME
3
3
  gji\-init \- print or install shell integration
4
4
  .SH SYNOPSIS
package/man/man1/gji-ls.1 CHANGED
@@ -1,4 +1,4 @@
1
- .TH GJI\-LS 1 "May 2026" "gji 0.5.0" "User Commands"
1
+ .TH GJI\-LS 1 "May 2026" "gji 0.6.0" "User Commands"
2
2
  .SH NAME
3
3
  gji\-ls \- list active worktrees
4
4
  .SH SYNOPSIS
@@ -1,4 +1,4 @@
1
- .TH GJI\-NEW 1 "May 2026" "gji 0.5.0" "User Commands"
1
+ .TH GJI\-NEW 1 "May 2026" "gji 0.6.0" "User Commands"
2
2
  .SH NAME
3
3
  gji\-new \- create a new branch or detached linked worktree
4
4
  .SH SYNOPSIS
@@ -13,6 +13,12 @@ remove and recreate the worktree if the target path already exists
13
13
  .B \-\-detached
14
14
  create a detached worktree without a branch
15
15
  .TP
16
+ .B \-\-open
17
+ open the new worktree in an editor after creation
18
+ .TP
19
+ .B \-\-editor <cli>
20
+ editor CLI to use with \-\-open (code, cursor, zed, …)
21
+ .TP
16
22
  .B \-\-dry\-run
17
23
  show what would be created without executing any git commands or writing files
18
24
  .TP
@@ -0,0 +1,19 @@
1
+ .TH GJI\-OPEN 1 "May 2026" "gji 0.6.0" "User Commands"
2
+ .SH NAME
3
+ gji\-open \- open the worktree in an editor
4
+ .SH SYNOPSIS
5
+ .B gji open [\fIoptions\fR] [options] [branch]
6
+ .SH DESCRIPTION
7
+ open the worktree in an editor
8
+ .SH OPTIONS
9
+ .TP
10
+ .B \-\-editor <cli>
11
+ editor CLI to use (code, cursor, zed, windsurf, subl, …)
12
+ .TP
13
+ .B \-\-save
14
+ save the chosen editor to global config
15
+ .TP
16
+ .B \-\-workspace
17
+ generate a .code\-workspace file before opening (VS Code / Cursor / Windsurf)
18
+ .SH "SEE ALSO"
19
+ .BR gji (1)
package/man/man1/gji-pr.1 CHANGED
@@ -1,4 +1,4 @@
1
- .TH GJI\-PR 1 "May 2026" "gji 0.5.0" "User Commands"
1
+ .TH GJI\-PR 1 "May 2026" "gji 0.6.0" "User Commands"
2
2
  .SH NAME
3
3
  gji\-pr \- fetch a pull request by number, #number, or URL into a linked worktree
4
4
  .SH SYNOPSIS
@@ -1,4 +1,4 @@
1
- .TH GJI\-REMOVE 1 "May 2026" "gji 0.5.0" "User Commands"
1
+ .TH GJI\-REMOVE 1 "May 2026" "gji 0.6.0" "User Commands"
2
2
  .SH NAME
3
3
  gji\-remove \- remove a linked worktree and delete its branch when present
4
4
  .SH SYNOPSIS
@@ -1,4 +1,4 @@
1
- .TH GJI\-ROOT 1 "May 2026" "gji 0.5.0" "User Commands"
1
+ .TH GJI\-ROOT 1 "May 2026" "gji 0.6.0" "User Commands"
2
2
  .SH NAME
3
3
  gji\-root \- print the main repository root path
4
4
  .SH SYNOPSIS
@@ -1,4 +1,4 @@
1
- .TH GJI\-STATUS 1 "May 2026" "gji 0.5.0" "User Commands"
1
+ .TH GJI\-STATUS 1 "May 2026" "gji 0.6.0" "User Commands"
2
2
  .SH NAME
3
3
  gji\-status \- summarize repository and worktree health
4
4
  .SH SYNOPSIS
@@ -1,4 +1,4 @@
1
- .TH GJI\-SYNC 1 "May 2026" "gji 0.5.0" "User Commands"
1
+ .TH GJI\-SYNC 1 "May 2026" "gji 0.6.0" "User Commands"
2
2
  .SH NAME
3
3
  gji\-sync \- fetch and update one or all worktrees
4
4
  .SH SYNOPSIS
@@ -1,4 +1,4 @@
1
- .TH GJI\-TRIGGER\-HOOK 1 "May 2026" "gji 0.5.0" "User Commands"
1
+ .TH GJI\-TRIGGER\-HOOK 1 "May 2026" "gji 0.6.0" "User Commands"
2
2
  .SH NAME
3
3
  gji\-trigger\-hook \- run a named hook (afterCreate, afterEnter, beforeRemove) in the current worktree
4
4
  .SH SYNOPSIS
@@ -0,0 +1,19 @@
1
+ .TH GJI\-WARP 1 "May 2026" "gji 0.6.0" "User Commands"
2
+ .SH NAME
3
+ gji\-warp \- jump to any worktree across all known repos
4
+ .SH SYNOPSIS
5
+ .B gji warp [\fIoptions\fR] [options] [branch]
6
+ .SH DESCRIPTION
7
+ jump to any worktree across all known repos
8
+ .SH OPTIONS
9
+ .TP
10
+ .B \-n, \-\-new [branch]
11
+ create a new worktree in a registered repo
12
+ .TP
13
+ .B \-\-print
14
+ print the resolved worktree path without changing directory
15
+ .TP
16
+ .B \-\-json
17
+ emit JSON on success or error instead of human\-readable output
18
+ .SH "SEE ALSO"
19
+ .BR gji (1)
package/man/man1/gji.1 CHANGED
@@ -1,4 +1,4 @@
1
- .TH GJI 1 "May 2026" "gji 0.5.0" "User Commands"
1
+ .TH GJI 1 "May 2026" "gji 0.6.0" "User Commands"
2
2
  .SH NAME
3
3
  gji \- Context switching without the mess.
4
4
  .SH SYNOPSIS
@@ -29,8 +29,13 @@ navigate to the previously visited worktree, optionally N steps back
29
29
  .B history [options]
30
30
  show navigation history
31
31
  .TP
32
+ .B open [options] [branch]
33
+ open the worktree in an editor
34
+ .TP
32
35
  .B go [options] [branch]
33
36
  print or select a worktree path
37
+ .br
38
+ Alias: \fBjump\fR
34
39
  .TP
35
40
  .B root [options]
36
41
  print the main repository root path
@@ -55,6 +60,9 @@ Alias: \fBrm\fR
55
60
  .B trigger\-hook [options] <hook>
56
61
  run a named hook (afterCreate, afterEnter, beforeRemove) in the current worktree
57
62
  .TP
63
+ .B warp [options] [branch]
64
+ jump to any worktree across all known repos
65
+ .TP
58
66
  .B config [options] [command]
59
67
  manage global config defaults
60
68
  .SH OPTIONS
@@ -68,6 +76,7 @@ output the version number
68
76
  .BR gji\-pr (1),
69
77
  .BR gji\-back (1),
70
78
  .BR gji\-history (1),
79
+ .BR gji\-open (1),
71
80
  .BR gji\-go (1),
72
81
  .BR gji\-root (1),
73
82
  .BR gji\-status (1),
@@ -76,4 +85,5 @@ output the version number
76
85
  .BR gji\-clean (1),
77
86
  .BR gji\-remove (1),
78
87
  .BR gji\-trigger\-hook (1),
88
+ .BR gji\-warp (1),
79
89
  .BR gji\-config (1)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solaqua/gji",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Git worktree CLI for fast context switching.",
5
5
  "license": "MIT",
6
6
  "author": "sjquant",
@@ -64,6 +64,8 @@
64
64
  "vitest": "^3.2.4"
65
65
  },
66
66
  "pnpm": {
67
- "onlyBuiltDependencies": ["esbuild"]
67
+ "onlyBuiltDependencies": [
68
+ "esbuild"
69
+ ]
68
70
  }
69
71
  }