offrouter-adapter-gemini 0.2.0 → 0.2.2

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,12 @@
1
+ /**
2
+ * offrouter-adapter-gemini public surface.
3
+ * Install / rollback for Gemini personal profiles (MCP config entry).
4
+ */
5
+ export declare const ADAPTER_GEMINI_PACKAGE: "offrouter-adapter-gemini";
6
+ export { isGeminiHarnessProfile, isSafeProfileId, isWorkLikeProfile, listAllowlistedPersonalGeminiProfiles, resolveGeminiProfileDir, SAFE_PROFILE_ID_RE, } from "./profile.js";
7
+ export type { ResolveGeminiProfileDirOptions } from "./profile.js";
8
+ export { GEMINI_CONFIG_REL, GEMINI_INSTALL_STATE_REL, GeminiInstallStateSchema, evaluateInstallProfilePolicy, formatInstallDiffText, installGeminiProfile, } from "./install.js";
9
+ export type { GeminiInstallOptions, GeminiInstallResult, GeminiInstallState, InstallChangeKind, InstallFileChange, } from "./install.js";
10
+ export { rollbackGeminiProfile } from "./rollback.js";
11
+ export type { GeminiRollbackOptions, GeminiRollbackResult, } from "./rollback.js";
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,eAAO,MAAM,sBAAsB,EAAG,0BAAmC,CAAC;AAE1E,OAAO,EACL,sBAAsB,EACtB,eAAe,EACf,iBAAiB,EACjB,qCAAqC,EACrC,uBAAuB,EACvB,kBAAkB,GACnB,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,8BAA8B,EAAE,MAAM,cAAc,CAAC;AAEnE,OAAO,EACL,iBAAiB,EACjB,wBAAwB,EACxB,wBAAwB,EACxB,4BAA4B,EAC5B,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,cAAc,CAAC;AACtB,YAAY,EACV,oBAAoB,EACpB,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACtD,YAAY,EACV,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,eAAe,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * offrouter-adapter-gemini public surface.
3
+ * Install / rollback for Gemini personal profiles (MCP config entry).
4
+ */
5
+ export const ADAPTER_GEMINI_PACKAGE = "offrouter-adapter-gemini";
6
+ export { isGeminiHarnessProfile, isSafeProfileId, isWorkLikeProfile, listAllowlistedPersonalGeminiProfiles, resolveGeminiProfileDir, SAFE_PROFILE_ID_RE, } from "./profile.js";
7
+ export { GEMINI_CONFIG_REL, GEMINI_INSTALL_STATE_REL, GeminiInstallStateSchema, evaluateInstallProfilePolicy, formatInstallDiffText, installGeminiProfile, } from "./install.js";
8
+ export { rollbackGeminiProfile } from "./rollback.js";
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,CAAC,MAAM,sBAAsB,GAAG,0BAAmC,CAAC;AAE1E,OAAO,EACL,sBAAsB,EACtB,eAAe,EACf,iBAAiB,EACjB,qCAAqC,EACrC,uBAAuB,EACvB,kBAAkB,GACnB,MAAM,cAAc,CAAC;AAGtB,OAAO,EACL,iBAAiB,EACjB,wBAAwB,EACxB,wBAAwB,EACxB,4BAA4B,EAC5B,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,cAAc,CAAC;AAStB,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,96 @@
1
+ import { type OffRouterConfig, type PolicyDenialCode } from "offrouter-core";
2
+ import { z } from "zod";
3
+ export declare const GEMINI_INSTALL_STATE_REL: ".offrouter/gemini-install-state.json";
4
+ export declare const GEMINI_CONFIG_REL: "settings.json";
5
+ export declare const GeminiInstallStateSchema: z.ZodObject<{
6
+ version: z.ZodLiteral<1>;
7
+ profile: z.ZodString;
8
+ harness: z.ZodLiteral<"gemini">;
9
+ installedAt: z.ZodString;
10
+ configRelative: z.ZodLiteral<"settings.json">;
11
+ files: z.ZodRecord<z.ZodString, z.ZodObject<{
12
+ previous: z.ZodNullable<z.ZodString>;
13
+ hash: z.ZodOptional<z.ZodString>;
14
+ }, "strip", z.ZodTypeAny, {
15
+ previous: string | null;
16
+ hash?: string | undefined;
17
+ }, {
18
+ previous: string | null;
19
+ hash?: string | undefined;
20
+ }>>;
21
+ }, "strip", z.ZodTypeAny, {
22
+ version: 1;
23
+ profile: string;
24
+ harness: "gemini";
25
+ installedAt: string;
26
+ configRelative: "settings.json";
27
+ files: Record<string, {
28
+ previous: string | null;
29
+ hash?: string | undefined;
30
+ }>;
31
+ }, {
32
+ version: 1;
33
+ profile: string;
34
+ harness: "gemini";
35
+ installedAt: string;
36
+ configRelative: "settings.json";
37
+ files: Record<string, {
38
+ previous: string | null;
39
+ hash?: string | undefined;
40
+ }>;
41
+ }>;
42
+ export type GeminiInstallState = z.infer<typeof GeminiInstallStateSchema>;
43
+ export type InstallChangeKind = "create" | "update" | "unchanged" | "remove";
44
+ export interface InstallFileChange {
45
+ path: string;
46
+ relativePath: string;
47
+ kind: InstallChangeKind;
48
+ /** Diff-like summary line for human output. */
49
+ summary: string;
50
+ }
51
+ export interface GeminiInstallOptions {
52
+ profile: string;
53
+ config: OffRouterConfig;
54
+ env?: NodeJS.ProcessEnv;
55
+ homedir?: () => string;
56
+ dryRun?: boolean;
57
+ }
58
+ export interface GeminiInstallResult {
59
+ ok: boolean;
60
+ dryRun: boolean;
61
+ profile: string;
62
+ profileDir: string;
63
+ changes: InstallFileChange[];
64
+ message: string;
65
+ errorCode?: PolicyDenialCode | "install_error";
66
+ statePath?: string;
67
+ }
68
+ /**
69
+ * Produce the desired settings.json content for the OffRouter MCP entry
70
+ * using a native JSON parser instead of ad hoc string surgery.
71
+ *
72
+ * - Missing/empty/invalid file => fresh config containing only the MCP entry.
73
+ * - Existing file already has mcpServers.offrouter with matching
74
+ * command/args => return the original bytes unchanged (idempotent).
75
+ * - Existing file has a conflicting offrouter entry (or none) => set/replace
76
+ * the entry and stringify, preserving all other unrelated keys.
77
+ */
78
+ export declare function patchConfigForMcp(existing: string | null): string;
79
+ /**
80
+ * Use routeRequest with empty candidates so work/unallowlisted profile denials
81
+ * reuse the same core policy codes users see from route explain / hooks.
82
+ */
83
+ export declare function evaluateInstallProfilePolicy(profile: string, config: OffRouterConfig): {
84
+ allowed: boolean;
85
+ errorCode?: PolicyDenialCode;
86
+ message?: string;
87
+ };
88
+ /**
89
+ * Install the OffRouter Gemini MCP entry into a single profile directory.
90
+ */
91
+ export declare function installGeminiProfile(options: GeminiInstallOptions): Promise<GeminiInstallResult>;
92
+ /**
93
+ * Human-readable diff-like text for an install/uninstall result matrix.
94
+ */
95
+ export declare function formatInstallDiffText(results: GeminiInstallResult[], headline?: string): string;
96
+ //# sourceMappingURL=install.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../src/install.ts"],"names":[],"mappings":"AAgBA,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,gBAAgB,EAEtB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAQxB,eAAO,MAAM,wBAAwB,EACnC,sCAA+C,CAAC;AAElD,eAAO,MAAM,iBAAiB,EAAG,eAAwB,CAAC;AAQ1D,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAanC,CAAC;AAEH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,QAAQ,GAAG,WAAW,GAAG,QAAQ,CAAC;AAE7E,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,iBAAiB,CAAC;IACxB,+CAA+C;IAC/C,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,eAAe,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,iBAAiB,EAAE,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,gBAAgB,GAAG,eAAe,CAAC;IAC/C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA8BD;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAiCjE;AA+DD;;;GAGG;AACH,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,eAAe,GACtB;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAuCtE;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,mBAAmB,CAAC,CA2K9B;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,mBAAmB,EAAE,EAC9B,QAAQ,CAAC,EAAE,MAAM,GAChB,MAAM,CA4BR"}
@@ -0,0 +1,374 @@
1
+ /**
2
+ * Profile-scoped installer for Gemini personal profiles.
3
+ *
4
+ * Mirrors the Codex adapter's safety model but writes ONE MCP server entry
5
+ * into the profile's JSON settings file (profile-scoped) at
6
+ * <profileDir>/settings.json and records rollback metadata inside the target
7
+ * profile (.offrouter/gemini-install-state.json).
8
+ *
9
+ * Gemini CLI settings.json uses native JSON (parsed with JSON, not ad hoc
10
+ * string surgery). MCP servers live under the top-level `mcpServers` key.
11
+ */
12
+ import { access, mkdir, readFile, writeFile } from "node:fs/promises";
13
+ import { constants } from "node:fs";
14
+ import { createHash } from "node:crypto";
15
+ import { dirname, join } from "node:path";
16
+ import { homedir as osHomedir } from "node:os";
17
+ import { ROUTE_PROTOCOL_VERSION, routeRequest, } from "offrouter-core";
18
+ import { z } from "zod";
19
+ import { isGeminiHarnessProfile, isSafeProfileId, isWorkLikeProfile, resolveGeminiProfileDir, } from "./profile.js";
20
+ export const GEMINI_INSTALL_STATE_REL = ".offrouter/gemini-install-state.json";
21
+ export const GEMINI_CONFIG_REL = "settings.json";
22
+ /** Key name and command/args for the OffRouter MCP server entry. */
23
+ const OFFROUTER_MCP_SERVER_NAME = "offrouter";
24
+ const OFFROUTER_MCP_SERVERS_KEY = "mcpServers";
25
+ const OFFROUTER_MCP_COMMAND = "offrouter";
26
+ const OFFROUTER_MCP_ARGS = ["mcp", "serve"];
27
+ export const GeminiInstallStateSchema = z.object({
28
+ version: z.literal(1),
29
+ profile: z.string().min(1),
30
+ harness: z.literal("gemini"),
31
+ installedAt: z.string().min(1),
32
+ configRelative: z.literal(GEMINI_CONFIG_REL),
33
+ files: z.record(z.string().min(1), z.object({
34
+ previous: z.string().nullable(),
35
+ hash: z.string().optional(),
36
+ })),
37
+ });
38
+ async function pathExists(path) {
39
+ try {
40
+ await access(path, constants.F_OK);
41
+ return true;
42
+ }
43
+ catch {
44
+ return false;
45
+ }
46
+ }
47
+ function sha256(content) {
48
+ return createHash("sha256").update(content).digest("hex");
49
+ }
50
+ function changeSummary(kind, relativePath) {
51
+ switch (kind) {
52
+ case "create":
53
+ return `+ ${relativePath}`;
54
+ case "update":
55
+ return `~ ${relativePath}`;
56
+ case "unchanged":
57
+ return `= ${relativePath}`;
58
+ case "remove":
59
+ return `- ${relativePath}`;
60
+ default:
61
+ return `? ${relativePath}`;
62
+ }
63
+ }
64
+ /**
65
+ * Produce the desired settings.json content for the OffRouter MCP entry
66
+ * using a native JSON parser instead of ad hoc string surgery.
67
+ *
68
+ * - Missing/empty/invalid file => fresh config containing only the MCP entry.
69
+ * - Existing file already has mcpServers.offrouter with matching
70
+ * command/args => return the original bytes unchanged (idempotent).
71
+ * - Existing file has a conflicting offrouter entry (or none) => set/replace
72
+ * the entry and stringify, preserving all other unrelated keys.
73
+ */
74
+ export function patchConfigForMcp(existing) {
75
+ if (existing === null || existing.trim() === "") {
76
+ return stringifyConfig(freshOffrouterConfig());
77
+ }
78
+ let root;
79
+ try {
80
+ const parsed = JSON.parse(existing);
81
+ root = isJsonObject(parsed) ? parsed : {};
82
+ }
83
+ catch {
84
+ // Invalid JSON: treat as empty so install can still add the entry.
85
+ return stringifyConfig(freshOffrouterConfig());
86
+ }
87
+ const servers = isJsonObject(root[OFFROUTER_MCP_SERVERS_KEY])
88
+ ? root[OFFROUTER_MCP_SERVERS_KEY]
89
+ : undefined;
90
+ if (servers !== undefined &&
91
+ isOffrouterMcpEntry(servers[OFFROUTER_MCP_SERVER_NAME])) {
92
+ return existing;
93
+ }
94
+ const nextRoot = {
95
+ ...root,
96
+ [OFFROUTER_MCP_SERVERS_KEY]: {
97
+ ...(servers ?? {}),
98
+ [OFFROUTER_MCP_SERVER_NAME]: offrouterMcpEntry(),
99
+ },
100
+ };
101
+ return stringifyConfig(nextRoot);
102
+ }
103
+ function stringifyConfig(root) {
104
+ return `${JSON.stringify(root, null, 2)}\n`;
105
+ }
106
+ function isJsonObject(value) {
107
+ return typeof value === "object" && value !== null && !Array.isArray(value);
108
+ }
109
+ /**
110
+ * True when an existing mcpServers.offrouter entry already matches the
111
+ * OffRouter command/args. Detects a correct prior install by value (not by
112
+ * string equality) so a conflicting entry is overwritten.
113
+ */
114
+ function isOffrouterMcpEntry(value) {
115
+ if (!isJsonObject(value)) {
116
+ return false;
117
+ }
118
+ if (value.command !== OFFROUTER_MCP_COMMAND) {
119
+ return false;
120
+ }
121
+ const args = value.args;
122
+ if (!Array.isArray(args) || args.length !== OFFROUTER_MCP_ARGS.length) {
123
+ return false;
124
+ }
125
+ return args.every((item, index) => item === OFFROUTER_MCP_ARGS[index]);
126
+ }
127
+ function offrouterMcpEntry() {
128
+ return {
129
+ command: OFFROUTER_MCP_COMMAND,
130
+ args: [...OFFROUTER_MCP_ARGS],
131
+ };
132
+ }
133
+ function freshOffrouterConfig() {
134
+ return {
135
+ [OFFROUTER_MCP_SERVERS_KEY]: {
136
+ [OFFROUTER_MCP_SERVER_NAME]: offrouterMcpEntry(),
137
+ },
138
+ };
139
+ }
140
+ function tinyPolicyProbeRequest(profile) {
141
+ return {
142
+ requestId: "install-policy-probe",
143
+ protocolVersion: ROUTE_PROTOCOL_VERSION,
144
+ harness: { kind: "gemini", profile },
145
+ task: {
146
+ promptPreview: "install policy probe",
147
+ promptDigest: "sha256:install-policy-probe",
148
+ kind: "other",
149
+ risk: "low",
150
+ },
151
+ workspace: { cwd: "/tmp/offrouter-install-probe", trusted: true },
152
+ constraints: {
153
+ subscriptionFirst: true,
154
+ allowApiKeyFallback: false,
155
+ },
156
+ };
157
+ }
158
+ /**
159
+ * Use routeRequest with empty candidates so work/unallowlisted profile denials
160
+ * reuse the same core policy codes users see from route explain / hooks.
161
+ */
162
+ export function evaluateInstallProfilePolicy(profile, config) {
163
+ const decision = routeRequest(tinyPolicyProbeRequest(profile), [], config.policy);
164
+ const denials = decision.policyDenials ?? [];
165
+ const hard = denials.find((d) => d.code === "work_profile_denied" ||
166
+ d.code === "profile_not_allowlisted" ||
167
+ d.code === "privacy_label_denied");
168
+ if (hard) {
169
+ return {
170
+ allowed: false,
171
+ errorCode: hard.code,
172
+ message: hard.message,
173
+ };
174
+ }
175
+ // Empty candidates also sets needsConfiguration + may include
176
+ // profile_not_allowlisted even for personal unallowlisted. When allowlisted,
177
+ // only needsConfiguration remains; that is fine for install.
178
+ const profileDenial = denials.find((d) => d.target === "profile");
179
+ if (profileDenial) {
180
+ return {
181
+ allowed: false,
182
+ errorCode: profileDenial.code,
183
+ message: profileDenial.message,
184
+ };
185
+ }
186
+ if (!config.policy.allowlistedProfiles.includes(profile)) {
187
+ return {
188
+ allowed: false,
189
+ errorCode: "profile_not_allowlisted",
190
+ message: `Profile ${profile} is not allowlisted. Add it explicitly before installing or routing.`,
191
+ };
192
+ }
193
+ return { allowed: true };
194
+ }
195
+ /**
196
+ * Install the OffRouter Gemini MCP entry into a single profile directory.
197
+ */
198
+ export async function installGeminiProfile(options) {
199
+ const { profile, config, dryRun = false, env = process.env, homedir = osHomedir, } = options;
200
+ if (!isSafeProfileId(profile)) {
201
+ return {
202
+ ok: false,
203
+ dryRun,
204
+ profile,
205
+ profileDir: "",
206
+ changes: [],
207
+ message: "Unsafe profile id. Use letters, numbers, dots, underscores, and dashes only.",
208
+ errorCode: "install_error",
209
+ };
210
+ }
211
+ const profileDir = resolveGeminiProfileDir(profile, { env, homedir });
212
+ const statePath = join(profileDir, GEMINI_INSTALL_STATE_REL);
213
+ const configPath = join(profileDir, GEMINI_CONFIG_REL);
214
+ if (!isGeminiHarnessProfile(profile)) {
215
+ return {
216
+ ok: false,
217
+ dryRun,
218
+ profile,
219
+ profileDir,
220
+ changes: [],
221
+ message: "Gemini install supports only profile ids named gemini or starting with gemini-.",
222
+ errorCode: "install_error",
223
+ };
224
+ }
225
+ // Work-like profile ids (e.g. gemini-work, gemini-work-staging) are never
226
+ // installable, even when allowlisted. This mirrors the filter used by
227
+ // listAllowlistedPersonalGeminiProfiles so explicit and --all-personal
228
+ // installs apply the same personal-only boundary.
229
+ if (isWorkLikeProfile(profile)) {
230
+ return {
231
+ ok: false,
232
+ dryRun,
233
+ profile,
234
+ profileDir,
235
+ changes: [],
236
+ message: `Profile ${profile} looks like a work profile and is never installable, even if allowlisted. Use a personal profile such as gemini-personal.`,
237
+ errorCode: "work_profile_denied",
238
+ };
239
+ }
240
+ const policy = evaluateInstallProfilePolicy(profile, config);
241
+ if (!policy.allowed) {
242
+ return {
243
+ ok: false,
244
+ dryRun,
245
+ profile,
246
+ profileDir,
247
+ changes: [],
248
+ message: policy.message ?? `Profile ${profile} is not installable.`,
249
+ errorCode: policy.errorCode ?? "install_error",
250
+ };
251
+ }
252
+ // Preserve original previous contents across re-installs (idempotent rollback).
253
+ let priorState = null;
254
+ if (await pathExists(statePath)) {
255
+ try {
256
+ const raw = await readFile(statePath, "utf8");
257
+ priorState = GeminiInstallStateSchema.parse(JSON.parse(raw));
258
+ }
259
+ catch (err) {
260
+ const detail = err instanceof Error ? err.message : String(err);
261
+ return {
262
+ ok: false,
263
+ dryRun,
264
+ profile,
265
+ profileDir,
266
+ changes: [],
267
+ message: `Invalid install state at ${statePath}: ${detail}`,
268
+ errorCode: "install_error",
269
+ };
270
+ }
271
+ }
272
+ const changes = [];
273
+ // --- settings.json MCP entry ---
274
+ let current = null;
275
+ if (await pathExists(configPath)) {
276
+ current = await readFile(configPath, "utf8");
277
+ }
278
+ const priorFile = priorState?.files[GEMINI_CONFIG_REL];
279
+ const previous = priorFile !== undefined ? priorFile.previous : current;
280
+ const desired = patchConfigForMcp(current);
281
+ let kind;
282
+ if (current === null) {
283
+ kind = "create";
284
+ }
285
+ else if (current === desired) {
286
+ kind = "unchanged";
287
+ }
288
+ else {
289
+ kind = "update";
290
+ }
291
+ changes.push({
292
+ path: configPath,
293
+ relativePath: GEMINI_CONFIG_REL,
294
+ kind,
295
+ summary: changeSummary(kind, GEMINI_CONFIG_REL),
296
+ });
297
+ if (!dryRun && kind !== "unchanged") {
298
+ await mkdir(dirname(configPath), { recursive: true });
299
+ await writeFile(configPath, desired, "utf8");
300
+ }
301
+ // --- install state metadata ---
302
+ const state = {
303
+ version: 1,
304
+ profile,
305
+ harness: "gemini",
306
+ installedAt: priorState?.installedAt ?? new Date().toISOString(),
307
+ configRelative: GEMINI_CONFIG_REL,
308
+ files: {
309
+ [GEMINI_CONFIG_REL]: {
310
+ previous,
311
+ hash: sha256(desired),
312
+ },
313
+ },
314
+ };
315
+ const stateJson = `${JSON.stringify(state, null, 2)}\n`;
316
+ let stateKind = "create";
317
+ if (await pathExists(statePath)) {
318
+ const existing = await readFile(statePath, "utf8");
319
+ stateKind = existing === stateJson ? "unchanged" : "update";
320
+ }
321
+ changes.push({
322
+ path: statePath,
323
+ relativePath: GEMINI_INSTALL_STATE_REL,
324
+ kind: stateKind,
325
+ summary: changeSummary(stateKind, GEMINI_INSTALL_STATE_REL),
326
+ });
327
+ if (!dryRun && stateKind !== "unchanged") {
328
+ await mkdir(dirname(statePath), { recursive: true });
329
+ await writeFile(statePath, stateJson, "utf8");
330
+ }
331
+ const wrote = changes.filter((c) => c.kind === "create" || c.kind === "update");
332
+ const message = dryRun
333
+ ? `Dry run: would apply ${wrote.length} file change(s) for profile ${profile}.`
334
+ : `Installed OffRouter Gemini MCP entry for profile ${profile} (${wrote.length} file change(s)).`;
335
+ return {
336
+ ok: true,
337
+ dryRun,
338
+ profile,
339
+ profileDir,
340
+ changes,
341
+ message,
342
+ statePath,
343
+ };
344
+ }
345
+ /**
346
+ * Human-readable diff-like text for an install/uninstall result matrix.
347
+ */
348
+ export function formatInstallDiffText(results, headline) {
349
+ const lines = [];
350
+ if (headline) {
351
+ lines.push(headline);
352
+ }
353
+ for (const result of results) {
354
+ lines.push(``);
355
+ lines.push(`## ${result.profile} (${result.ok ? (result.dryRun ? "dry-run" : "applied") : "error"})`);
356
+ lines.push(`profileDir: ${result.profileDir}`);
357
+ if (!result.ok) {
358
+ lines.push(`error: ${result.message}`);
359
+ continue;
360
+ }
361
+ for (const change of result.changes) {
362
+ if (change.kind === "unchanged")
363
+ continue;
364
+ lines.push(change.summary);
365
+ }
366
+ const unchanged = result.changes.filter((c) => c.kind === "unchanged").length;
367
+ if (unchanged > 0) {
368
+ lines.push(`= ${unchanged} unchanged`);
369
+ }
370
+ lines.push(result.message);
371
+ }
372
+ return lines.join("\n").trimEnd() + "\n";
373
+ }
374
+ //# sourceMappingURL=install.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.js","sourceRoot":"","sources":["../src/install.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import type { OffRouterConfig } from "offrouter-core";
2
+ export declare const SAFE_PROFILE_ID_RE: RegExp;
3
+ export declare function isSafeProfileId(profile: string): boolean;
4
+ export interface ResolveGeminiProfileDirOptions {
5
+ env?: NodeJS.ProcessEnv;
6
+ homedir?: () => string;
7
+ }
8
+ /**
9
+ * Resolve the on-disk Gemini profile directory for a OffRouter profile id.
10
+ *
11
+ * - When OFFROUTER_GEMINI_PROFILES_DIR is set: $DIR/<profile>
12
+ * - profile id `gemini` -> GEMINI_HOME (if set) else ~/.gemini
13
+ * - otherwise -> ~/.<profile> (e.g. gemini-personal -> ~/.gemini-personal)
14
+ */
15
+ export declare function resolveGeminiProfileDir(profile: string, options?: ResolveGeminiProfileDirOptions): string;
16
+ /**
17
+ * Personal Gemini profiles only: id starts with "gemini" or is exactly "gemini".
18
+ */
19
+ export declare function isGeminiHarnessProfile(profile: string): boolean;
20
+ export declare function isWorkLikeProfile(profile: string): boolean;
21
+ /**
22
+ * Allowlisted personal Gemini profiles eligible for install --all-personal.
23
+ * Excludes the bare global `gemini` profile (only mutable when explicit) and
24
+ * work-like ids, and applies deniedProfilePatterns glob filters.
25
+ */
26
+ export declare function listAllowlistedPersonalGeminiProfiles(config: OffRouterConfig): string[];
27
+ //# sourceMappingURL=profile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile.d.ts","sourceRoot":"","sources":["../src/profile.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEtD,eAAO,MAAM,kBAAkB,QAAiC,CAAC;AAEjE,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAExD;AAED,MAAM,WAAW,8BAA8B;IAC7C,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,MAAM,CAAC;CACxB;AAUD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,8BAAmC,GAC3C,MAAM,CAiBR;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAE/D;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAE1D;AAQD;;;;GAIG;AACH,wBAAgB,qCAAqC,CACnD,MAAM,EAAE,eAAe,GACtB,MAAM,EAAE,CAaV"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Gemini profile directory resolution and allowlist helpers.
3
+ *
4
+ * Mirrors the Codex adapter's profile isolation model:
5
+ * - env OFFROUTER_GEMINI_PROFILES_DIR set => <root>/<profile>
6
+ * - profile === "gemini" => env GEMINI_HOME || ~/.gemini (bare global; only
7
+ * mutable when explicitly allowlisted)
8
+ * - otherwise => ~/.<profile> (e.g. gemini-personal => ~/.gemini-personal)
9
+ */
10
+ import { homedir as osHomedir } from "node:os";
11
+ import { join, resolve } from "node:path";
12
+ export const SAFE_PROFILE_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
13
+ export function isSafeProfileId(profile) {
14
+ return SAFE_PROFILE_ID_RE.test(profile);
15
+ }
16
+ function envValue(env, key) {
17
+ const v = env?.[key]?.trim();
18
+ return v || undefined;
19
+ }
20
+ /**
21
+ * Resolve the on-disk Gemini profile directory for a OffRouter profile id.
22
+ *
23
+ * - When OFFROUTER_GEMINI_PROFILES_DIR is set: $DIR/<profile>
24
+ * - profile id `gemini` -> GEMINI_HOME (if set) else ~/.gemini
25
+ * - otherwise -> ~/.<profile> (e.g. gemini-personal -> ~/.gemini-personal)
26
+ */
27
+ export function resolveGeminiProfileDir(profile, options = {}) {
28
+ if (!isSafeProfileId(profile)) {
29
+ throw new Error(`Unsafe profile id "${profile}". Use letters, numbers, dots, underscores, and dashes only.`);
30
+ }
31
+ const env = options.env ?? process.env;
32
+ const home = (options.homedir ?? osHomedir)();
33
+ const profilesRoot = envValue(env, "OFFROUTER_GEMINI_PROFILES_DIR");
34
+ if (profilesRoot) {
35
+ return resolve(join(profilesRoot, profile));
36
+ }
37
+ if (profile === "gemini") {
38
+ const geminiHome = envValue(env, "GEMINI_HOME");
39
+ return resolve(geminiHome ?? join(home, ".gemini"));
40
+ }
41
+ return resolve(join(home, `.${profile}`));
42
+ }
43
+ /**
44
+ * Personal Gemini profiles only: id starts with "gemini" or is exactly "gemini".
45
+ */
46
+ export function isGeminiHarnessProfile(profile) {
47
+ return profile === "gemini" || profile.startsWith("gemini-");
48
+ }
49
+ export function isWorkLikeProfile(profile) {
50
+ return profile.endsWith("-work") || profile.includes("-work-");
51
+ }
52
+ function matchGlob(pattern, value) {
53
+ const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
54
+ const re = new RegExp(`^${escaped.replace(/\*/g, ".*")}$`);
55
+ return re.test(value);
56
+ }
57
+ /**
58
+ * Allowlisted personal Gemini profiles eligible for install --all-personal.
59
+ * Excludes the bare global `gemini` profile (only mutable when explicit) and
60
+ * work-like ids, and applies deniedProfilePatterns glob filters.
61
+ */
62
+ export function listAllowlistedPersonalGeminiProfiles(config) {
63
+ return [...config.policy.allowlistedProfiles]
64
+ .filter((id) => isSafeProfileId(id) &&
65
+ isGeminiHarnessProfile(id) &&
66
+ id !== "gemini" &&
67
+ !isWorkLikeProfile(id) &&
68
+ !(config.policy.deniedProfilePatterns ?? []).some((p) => matchGlob(p, id)))
69
+ .sort((a, b) => a.localeCompare(b));
70
+ }
71
+ //# sourceMappingURL=profile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile.js","sourceRoot":"","sources":["../src/profile.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import { type InstallFileChange } from "./install.js";
2
+ export interface GeminiRollbackOptions {
3
+ profile: string;
4
+ env?: NodeJS.ProcessEnv;
5
+ homedir?: () => string;
6
+ dryRun?: boolean;
7
+ /** Optional absolute override of the profile directory (tests). */
8
+ profileDir?: string;
9
+ }
10
+ export interface GeminiRollbackResult {
11
+ ok: boolean;
12
+ dryRun: boolean;
13
+ profile: string;
14
+ profileDir: string;
15
+ changes: InstallFileChange[];
16
+ message: string;
17
+ errorCode?: "no_install_state" | "invalid_install_state" | "rollback_error";
18
+ }
19
+ /**
20
+ * Restore files tracked by gemini-install-state.json for a profile.
21
+ */
22
+ export declare function rollbackGeminiProfile(options: GeminiRollbackOptions): Promise<GeminiRollbackResult>;
23
+ //# sourceMappingURL=rollback.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rollback.d.ts","sourceRoot":"","sources":["../src/rollback.ts"],"names":[],"mappings":"AAgBA,OAAO,EAML,KAAK,iBAAiB,EACvB,MAAM,cAAc,CAAC;AAGtB,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,mEAAmE;IACnE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,iBAAiB,EAAE,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,kBAAkB,GAAG,uBAAuB,GAAG,gBAAgB,CAAC;CAC7E;AAmID;;GAEG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,CAAC,CA8G/B"}