@solaqua/gji 0.1.0-beta.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,201 @@
1
+ # gji
2
+
3
+ Context switching without the mess.
4
+
5
+ `gji` is a Git worktree CLI for people who jump between tasks all day. It gives each branch or PR its own directory, so you stop doing `stash`, `pop`, reinstall cycles, and fragile branch juggling.
6
+
7
+ ## Why
8
+
9
+ Standard branch switching gets annoying when you are:
10
+
11
+ - fixing one bug while reviewing another branch
12
+ - hopping between feature work and PR checks
13
+ - using multiple terminals, editors, or AI agents at the same time
14
+
15
+ `gji` keeps those contexts isolated in separate worktrees with deterministic paths.
16
+
17
+ ## Install
18
+
19
+ Install from npm:
20
+
21
+ ```sh
22
+ npm install -g @solaqua/gji
23
+ ```
24
+
25
+ Confirm the CLI is available:
26
+
27
+ ```sh
28
+ gji --help
29
+ ```
30
+
31
+ The installed command is still:
32
+
33
+ ```sh
34
+ gji
35
+ ```
36
+
37
+ ## Quick start
38
+
39
+ Inside a Git repository:
40
+
41
+ ```sh
42
+ gji new feature/login-form
43
+ gji go feature/login-form
44
+ gji status
45
+ ```
46
+
47
+ That creates a linked worktree at a deterministic path:
48
+
49
+ ```text
50
+ ../worktrees/<repo>/<branch>
51
+ ```
52
+
53
+ ## Shell setup
54
+
55
+ `gji go` can only change your current directory when shell integration is installed. Without shell integration, the raw CLI prints the target path so it stays script-friendly.
56
+
57
+ For zsh:
58
+
59
+ ```sh
60
+ echo 'eval "$(gji init zsh)"' >> ~/.zshrc
61
+ source ~/.zshrc
62
+ ```
63
+
64
+ After that:
65
+
66
+ ```sh
67
+ gji go feature/login-form
68
+ ```
69
+
70
+ changes your shell directory directly.
71
+
72
+ For scripts or explicit piping:
73
+
74
+ ```sh
75
+ gji go --print feature/login-form
76
+ ```
77
+
78
+ ## Daily workflow
79
+
80
+ Start a task:
81
+
82
+ ```sh
83
+ gji new feature/refactor-auth
84
+ gji go feature/refactor-auth
85
+ ```
86
+
87
+ Check what is active:
88
+
89
+ ```sh
90
+ gji status
91
+ gji ls
92
+ ```
93
+
94
+ Pull a PR into its own worktree:
95
+
96
+ ```sh
97
+ gji pr 123
98
+ ```
99
+
100
+ Sync the current worktree with the latest default branch:
101
+
102
+ ```sh
103
+ gji sync
104
+ ```
105
+
106
+ Sync every worktree in the repository:
107
+
108
+ ```sh
109
+ gji sync --all
110
+ ```
111
+
112
+ Clean up stale linked worktrees interactively:
113
+
114
+ ```sh
115
+ gji clean
116
+ ```
117
+
118
+ Finish a single worktree explicitly:
119
+
120
+ ```sh
121
+ gji remove feature/refactor-auth
122
+ ```
123
+
124
+ ## Commands
125
+
126
+ - `gji init [shell]` prints shell integration for `zsh`, `bash`, or `fish`
127
+ - `gji new [branch]` creates a branch and linked worktree; when omitted, it prompts with a placeholder branch name
128
+ - `gji pr <number>` fetches `origin/pull/<number>/head` and creates a linked `pr/<number>` worktree
129
+ - `gji go [branch]` jumps to an existing worktree when shell integration is installed, or prints the matching worktree path otherwise
130
+ - `gji root` prints the main repository root path from either the repo root or a linked worktree
131
+ - `gji status [--json]` prints repository metadata, worktree health, and upstream divergence
132
+ - `gji sync [--all]` fetches from the configured remote and rebases or fast-forwards worktrees onto the configured default branch
133
+ - `gji ls [--json]` lists active worktrees in a table or JSON
134
+ - `gji clean` interactively prunes one or more linked worktrees, including detached entries, while excluding the current worktree
135
+ - `gji remove [branch]` removes a linked worktree and deletes its branch when present
136
+ - `gji config` reads or updates global defaults
137
+
138
+ ## Configuration
139
+
140
+ `gji` is usable without setup, but it supports defaults through:
141
+
142
+ - global config at `~/.config/gji/config.json`
143
+ - repo-local config at `.gji.json`
144
+
145
+ Repo-local values override global defaults.
146
+
147
+ Supported keys:
148
+
149
+ - `branchPrefix`
150
+ - `syncRemote`
151
+ - `syncDefaultBranch`
152
+
153
+ Example:
154
+
155
+ ```json
156
+ {
157
+ "branchPrefix": "feature/",
158
+ "syncRemote": "upstream",
159
+ "syncDefaultBranch": "main"
160
+ }
161
+ ```
162
+
163
+ Behavior:
164
+
165
+ - if `syncRemote` is unset, `gji sync` defaults to `origin`
166
+ - if `syncDefaultBranch` is unset, `gji sync` resolves the remote default branch from `HEAD`
167
+
168
+ ## JSON output
169
+
170
+ `gji ls --json` returns branch/path entries:
171
+
172
+ ```sh
173
+ gji ls --json
174
+ ```
175
+
176
+ `gji status --json` returns a top-level object with:
177
+
178
+ - `repoRoot`
179
+ - `currentRoot`
180
+ - `worktrees`
181
+
182
+ Each worktree entry contains:
183
+
184
+ - `branch`: branch name or `null` for detached worktrees
185
+ - `current`
186
+ - `path`
187
+ - `status`: `clean` or `dirty`
188
+ - `upstream`: one of
189
+ - `{ "kind": "detached" }`
190
+ - `{ "kind": "no-upstream" }`
191
+ - `{ "kind": "tracked", "ahead": number, "behind": number }`
192
+
193
+ ## Notes
194
+
195
+ - `gji` works from either the main repository root or any linked worktree
196
+ - the current worktree is never offered as a `gji clean` removal candidate
197
+ - `gji` currently uses GitHub-style PR refs for `gji pr`
198
+
199
+ ## License
200
+
201
+ MIT
@@ -0,0 +1,12 @@
1
+ import type { WorktreeEntry } from './repo.js';
2
+ export interface CleanCommandOptions {
3
+ cwd: string;
4
+ stderr: (chunk: string) => void;
5
+ stdout: (chunk: string) => void;
6
+ }
7
+ export interface CleanCommandDependencies {
8
+ confirmRemoval: (worktrees: WorktreeEntry[]) => Promise<boolean>;
9
+ promptForWorktrees: (worktrees: WorktreeEntry[]) => Promise<string[] | null>;
10
+ }
11
+ export declare function createCleanCommand(dependencies?: Partial<CleanCommandDependencies>): (options: CleanCommandOptions) => Promise<number>;
12
+ export declare const runCleanCommand: (options: CleanCommandOptions) => Promise<number>;
package/dist/clean.js ADDED
@@ -0,0 +1,80 @@
1
+ import { confirm, isCancel, multiselect } from '@clack/prompts';
2
+ import { deleteBranch, loadLinkedWorktrees, removeWorktree, } from './worktree-management.js';
3
+ export function createCleanCommand(dependencies = {}) {
4
+ const promptForWorktrees = dependencies.promptForWorktrees ?? defaultPromptForWorktrees;
5
+ const confirmRemoval = dependencies.confirmRemoval ?? defaultConfirmRemoval;
6
+ return async function runCleanCommand(options) {
7
+ const { linkedWorktrees, repository } = await loadLinkedWorktrees(options.cwd);
8
+ const cleanupCandidates = linkedWorktrees.filter((worktree) => worktree.path !== repository.currentRoot);
9
+ if (cleanupCandidates.length === 0) {
10
+ options.stderr('No linked worktrees to clean\n');
11
+ return 1;
12
+ }
13
+ const selections = await promptForWorktrees(cleanupCandidates);
14
+ if (!selections || selections.length === 0) {
15
+ options.stderr('Aborted\n');
16
+ return 1;
17
+ }
18
+ const selectedWorktrees = resolveSelectedWorktrees(cleanupCandidates, selections);
19
+ if (selectedWorktrees.length !== selections.length) {
20
+ options.stderr('Selected worktree no longer exists\n');
21
+ return 1;
22
+ }
23
+ if (!(await confirmRemoval(selectedWorktrees))) {
24
+ options.stderr('Aborted\n');
25
+ return 1;
26
+ }
27
+ for (const worktree of selectedWorktrees) {
28
+ await removeWorktree(repository.repoRoot, worktree.path);
29
+ if (worktree.branch) {
30
+ await deleteBranch(repository.repoRoot, worktree.branch);
31
+ }
32
+ }
33
+ options.stdout(`${repository.repoRoot}\n`);
34
+ return 0;
35
+ };
36
+ }
37
+ export const runCleanCommand = createCleanCommand();
38
+ function resolveSelectedWorktrees(worktrees, selections) {
39
+ const selectedWorktrees = [];
40
+ const seenPaths = new Set();
41
+ for (const selection of selections) {
42
+ const worktree = worktrees.find((entry) => entry.path === selection || entry.branch === selection);
43
+ if (!worktree || seenPaths.has(worktree.path)) {
44
+ continue;
45
+ }
46
+ selectedWorktrees.push(worktree);
47
+ seenPaths.add(worktree.path);
48
+ }
49
+ return selectedWorktrees;
50
+ }
51
+ async function defaultPromptForWorktrees(worktrees) {
52
+ const choice = await multiselect({
53
+ message: 'Choose worktrees to clean',
54
+ options: worktrees.map((worktree) => ({
55
+ hint: worktree.path,
56
+ label: worktree.branch ?? '(detached)',
57
+ value: worktree.path,
58
+ })),
59
+ required: true,
60
+ });
61
+ return isCancel(choice) ? null : choice;
62
+ }
63
+ async function defaultConfirmRemoval(worktrees) {
64
+ const branchCount = worktrees.filter((worktree) => worktree.branch !== null).length;
65
+ const detachedCount = worktrees.length - branchCount;
66
+ const messageParts = [`Remove ${worktrees.length} linked worktree${worktrees.length === 1 ? '' : 's'}`];
67
+ if (branchCount > 0) {
68
+ messageParts.push(`delete ${branchCount} branch${branchCount === 1 ? '' : 'es'}`);
69
+ }
70
+ if (detachedCount > 0) {
71
+ messageParts.push(`remove ${detachedCount} detached worktree${detachedCount === 1 ? '' : 's'}`);
72
+ }
73
+ const choice = await confirm({
74
+ active: 'Yes',
75
+ inactive: 'No',
76
+ initialValue: true,
77
+ message: `${messageParts.join(', ')}?`,
78
+ });
79
+ return !isCancel(choice) && choice;
80
+ }
package/dist/cli.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { Command } from 'commander';
2
+ export interface RunCliOptions {
3
+ cwd?: string;
4
+ stderr?: (chunk: string) => void;
5
+ stdout?: (chunk: string) => void;
6
+ }
7
+ export interface RunCliResult {
8
+ exitCode: number;
9
+ }
10
+ export declare function createProgram(): Command;
11
+ export declare function runCli(argv: string[], options?: RunCliOptions): Promise<RunCliResult>;
package/dist/cli.js ADDED
@@ -0,0 +1,294 @@
1
+ import { Command } from 'commander';
2
+ import { runCleanCommand } from './clean.js';
3
+ import { runConfigCommand } from './config-command.js';
4
+ import { runGoCommand } from './go.js';
5
+ import { runInitCommand } from './init.js';
6
+ import { runLsCommand } from './ls.js';
7
+ import { runNewCommand } from './new.js';
8
+ import { runPrCommand } from './pr.js';
9
+ import { runRemoveCommand } from './remove.js';
10
+ import { runRootCommand } from './root.js';
11
+ import { runStatusCommand } from './status.js';
12
+ import { runSyncCommand } from './sync.js';
13
+ export function createProgram() {
14
+ const program = new Command();
15
+ program
16
+ .name('gji')
17
+ .description('Context switching without the mess.')
18
+ .showHelpAfterError()
19
+ .showSuggestionAfterError();
20
+ registerCommands(program);
21
+ return program;
22
+ }
23
+ export async function runCli(argv, options = {}) {
24
+ const program = createProgram();
25
+ const cwd = options.cwd ?? process.cwd();
26
+ const stdout = options.stdout ?? (() => undefined);
27
+ const stderr = options.stderr ?? (() => undefined);
28
+ program.configureOutput({
29
+ writeErr: stderr,
30
+ writeOut: stdout,
31
+ });
32
+ program.exitOverride();
33
+ if (argv.length === 0) {
34
+ program.outputHelp();
35
+ return { exitCode: 0 };
36
+ }
37
+ try {
38
+ attachCommandActions(program, { cwd, stderr, stdout });
39
+ await program.parseAsync(['node', 'gji', ...argv], { from: 'node' });
40
+ return { exitCode: 0 };
41
+ }
42
+ catch (error) {
43
+ if (isCommanderExit(error)) {
44
+ return { exitCode: error.exitCode };
45
+ }
46
+ throw error;
47
+ }
48
+ }
49
+ function registerCommands(program) {
50
+ program
51
+ .command('new [branch]')
52
+ .description('create a new branch and linked worktree')
53
+ .action(notImplemented('new'));
54
+ program
55
+ .command('init [shell]')
56
+ .description('print or install shell integration')
57
+ .option('--write', 'write the integration to the shell config file')
58
+ .action(notImplemented('init'));
59
+ program
60
+ .command('pr <number>')
61
+ .description('fetch a pull request ref and create a linked worktree')
62
+ .action(notImplemented('pr'));
63
+ program
64
+ .command('go [branch]')
65
+ .description('print or select a worktree path')
66
+ .option('--print', 'print the resolved worktree path explicitly')
67
+ .action(notImplemented('go'));
68
+ program
69
+ .command('root')
70
+ .description('print the main repository root path')
71
+ .action(notImplemented('root'));
72
+ program
73
+ .command('status')
74
+ .description('summarize repository and worktree health')
75
+ .option('--json', 'print repository and worktree health as JSON')
76
+ .action(notImplemented('status'));
77
+ program
78
+ .command('sync')
79
+ .description('fetch and update one or all worktrees')
80
+ .option('--all', 'sync every worktree in the repository')
81
+ .action(notImplemented('sync'));
82
+ program
83
+ .command('ls')
84
+ .description('list active worktrees')
85
+ .option('--json', 'print active worktrees as JSON')
86
+ .action(notImplemented('ls'));
87
+ program
88
+ .command('clean')
89
+ .description('interactively prune linked worktrees')
90
+ .action(notImplemented('clean'));
91
+ program
92
+ .command('remove [branch]')
93
+ .description('remove a linked worktree and delete its branch when present')
94
+ .action(notImplemented('remove'));
95
+ const configCommand = program
96
+ .command('config')
97
+ .description('manage global config defaults')
98
+ .action(notImplemented('config'));
99
+ configCommand
100
+ .command('get [key]')
101
+ .description('print the global config or a single key')
102
+ .action(notImplemented('config get'));
103
+ configCommand
104
+ .command('set <key> <value>')
105
+ .description('set a global config value')
106
+ .action(notImplemented('config set'));
107
+ configCommand
108
+ .command('unset <key>')
109
+ .description('remove a global config value')
110
+ .action(notImplemented('config unset'));
111
+ }
112
+ function attachCommandActions(program, options) {
113
+ program.commands
114
+ .find((command) => command.name() === 'new')
115
+ ?.action(async (branch) => {
116
+ const exitCode = await runNewCommand({ ...options, branch });
117
+ if (exitCode !== 0) {
118
+ throw commanderExit(exitCode);
119
+ }
120
+ });
121
+ program.commands
122
+ .find((command) => command.name() === 'init')
123
+ ?.action(async (shell, commandOptions) => {
124
+ const exitCode = await runInitCommand({
125
+ cwd: options.cwd,
126
+ shell,
127
+ stdout: options.stdout,
128
+ write: commandOptions.write,
129
+ });
130
+ if (exitCode !== 0) {
131
+ throw commanderExit(exitCode);
132
+ }
133
+ });
134
+ program.commands
135
+ .find((command) => command.name() === 'pr')
136
+ ?.action(async (number) => {
137
+ const exitCode = await runPrCommand({ cwd: options.cwd, number, stdout: options.stdout });
138
+ if (exitCode !== 0) {
139
+ throw commanderExit(exitCode);
140
+ }
141
+ });
142
+ program.commands
143
+ .find((command) => command.name() === 'go')
144
+ ?.action(async (branch, commandOptions) => {
145
+ const exitCode = await runGoCommand({
146
+ branch,
147
+ cwd: options.cwd,
148
+ print: commandOptions.print,
149
+ stderr: options.stderr,
150
+ stdout: options.stdout,
151
+ });
152
+ if (exitCode !== 0) {
153
+ throw commanderExit(exitCode);
154
+ }
155
+ });
156
+ program.commands
157
+ .find((command) => command.name() === 'root')
158
+ ?.action(async () => {
159
+ const exitCode = await runRootCommand({
160
+ cwd: options.cwd,
161
+ stdout: options.stdout,
162
+ });
163
+ if (exitCode !== 0) {
164
+ throw commanderExit(exitCode);
165
+ }
166
+ });
167
+ program.commands
168
+ .find((command) => command.name() === 'status')
169
+ ?.action(async (commandOptions) => {
170
+ const exitCode = await runStatusCommand({
171
+ cwd: options.cwd,
172
+ json: commandOptions.json,
173
+ stdout: options.stdout,
174
+ });
175
+ if (exitCode !== 0) {
176
+ throw commanderExit(exitCode);
177
+ }
178
+ });
179
+ program.commands
180
+ .find((command) => command.name() === 'sync')
181
+ ?.action(async (commandOptions) => {
182
+ const exitCode = await runSyncCommand({
183
+ all: commandOptions.all,
184
+ cwd: options.cwd,
185
+ stderr: options.stderr,
186
+ stdout: options.stdout,
187
+ });
188
+ if (exitCode !== 0) {
189
+ throw commanderExit(exitCode);
190
+ }
191
+ });
192
+ program.commands
193
+ .find((command) => command.name() === 'ls')
194
+ ?.action(async (commandOptions) => {
195
+ const exitCode = await runLsCommand({
196
+ cwd: options.cwd,
197
+ json: commandOptions.json,
198
+ stdout: options.stdout,
199
+ });
200
+ if (exitCode !== 0) {
201
+ throw commanderExit(exitCode);
202
+ }
203
+ });
204
+ program.commands
205
+ .find((command) => command.name() === 'clean')
206
+ ?.action(async () => {
207
+ const exitCode = await runCleanCommand({
208
+ cwd: options.cwd,
209
+ stderr: options.stderr,
210
+ stdout: options.stdout,
211
+ });
212
+ if (exitCode !== 0) {
213
+ throw commanderExit(exitCode);
214
+ }
215
+ });
216
+ const runRemovalCommand = async (branch) => {
217
+ const exitCode = await runRemoveCommand({
218
+ branch,
219
+ cwd: options.cwd,
220
+ stderr: options.stderr,
221
+ stdout: options.stdout,
222
+ });
223
+ if (exitCode !== 0) {
224
+ throw commanderExit(exitCode);
225
+ }
226
+ };
227
+ program.commands
228
+ .find((command) => command.name() === 'remove')
229
+ ?.action(runRemovalCommand);
230
+ const configCommand = program.commands.find((command) => command.name() === 'config');
231
+ configCommand?.action(async () => {
232
+ const exitCode = await runConfigCommand({
233
+ cwd: options.cwd,
234
+ stdout: options.stdout,
235
+ });
236
+ if (exitCode !== 0) {
237
+ throw commanderExit(exitCode);
238
+ }
239
+ });
240
+ configCommand?.commands.find((command) => command.name() === 'get')?.action(async (key) => {
241
+ const exitCode = await runConfigCommand({
242
+ action: 'get',
243
+ cwd: options.cwd,
244
+ key,
245
+ stdout: options.stdout,
246
+ });
247
+ if (exitCode !== 0) {
248
+ throw commanderExit(exitCode);
249
+ }
250
+ });
251
+ configCommand?.commands
252
+ .find((command) => command.name() === 'set')
253
+ ?.action(async (key, value) => {
254
+ const exitCode = await runConfigCommand({
255
+ action: 'set',
256
+ cwd: options.cwd,
257
+ key,
258
+ stdout: options.stdout,
259
+ value,
260
+ });
261
+ if (exitCode !== 0) {
262
+ throw commanderExit(exitCode);
263
+ }
264
+ });
265
+ configCommand?.commands.find((command) => command.name() === 'unset')?.action(async (key) => {
266
+ const exitCode = await runConfigCommand({
267
+ action: 'unset',
268
+ cwd: options.cwd,
269
+ key,
270
+ stdout: options.stdout,
271
+ });
272
+ if (exitCode !== 0) {
273
+ throw commanderExit(exitCode);
274
+ }
275
+ });
276
+ }
277
+ function notImplemented(commandName) {
278
+ return () => {
279
+ throw new Error(`'${commandName}' is not implemented yet.`);
280
+ };
281
+ }
282
+ function commanderExit(exitCode) {
283
+ const error = new Error(`Command exited with code ${exitCode}.`);
284
+ error.code = 'commander.executeSubCommandAsync';
285
+ error.exitCode = exitCode;
286
+ return error;
287
+ }
288
+ function isCommanderExit(error) {
289
+ return (error instanceof Error &&
290
+ 'code' in error &&
291
+ 'exitCode' in error &&
292
+ typeof error.code === 'string' &&
293
+ typeof error.exitCode === 'number');
294
+ }
@@ -0,0 +1,8 @@
1
+ export interface ConfigCommandOptions {
2
+ action?: string;
3
+ cwd: string;
4
+ key?: string;
5
+ stdout: (chunk: string) => void;
6
+ value?: string;
7
+ }
8
+ export declare function runConfigCommand(options: ConfigCommandOptions): Promise<number>;
@@ -0,0 +1,31 @@
1
+ import { loadGlobalConfig, parseConfigValue, unsetGlobalConfigKey, updateGlobalConfigKey, } from './config.js';
2
+ export async function runConfigCommand(options) {
3
+ switch (options.action) {
4
+ case undefined: {
5
+ const loaded = await loadGlobalConfig();
6
+ writeJson(options.stdout, loaded.config);
7
+ return 0;
8
+ }
9
+ case 'get': {
10
+ const loaded = await loadGlobalConfig();
11
+ writeJson(options.stdout, options.key ? loaded.config[options.key] : loaded.config);
12
+ return 0;
13
+ }
14
+ case 'set':
15
+ if (options.key && options.value !== undefined) {
16
+ await updateGlobalConfigKey(options.key, parseConfigValue(options.value));
17
+ return 0;
18
+ }
19
+ break;
20
+ case 'unset':
21
+ if (options.key) {
22
+ await unsetGlobalConfigKey(options.key);
23
+ return 0;
24
+ }
25
+ break;
26
+ }
27
+ throw new Error(`Invalid config arguments: ${[options.action, options.key, options.value].filter(Boolean).join(' ')}`);
28
+ }
29
+ function writeJson(stdout, value) {
30
+ stdout(`${JSON.stringify(value, null, 2)}\n`);
31
+ }
@@ -0,0 +1,18 @@
1
+ export declare const CONFIG_FILE_NAME = ".gji.json";
2
+ export declare const GLOBAL_CONFIG_DIRECTORY = ".config/gji";
3
+ export declare const GLOBAL_CONFIG_NAME = "config.json";
4
+ export type GjiConfig = Record<string, unknown>;
5
+ export interface LoadedConfig {
6
+ config: GjiConfig;
7
+ exists: boolean;
8
+ path: string;
9
+ }
10
+ export declare const DEFAULT_CONFIG: GjiConfig;
11
+ export declare function loadConfig(root: string): Promise<LoadedConfig>;
12
+ export declare function loadEffectiveConfig(root: string, home?: string): Promise<GjiConfig>;
13
+ export declare function loadGlobalConfig(home?: string): Promise<LoadedConfig>;
14
+ export declare function saveGlobalConfig(config: GjiConfig, home?: string): Promise<string>;
15
+ export declare function unsetGlobalConfigKey(key: string, home?: string): Promise<GjiConfig>;
16
+ export declare function updateGlobalConfigKey(key: string, value: unknown, home?: string): Promise<GjiConfig>;
17
+ export declare function GLOBAL_CONFIG_FILE_PATH(home?: string): string;
18
+ export declare function parseConfigValue(value: string): unknown;