@forceuser/git-profile-switcher 0.1.4

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.
@@ -0,0 +1,183 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
3
+ import { dirname } from "node:path";
4
+ import { normalizeDirectoryPath } from "#config/paths";
5
+ import { createEmptyProfileStore, PROFILE_PROMPT_COLORS, } from "./types.js";
6
+ export function createProfileRepository(path) {
7
+ async function read() {
8
+ try {
9
+ const raw = await readFile(path, "utf8");
10
+ const parsed = JSON.parse(raw);
11
+ return normalizeStore(parsed);
12
+ }
13
+ catch (error) {
14
+ if (isNotFoundError(error)) {
15
+ return createEmptyProfileStore();
16
+ }
17
+ throw error;
18
+ }
19
+ }
20
+ async function save(data) {
21
+ await mkdir(dirname(path), { recursive: true });
22
+ await writeFile(path, `${JSON.stringify(normalizeStore(data), null, 2)}\n`, {
23
+ mode: 0o600,
24
+ });
25
+ }
26
+ return {
27
+ read,
28
+ save,
29
+ async upsertProfile(input) {
30
+ const data = await read();
31
+ const now = new Date().toISOString();
32
+ const existing = data.profiles.find((profile) => profile.name === input.name);
33
+ if (existing) {
34
+ existing.userName = input.userName;
35
+ existing.userEmail = input.userEmail;
36
+ if (input.promptColor !== undefined) {
37
+ existing.promptColor = input.promptColor;
38
+ }
39
+ existing.updatedAt = now;
40
+ await save(data);
41
+ return existing;
42
+ }
43
+ const profile = {
44
+ name: input.name,
45
+ userName: input.userName,
46
+ userEmail: input.userEmail,
47
+ ...(input.promptColor ? { promptColor: input.promptColor } : {}),
48
+ createdAt: now,
49
+ updatedAt: now,
50
+ };
51
+ data.profiles.push(profile);
52
+ sortStore(data);
53
+ await save(data);
54
+ return profile;
55
+ },
56
+ async setProfilePromptColor(input) {
57
+ const data = await read();
58
+ const profile = data.profiles.find((candidate) => candidate.name === input.name);
59
+ if (!profile) {
60
+ throw new Error(`Unknown profile: ${input.name}`);
61
+ }
62
+ if (input.promptColor) {
63
+ profile.promptColor = input.promptColor;
64
+ }
65
+ else {
66
+ delete profile.promptColor;
67
+ }
68
+ profile.updatedAt = new Date().toISOString();
69
+ await save(data);
70
+ return profile;
71
+ },
72
+ async removeProfile(name) {
73
+ const data = await read();
74
+ const nextProfiles = data.profiles.filter((profile) => profile.name !== name);
75
+ if (nextProfiles.length === data.profiles.length) {
76
+ return false;
77
+ }
78
+ data.profiles = nextProfiles;
79
+ data.rules = data.rules.filter((rule) => rule.profileName !== name);
80
+ await save(data);
81
+ return true;
82
+ },
83
+ async addRule(input) {
84
+ const data = await read();
85
+ if (!data.profiles.some((profile) => profile.name === input.profileName)) {
86
+ throw new Error(`Unknown profile: ${input.profileName}`);
87
+ }
88
+ const directory = normalizeDirectoryPath(input.directory, input.homeDir);
89
+ const existing = data.rules.find((rule) => rule.profileName === input.profileName && rule.directory === directory);
90
+ if (existing) {
91
+ return existing;
92
+ }
93
+ const rule = {
94
+ id: `rule_${randomUUID().replaceAll("-", "").slice(0, 12)}`,
95
+ profileName: input.profileName,
96
+ directory,
97
+ createdAt: new Date().toISOString(),
98
+ };
99
+ data.rules.push(rule);
100
+ sortStore(data);
101
+ await save(data);
102
+ return rule;
103
+ },
104
+ async setDirectoryProfile(input) {
105
+ const data = await read();
106
+ if (!data.profiles.some((profile) => profile.name === input.profileName)) {
107
+ throw new Error(`Unknown profile: ${input.profileName}`);
108
+ }
109
+ const directory = normalizeDirectoryPath(input.directory, input.homeDir);
110
+ const existing = data.rules.find((rule) => rule.profileName === input.profileName && rule.directory === directory);
111
+ if (existing) {
112
+ data.rules = data.rules.filter((rule) => rule.directory !== directory || rule.id === existing.id);
113
+ sortStore(data);
114
+ await save(data);
115
+ return existing;
116
+ }
117
+ const rule = {
118
+ id: `rule_${randomUUID().replaceAll("-", "").slice(0, 12)}`,
119
+ profileName: input.profileName,
120
+ directory,
121
+ createdAt: new Date().toISOString(),
122
+ };
123
+ data.rules = [...data.rules.filter((candidate) => candidate.directory !== directory), rule];
124
+ sortStore(data);
125
+ await save(data);
126
+ return rule;
127
+ },
128
+ async clearDirectoryProfile(input) {
129
+ const data = await read();
130
+ const directory = normalizeDirectoryPath(input.directory, input.homeDir);
131
+ const removed = data.rules.filter((rule) => rule.directory === directory);
132
+ if (removed.length === 0) {
133
+ return [];
134
+ }
135
+ data.rules = data.rules.filter((rule) => rule.directory !== directory);
136
+ await save(data);
137
+ return removed;
138
+ },
139
+ async removeRule(id) {
140
+ const data = await read();
141
+ const nextRules = data.rules.filter((rule) => rule.id !== id);
142
+ if (nextRules.length === data.rules.length) {
143
+ return false;
144
+ }
145
+ data.rules = nextRules;
146
+ await save(data);
147
+ return true;
148
+ },
149
+ };
150
+ }
151
+ function normalizeStore(input) {
152
+ const data = {
153
+ version: 1,
154
+ profiles: Array.isArray(input.profiles) ? input.profiles.map(normalizeProfile) : [],
155
+ rules: Array.isArray(input.rules) ? input.rules : [],
156
+ };
157
+ sortStore(data);
158
+ return data;
159
+ }
160
+ function normalizeProfile(profile) {
161
+ const { promptColor, ...profileWithoutPromptColor } = profile;
162
+ const normalizedPromptColor = normalizePromptColor(promptColor);
163
+ return {
164
+ ...profileWithoutPromptColor,
165
+ ...(normalizedPromptColor ? { promptColor: normalizedPromptColor } : {}),
166
+ };
167
+ }
168
+ export function normalizePromptColor(value) {
169
+ if (typeof value !== "string") {
170
+ return null;
171
+ }
172
+ const normalized = value.trim().toLowerCase();
173
+ return PROFILE_PROMPT_COLORS.includes(normalized)
174
+ ? normalized
175
+ : null;
176
+ }
177
+ function sortStore(data) {
178
+ data.profiles.sort((left, right) => left.name.localeCompare(right.name));
179
+ data.rules.sort((left, right) => left.directory.localeCompare(right.directory));
180
+ }
181
+ function isNotFoundError(error) {
182
+ return error instanceof Error && "code" in error && error.code === "ENOENT";
183
+ }
@@ -0,0 +1,111 @@
1
+ import { createEmptyProfileStore, PROFILE_PROMPT_COLORS, } from "#profiles/types";
2
+ export function createProfileExportBundle(data) {
3
+ return {
4
+ kind: "git-profile-switcher/profile-store",
5
+ version: 1,
6
+ exportedAt: new Date().toISOString(),
7
+ data: normalizeProfileStoreData(data),
8
+ };
9
+ }
10
+ export function parseProfileImportBundle(input) {
11
+ if (isRecord(input) && input.kind === "git-profile-switcher/profile-store") {
12
+ if (input.version !== 1) {
13
+ throw new Error(`Unsupported export bundle version: ${String(input.version)}`);
14
+ }
15
+ return normalizeProfileStoreData(input.data);
16
+ }
17
+ return normalizeProfileStoreData(input);
18
+ }
19
+ export function mergeProfileStoreData(current, incoming) {
20
+ const next = createEmptyProfileStore();
21
+ const profiles = new Map();
22
+ for (const profile of current.profiles) {
23
+ profiles.set(profile.name, profile);
24
+ }
25
+ for (const profile of incoming.profiles) {
26
+ profiles.set(profile.name, profile);
27
+ }
28
+ next.profiles = [...profiles.values()];
29
+ const knownProfiles = new Set(next.profiles.map((profile) => profile.name));
30
+ const rules = new Map();
31
+ for (const rule of [...current.rules, ...incoming.rules]) {
32
+ if (!knownProfiles.has(rule.profileName)) {
33
+ continue;
34
+ }
35
+ rules.set(rule.id, rule);
36
+ }
37
+ next.rules = [...rules.values()];
38
+ return sortProfileStoreData(next);
39
+ }
40
+ export function normalizeProfileStoreData(input) {
41
+ if (!isRecord(input)) {
42
+ throw new Error("Invalid profile store: expected an object.");
43
+ }
44
+ const version = input.version;
45
+ if (version !== 1) {
46
+ throw new Error(`Unsupported profile store version: ${String(version)}`);
47
+ }
48
+ const profiles = readArray(input.profiles, "profiles").map(parseProfile);
49
+ const profileNames = new Set(profiles.map((profile) => profile.name));
50
+ const rules = readArray(input.rules, "rules")
51
+ .map(parseRule)
52
+ .filter((rule) => profileNames.has(rule.profileName));
53
+ return sortProfileStoreData({ version: 1, profiles, rules });
54
+ }
55
+ function parseProfile(input) {
56
+ if (!isRecord(input)) {
57
+ throw new Error("Invalid profile: expected an object.");
58
+ }
59
+ return {
60
+ name: readString(input.name, "profile.name"),
61
+ userName: readString(input.userName, "profile.userName"),
62
+ userEmail: readString(input.userEmail, "profile.userEmail"),
63
+ ...readOptionalPromptColor(input.promptColor),
64
+ createdAt: readString(input.createdAt, "profile.createdAt"),
65
+ updatedAt: readString(input.updatedAt, "profile.updatedAt"),
66
+ };
67
+ }
68
+ function parseRule(input) {
69
+ if (!isRecord(input)) {
70
+ throw new Error("Invalid rule: expected an object.");
71
+ }
72
+ return {
73
+ id: readString(input.id, "rule.id"),
74
+ profileName: readString(input.profileName, "rule.profileName"),
75
+ directory: readString(input.directory, "rule.directory"),
76
+ createdAt: readString(input.createdAt, "rule.createdAt"),
77
+ };
78
+ }
79
+ function sortProfileStoreData(data) {
80
+ data.profiles.sort((left, right) => left.name.localeCompare(right.name));
81
+ data.rules.sort((left, right) => left.directory.localeCompare(right.directory));
82
+ return data;
83
+ }
84
+ function readArray(value, field) {
85
+ if (!Array.isArray(value)) {
86
+ throw new Error(`Invalid profile store: ${field} must be an array.`);
87
+ }
88
+ return value;
89
+ }
90
+ function readString(value, field) {
91
+ if (typeof value !== "string" || value.length === 0) {
92
+ throw new Error(`Invalid profile store: ${field} must be a non-empty string.`);
93
+ }
94
+ return value;
95
+ }
96
+ function readOptionalPromptColor(value) {
97
+ if (value === undefined || value === null || value === "") {
98
+ return {};
99
+ }
100
+ if (typeof value !== "string") {
101
+ throw new Error("Invalid profile store: profile.promptColor must be a string.");
102
+ }
103
+ const normalized = value.trim().toLowerCase();
104
+ if (!PROFILE_PROMPT_COLORS.includes(normalized)) {
105
+ throw new Error(`Invalid profile store: unsupported profile.promptColor ${value}.`);
106
+ }
107
+ return { promptColor: normalized };
108
+ }
109
+ function isRecord(value) {
110
+ return typeof value === "object" && value !== null;
111
+ }
@@ -0,0 +1,23 @@
1
+ export const PROFILE_PROMPT_COLOR_CODES = {
2
+ red: "31",
3
+ green: "32",
4
+ yellow: "33",
5
+ blue: "34",
6
+ magenta: "35",
7
+ cyan: "36",
8
+ gray: "90",
9
+ "bright-red": "91",
10
+ "bright-green": "92",
11
+ "bright-yellow": "93",
12
+ "bright-blue": "94",
13
+ "bright-magenta": "95",
14
+ "bright-cyan": "96",
15
+ };
16
+ export const PROFILE_PROMPT_COLORS = Object.keys(PROFILE_PROMPT_COLOR_CODES);
17
+ export function createEmptyProfileStore() {
18
+ return {
19
+ version: 1,
20
+ profiles: [],
21
+ rules: [],
22
+ };
23
+ }
@@ -0,0 +1,103 @@
1
+ import { resolve } from "node:path";
2
+ import { findMatchingRule, readActiveGitIdentity } from "#git/config";
3
+ import { PROFILE_PROMPT_COLOR_CODES, } from "#profiles/types";
4
+ export function getPromptStatus(input) {
5
+ const cwd = resolve(input.cwd ?? process.cwd());
6
+ const sessionIdentity = readSessionGitIdentity(input.env ?? process.env);
7
+ if (sessionIdentity.userName || sessionIdentity.userEmail || sessionIdentity.profileName) {
8
+ const sessionProfileByName = sessionIdentity.profileName
9
+ ? findProfileByName(input.data.profiles, sessionIdentity.profileName)
10
+ : null;
11
+ const sessionProfileByIdentity = findProfileByIdentity(input.data.profiles, {
12
+ userName: sessionIdentity.userName,
13
+ userEmail: sessionIdentity.userEmail,
14
+ });
15
+ return {
16
+ cwd,
17
+ profileName: sessionProfileByName?.name ??
18
+ sessionProfileByIdentity?.name ??
19
+ sessionIdentity.profileName ??
20
+ null,
21
+ profilePromptColor: sessionProfileByName?.promptColor ?? sessionProfileByIdentity?.promptColor ?? null,
22
+ userName: sessionIdentity.userName,
23
+ userEmail: sessionIdentity.userEmail,
24
+ ruleId: null,
25
+ directory: null,
26
+ };
27
+ }
28
+ const rule = findMatchingRule(input.data, cwd);
29
+ const gitIdentity = readActiveGitIdentity(cwd);
30
+ const ruleProfile = rule ? findProfileByName(input.data.profiles, rule.profileName) : null;
31
+ const identityProfile = findProfileByIdentity(input.data.profiles, {
32
+ userName: gitIdentity.userName,
33
+ userEmail: gitIdentity.userEmail,
34
+ });
35
+ return {
36
+ cwd,
37
+ profileName: ruleProfile?.name ?? rule?.profileName ?? identityProfile?.name ?? null,
38
+ profilePromptColor: ruleProfile?.promptColor ?? identityProfile?.promptColor ?? null,
39
+ userName: ruleProfile?.userName ?? gitIdentity.userName ?? null,
40
+ userEmail: ruleProfile?.userEmail ?? gitIdentity.userEmail ?? null,
41
+ ruleId: rule?.id ?? null,
42
+ directory: rule?.directory ?? null,
43
+ };
44
+ }
45
+ function readSessionGitIdentity(env) {
46
+ return {
47
+ profileName: env.GIP_PROFILE_NAME || null,
48
+ userName: env.GIT_AUTHOR_NAME || env.GIT_COMMITTER_NAME || null,
49
+ userEmail: env.GIT_AUTHOR_EMAIL || env.GIT_COMMITTER_EMAIL || null,
50
+ };
51
+ }
52
+ export function renderPromptStatus(status, format = "auto", shell = null) {
53
+ if (format === "profile") {
54
+ return renderProfilePrompt(status, shell);
55
+ }
56
+ if (format === "auto" && status.profileName) {
57
+ return renderProfilePrompt(status, shell);
58
+ }
59
+ if (format === "auto") {
60
+ return renderIdentityPrompt(status);
61
+ }
62
+ return renderIdentityPrompt(status);
63
+ }
64
+ function renderIdentityPrompt(status) {
65
+ if (!status.userName && !status.userEmail) {
66
+ return "";
67
+ }
68
+ if (status.userName && status.userEmail) {
69
+ return `${status.userName} <${status.userEmail}>`;
70
+ }
71
+ return status.userName ?? status.userEmail ?? "";
72
+ }
73
+ function renderProfilePrompt(status, shell) {
74
+ if (!status.profileName) {
75
+ return "";
76
+ }
77
+ const profileName = status.profilePromptColor
78
+ ? applyPromptColor(status.profileName, status.profilePromptColor, shell)
79
+ : status.profileName;
80
+ return `[gip ${profileName}]`;
81
+ }
82
+ function applyPromptColor(value, promptColor, shell) {
83
+ if (!shell) {
84
+ return value;
85
+ }
86
+ const code = PROFILE_PROMPT_COLOR_CODES[promptColor];
87
+ if (shell === "bash") {
88
+ return `\u0001\x1b[${code}m\u0002${value}\u0001\x1b[0m\u0002`;
89
+ }
90
+ if (shell === "zsh") {
91
+ return `%{\x1b[${code}m%}${value}%{\x1b[0m%}`;
92
+ }
93
+ return `\x1b[${code}m${value}\x1b[0m`;
94
+ }
95
+ function findProfileByName(profiles, name) {
96
+ return profiles.find((profile) => profile.name === name) ?? null;
97
+ }
98
+ function findProfileByIdentity(profiles, identity) {
99
+ if (!identity.userName || !identity.userEmail) {
100
+ return null;
101
+ }
102
+ return (profiles.find((profile) => profile.userName === identity.userName && profile.userEmail === identity.userEmail) ?? null);
103
+ }
@@ -0,0 +1,254 @@
1
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import { dirname, join, resolve } from "node:path";
4
+ import { generateCompletionScript } from "#cli/completion";
5
+ const PROMPT_BLOCK_START = "# >>> gip prompt >>>";
6
+ const PROMPT_BLOCK_END = "# <<< gip prompt <<<";
7
+ const COMPLETION_BLOCK_START = "# >>> gip completion >>>";
8
+ const COMPLETION_BLOCK_END = "# <<< gip completion <<<";
9
+ const SHELL_BLOCK_START = "# >>> gip shell >>>";
10
+ const SHELL_BLOCK_END = "# <<< gip shell <<<";
11
+ const PROMPT_BLOCK_PATTERN = new RegExp(`${escapeRegExp(PROMPT_BLOCK_START)}\\n[\\s\\S]*?\\n${escapeRegExp(PROMPT_BLOCK_END)}\\n?`, "g");
12
+ const COMPLETION_BLOCK_PATTERN = new RegExp(`${escapeRegExp(COMPLETION_BLOCK_START)}\\n[\\s\\S]*?\\n${escapeRegExp(COMPLETION_BLOCK_END)}\\n?`, "g");
13
+ const SHELL_BLOCK_PATTERN = new RegExp(`${escapeRegExp(SHELL_BLOCK_START)}\\n[\\s\\S]*?\\n${escapeRegExp(SHELL_BLOCK_END)}\\n?`, "g");
14
+ export async function installShellPrompt(input) {
15
+ const path = resolve(input.configPath ?? getDefaultShellConfigPath(input.shell));
16
+ const current = await readOptionalText(path);
17
+ const next = appendManagedBlock(removeShellPromptBlock(current), renderPromptBlock(input.shell, input.promptFormat));
18
+ return await writeIfChanged(path, current, next);
19
+ }
20
+ export async function uninstallShellPrompt(input) {
21
+ const path = resolve(input.configPath ?? getDefaultShellConfigPath(input.shell));
22
+ const current = await readOptionalText(path);
23
+ return await writeIfChanged(path, current, removeShellPromptBlock(current));
24
+ }
25
+ export async function installShellCompletion(input) {
26
+ const path = resolve(input.configPath ?? getDefaultShellConfigPath(input.shell));
27
+ const current = await readOptionalText(path);
28
+ const next = appendManagedBlock(removeShellCompletionBlock(current), renderCompletionBlock(input.shell));
29
+ return await writeIfChanged(path, current, next);
30
+ }
31
+ export async function uninstallShellCompletion(input) {
32
+ const path = resolve(input.configPath ?? getDefaultShellConfigPath(input.shell));
33
+ const current = await readOptionalText(path);
34
+ return await writeIfChanged(path, current, removeShellCompletionBlock(current));
35
+ }
36
+ export async function installShellSession(input) {
37
+ const path = resolve(input.configPath ?? getDefaultShellConfigPath(input.shell));
38
+ const current = await readOptionalText(path);
39
+ const next = appendManagedBlock(removeShellSessionBlock(current), renderShellSessionBlock(input.shell));
40
+ return await writeIfChanged(path, current, next);
41
+ }
42
+ export async function uninstallShellSession(input) {
43
+ const path = resolve(input.configPath ?? getDefaultShellConfigPath(input.shell));
44
+ const current = await readOptionalText(path);
45
+ return await writeIfChanged(path, current, removeShellSessionBlock(current));
46
+ }
47
+ export async function installShellAll(input) {
48
+ const path = resolve(input.configPath ?? getDefaultShellConfigPath(input.shell));
49
+ const current = await readOptionalText(path);
50
+ const cleaned = removeShellSessionBlock(removeShellPromptBlock(removeShellCompletionBlock(current)));
51
+ const next = appendManagedBlock(appendManagedBlock(appendManagedBlock(cleaned, renderCompletionBlock(input.shell)), renderShellSessionBlock(input.shell)), renderPromptBlock(input.shell, input.promptFormat));
52
+ return await writeIfChanged(path, current, next);
53
+ }
54
+ export async function uninstallShellAll(input) {
55
+ const path = resolve(input.configPath ?? getDefaultShellConfigPath(input.shell));
56
+ const current = await readOptionalText(path);
57
+ const next = removeShellSessionBlock(removeShellPromptBlock(removeShellCompletionBlock(current)));
58
+ return await writeIfChanged(path, current, next);
59
+ }
60
+ export function detectShell(env = process.env) {
61
+ const shell = env.SHELL?.toLowerCase() ?? "";
62
+ if (shell.endsWith("/zsh") || env.ZSH_VERSION) {
63
+ return "zsh";
64
+ }
65
+ if (shell.endsWith("/bash") || env.BASH_VERSION) {
66
+ return "bash";
67
+ }
68
+ if (shell.endsWith("/fish") || env.FISH_VERSION) {
69
+ return "fish";
70
+ }
71
+ return null;
72
+ }
73
+ export function renderPromptBlock(shell, promptFormat = "auto") {
74
+ const promptCommand = renderPromptCommand(promptFormat);
75
+ if (shell === "fish") {
76
+ return `${PROMPT_BLOCK_START}
77
+ if functions -q fish_prompt; and not functions -q __gip_original_fish_prompt
78
+ functions -c fish_prompt __gip_original_fish_prompt
79
+ end
80
+
81
+ function fish_prompt
82
+ set -lx GIP_PROMPT_SHELL fish
83
+ set -l gip_segment (${promptCommand} 2>/dev/null)
84
+ set -l gip_original_prompt
85
+ if functions -q __gip_original_fish_prompt
86
+ set gip_original_prompt (__gip_original_fish_prompt | string collect)
87
+ end
88
+
89
+ if test -n "$gip_segment"
90
+ if test -n "$gip_original_prompt"; and string match -q "*\\n*" "$gip_original_prompt"
91
+ printf "%s " "$gip_segment"
92
+ else
93
+ printf "%s\\n" "$gip_segment"
94
+ end
95
+ end
96
+
97
+ printf "%s" "$gip_original_prompt"
98
+ end
99
+ ${PROMPT_BLOCK_END}
100
+ `;
101
+ }
102
+ if (shell === "zsh") {
103
+ return `${PROMPT_BLOCK_START}
104
+ setopt prompt_subst
105
+
106
+ _gip_prompt_segment() {
107
+ local gip_segment
108
+ gip_segment="$(GIP_PROMPT_SHELL=zsh ${promptCommand} 2>/dev/null)" || return
109
+ if [ -n "$gip_segment" ]; then
110
+ case "$GIP_ORIGINAL_PROMPT" in
111
+ *$'\\n'*|*'\\n'*)
112
+ printf '%s ' "$gip_segment"
113
+ ;;
114
+ *)
115
+ printf '%s\\n%%{%%}' "$gip_segment"
116
+ ;;
117
+ esac
118
+ fi
119
+ }
120
+
121
+ if [ -z "\${GIP_ORIGINAL_PROMPT+x}" ]; then
122
+ GIP_ORIGINAL_PROMPT="$PROMPT"
123
+ fi
124
+
125
+ PROMPT='$(_gip_prompt_segment)'"\${GIP_ORIGINAL_PROMPT}"
126
+ ${PROMPT_BLOCK_END}
127
+ `;
128
+ }
129
+ return `${PROMPT_BLOCK_START}
130
+ _gip_prompt_segment() {
131
+ local gip_segment
132
+ if [ -n "\${GIP_PROMPT_DEBUG_LOG:-}" ]; then
133
+ gip_segment="$(GIP_PROMPT_SHELL=bash ${promptCommand} 2>>"\${GIP_PROMPT_DEBUG_LOG}")" || return
134
+ else
135
+ gip_segment="$(GIP_PROMPT_SHELL=bash ${promptCommand} 2>/dev/null)" || return
136
+ fi
137
+ if [ -n "$gip_segment" ]; then
138
+ case "$GIP_ORIGINAL_PS1" in
139
+ *$'\\n'*|*'\\n'*)
140
+ printf '%s ' "$gip_segment"
141
+ ;;
142
+ *)
143
+ printf '%s\\n\\001\\002' "$gip_segment"
144
+ ;;
145
+ esac
146
+ fi
147
+ }
148
+
149
+ if [ -z "\${GIP_ORIGINAL_PS1+x}" ]; then
150
+ GIP_ORIGINAL_PS1="$PS1"
151
+ fi
152
+
153
+ PS1='$(_gip_prompt_segment)'"\${GIP_ORIGINAL_PS1}"
154
+ ${PROMPT_BLOCK_END}
155
+ `;
156
+ }
157
+ export function renderCompletionBlock(shell) {
158
+ return `${COMPLETION_BLOCK_START}
159
+ ${generateCompletionScript(shell).trimEnd()}
160
+ ${COMPLETION_BLOCK_END}
161
+ `;
162
+ }
163
+ export function renderShellSessionBlock(shell) {
164
+ return `${SHELL_BLOCK_START}
165
+ ${renderSessionCommandWrapper(shell).trimEnd()}
166
+ ${SHELL_BLOCK_END}
167
+ `;
168
+ }
169
+ function renderSessionCommandWrapper(shell) {
170
+ if (shell === "fish") {
171
+ return `function gip
172
+ if test (count $argv) -gt 0; and test "$argv[1]" = now; and not contains -- --help $argv; and not contains -- -h $argv
173
+ command gip $argv --exports --shell fish | source
174
+ else
175
+ command gip $argv
176
+ end
177
+ end
178
+ `;
179
+ }
180
+ const shellName = shell === "zsh" ? "zsh" : "bash";
181
+ return `gip() {
182
+ if [ "\${1:-}" = "now" ]; then
183
+ local gip_arg
184
+ for gip_arg in "$@"; do
185
+ case "$gip_arg" in
186
+ --help|-h)
187
+ command gip "$@"
188
+ return
189
+ ;;
190
+ esac
191
+ done
192
+ local gip_session
193
+ gip_session="$(command gip "$@" --exports --shell ${shellName})" || return
194
+ eval "$gip_session"
195
+ else
196
+ command gip "$@"
197
+ fi
198
+ }
199
+ `;
200
+ }
201
+ function renderPromptCommand(promptFormat) {
202
+ if (promptFormat === "auto") {
203
+ return "gip prompt";
204
+ }
205
+ return `gip prompt --format ${promptFormat}`;
206
+ }
207
+ export function removeShellPromptBlock(text) {
208
+ return text.replace(PROMPT_BLOCK_PATTERN, "").trimEnd();
209
+ }
210
+ export function removeShellCompletionBlock(text) {
211
+ return text.replace(COMPLETION_BLOCK_PATTERN, "").trimEnd();
212
+ }
213
+ export function removeShellSessionBlock(text) {
214
+ return text.replace(SHELL_BLOCK_PATTERN, "").trimEnd();
215
+ }
216
+ export function getDefaultShellConfigPath(shell) {
217
+ const home = homedir();
218
+ if (shell === "zsh") {
219
+ return join(home, ".zshrc");
220
+ }
221
+ if (shell === "bash") {
222
+ return join(home, ".bashrc");
223
+ }
224
+ return join(home, ".config", "fish", "config.fish");
225
+ }
226
+ function appendManagedBlock(text, block) {
227
+ const trimmed = text.trimEnd();
228
+ if (!trimmed) {
229
+ return block;
230
+ }
231
+ return `${trimmed}\n\n${block}`;
232
+ }
233
+ async function writeIfChanged(path, current, next) {
234
+ if (next === current) {
235
+ return { path, changed: false };
236
+ }
237
+ await mkdir(dirname(path), { recursive: true });
238
+ await writeFile(path, next);
239
+ return { path, changed: true };
240
+ }
241
+ async function readOptionalText(path) {
242
+ try {
243
+ return await readFile(path, "utf8");
244
+ }
245
+ catch (error) {
246
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
247
+ return "";
248
+ }
249
+ throw error;
250
+ }
251
+ }
252
+ function escapeRegExp(value) {
253
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
254
+ }