@openchamber/web 1.5.5 → 1.5.7

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.
@@ -432,6 +432,65 @@ export async function getDiff(directory, { path, staged = false, contextLines =
432
432
  }
433
433
  }
434
434
 
435
+ export async function getRangeDiff(directory, { base, head, path, contextLines = 3 } = {}) {
436
+ const git = simpleGit(normalizeDirectoryPath(directory));
437
+ const baseRef = typeof base === 'string' ? base.trim() : '';
438
+ const headRef = typeof head === 'string' ? head.trim() : '';
439
+ if (!baseRef || !headRef) {
440
+ throw new Error('base and head are required');
441
+ }
442
+
443
+ // Prefer remote-tracking base ref so merged commits don't reappear
444
+ // when local base branch is stale (common when user stays on feature branch).
445
+ let resolvedBase = baseRef;
446
+ const originCandidate = `refs/remotes/origin/${baseRef}`;
447
+ try {
448
+ const verified = await git.raw(['rev-parse', '--verify', originCandidate]);
449
+ if (verified && verified.trim()) {
450
+ resolvedBase = `origin/${baseRef}`;
451
+ }
452
+ } catch {
453
+ // ignore
454
+ }
455
+
456
+ const args = ['diff', '--no-color'];
457
+ if (typeof contextLines === 'number' && !Number.isNaN(contextLines)) {
458
+ args.push(`-U${Math.max(0, contextLines)}`);
459
+ }
460
+ args.push(`${resolvedBase}...${headRef}`);
461
+ if (path) {
462
+ args.push('--', path);
463
+ }
464
+ const diff = await git.raw(args);
465
+ return diff;
466
+ }
467
+
468
+ export async function getRangeFiles(directory, { base, head } = {}) {
469
+ const git = simpleGit(normalizeDirectoryPath(directory));
470
+ const baseRef = typeof base === 'string' ? base.trim() : '';
471
+ const headRef = typeof head === 'string' ? head.trim() : '';
472
+ if (!baseRef || !headRef) {
473
+ throw new Error('base and head are required');
474
+ }
475
+
476
+ let resolvedBase = baseRef;
477
+ const originCandidate = `refs/remotes/origin/${baseRef}`;
478
+ try {
479
+ const verified = await git.raw(['rev-parse', '--verify', originCandidate]);
480
+ if (verified && verified.trim()) {
481
+ resolvedBase = `origin/${baseRef}`;
482
+ }
483
+ } catch {
484
+ // ignore
485
+ }
486
+
487
+ const raw = await git.raw(['diff', '--name-only', `${resolvedBase}...${headRef}`]);
488
+ return String(raw || '')
489
+ .split('\n')
490
+ .map((l) => l.trim())
491
+ .filter(Boolean);
492
+ }
493
+
435
494
  const IMAGE_EXTENSIONS = ['png', 'jpg', 'jpeg', 'gif', 'svg', 'webp', 'ico', 'bmp', 'avif'];
436
495
 
437
496
  function isImageFile(filePath) {
@@ -0,0 +1,159 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import os from 'os';
4
+
5
+ const OPENCHAMBER_DATA_DIR = process.env.OPENCHAMBER_DATA_DIR
6
+ ? path.resolve(process.env.OPENCHAMBER_DATA_DIR)
7
+ : path.join(os.homedir(), '.config', 'openchamber');
8
+
9
+ const STORAGE_DIR = OPENCHAMBER_DATA_DIR;
10
+ const STORAGE_FILE = path.join(STORAGE_DIR, 'github-auth.json');
11
+ const SETTINGS_FILE = path.join(OPENCHAMBER_DATA_DIR, 'settings.json');
12
+
13
+ const DEFAULT_GITHUB_CLIENT_ID = 'Ov23liNd8TxDcMXtAHHM';
14
+ const DEFAULT_GITHUB_SCOPES = 'repo read:org workflow read:user user:email';
15
+
16
+ function ensureStorageDir() {
17
+ if (!fs.existsSync(STORAGE_DIR)) {
18
+ fs.mkdirSync(STORAGE_DIR, { recursive: true });
19
+ }
20
+ }
21
+
22
+ function readJsonFile() {
23
+ ensureStorageDir();
24
+ if (!fs.existsSync(STORAGE_FILE)) {
25
+ return null;
26
+ }
27
+ try {
28
+ const raw = fs.readFileSync(STORAGE_FILE, 'utf8');
29
+ const trimmed = raw.trim();
30
+ if (!trimmed) {
31
+ return null;
32
+ }
33
+ const parsed = JSON.parse(trimmed);
34
+ if (!parsed || typeof parsed !== 'object') {
35
+ return null;
36
+ }
37
+ return parsed;
38
+ } catch (error) {
39
+ console.error('Failed to read GitHub auth file:', error);
40
+ return null;
41
+ }
42
+ }
43
+
44
+ function writeJsonFile(payload) {
45
+ ensureStorageDir();
46
+
47
+ // Atomic write so multiple OpenChamber instances can safely share the same file.
48
+ const tmpFile = `${STORAGE_FILE}.${process.pid}.${Date.now()}.tmp`;
49
+ fs.writeFileSync(tmpFile, JSON.stringify(payload, null, 2), 'utf8');
50
+ try {
51
+ fs.chmodSync(tmpFile, 0o600);
52
+ } catch {
53
+ // best-effort
54
+ }
55
+
56
+ fs.renameSync(tmpFile, STORAGE_FILE);
57
+ try {
58
+ fs.chmodSync(STORAGE_FILE, 0o600);
59
+ } catch {
60
+ // best-effort
61
+ }
62
+ }
63
+
64
+ export function getGitHubAuth() {
65
+ const data = readJsonFile();
66
+ if (!data) {
67
+ return null;
68
+ }
69
+ const accessToken = typeof data.accessToken === 'string' ? data.accessToken : '';
70
+ if (!accessToken) {
71
+ return null;
72
+ }
73
+ return {
74
+ accessToken,
75
+ scope: typeof data.scope === 'string' ? data.scope : '',
76
+ tokenType: typeof data.tokenType === 'string' ? data.tokenType : 'bearer',
77
+ createdAt: typeof data.createdAt === 'number' ? data.createdAt : null,
78
+ user: data.user && typeof data.user === 'object'
79
+ ? {
80
+ login: typeof data.user.login === 'string' ? data.user.login : null,
81
+ avatarUrl: typeof data.user.avatarUrl === 'string' ? data.user.avatarUrl : null,
82
+ id: typeof data.user.id === 'number' ? data.user.id : null,
83
+ name: typeof data.user.name === 'string' ? data.user.name : null,
84
+ email: typeof data.user.email === 'string' ? data.user.email : null,
85
+ }
86
+ : null,
87
+ };
88
+ }
89
+
90
+ export function setGitHubAuth({ accessToken, scope, tokenType, user }) {
91
+ if (!accessToken || typeof accessToken !== 'string') {
92
+ throw new Error('accessToken is required');
93
+ }
94
+ writeJsonFile({
95
+ accessToken,
96
+ scope: typeof scope === 'string' ? scope : '',
97
+ tokenType: typeof tokenType === 'string' ? tokenType : 'bearer',
98
+ createdAt: Date.now(),
99
+ user: user && typeof user === 'object'
100
+ ? {
101
+ login: typeof user.login === 'string' ? user.login : undefined,
102
+ avatarUrl: typeof user.avatarUrl === 'string' ? user.avatarUrl : undefined,
103
+ id: typeof user.id === 'number' ? user.id : undefined,
104
+ name: typeof user.name === 'string' ? user.name : undefined,
105
+ email: typeof user.email === 'string' ? user.email : undefined,
106
+ }
107
+ : undefined,
108
+ });
109
+ }
110
+
111
+ export function clearGitHubAuth() {
112
+ try {
113
+ if (fs.existsSync(STORAGE_FILE)) {
114
+ fs.unlinkSync(STORAGE_FILE);
115
+ }
116
+ return true;
117
+ } catch (error) {
118
+ console.error('Failed to clear GitHub auth file:', error);
119
+ return false;
120
+ }
121
+ }
122
+
123
+ export function getGitHubClientId() {
124
+ const raw = process.env.OPENCHAMBER_GITHUB_CLIENT_ID;
125
+ const clientId = typeof raw === 'string' ? raw.trim() : '';
126
+ if (clientId) return clientId;
127
+
128
+ try {
129
+ if (fs.existsSync(SETTINGS_FILE)) {
130
+ const parsed = JSON.parse(fs.readFileSync(SETTINGS_FILE, 'utf8'));
131
+ const stored = typeof parsed?.githubClientId === 'string' ? parsed.githubClientId.trim() : '';
132
+ if (stored) return stored;
133
+ }
134
+ } catch {
135
+ // ignore
136
+ }
137
+
138
+ return DEFAULT_GITHUB_CLIENT_ID;
139
+ }
140
+
141
+ export function getGitHubScopes() {
142
+ const raw = process.env.OPENCHAMBER_GITHUB_SCOPES;
143
+ const fromEnv = typeof raw === 'string' ? raw.trim() : '';
144
+ if (fromEnv) return fromEnv;
145
+
146
+ try {
147
+ if (fs.existsSync(SETTINGS_FILE)) {
148
+ const parsed = JSON.parse(fs.readFileSync(SETTINGS_FILE, 'utf8'));
149
+ const stored = typeof parsed?.githubScopes === 'string' ? parsed.githubScopes.trim() : '';
150
+ if (stored) return stored;
151
+ }
152
+ } catch {
153
+ // ignore
154
+ }
155
+
156
+ return DEFAULT_GITHUB_SCOPES;
157
+ }
158
+
159
+ export const GITHUB_AUTH_FILE = STORAGE_FILE;
@@ -0,0 +1,50 @@
1
+ const DEVICE_CODE_URL = 'https://github.com/login/device/code';
2
+ const ACCESS_TOKEN_URL = 'https://github.com/login/oauth/access_token';
3
+ const DEVICE_GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:device_code';
4
+
5
+ const encodeForm = (params) => {
6
+ const body = new URLSearchParams();
7
+ for (const [key, value] of Object.entries(params)) {
8
+ if (value == null) continue;
9
+ body.set(key, String(value));
10
+ }
11
+ return body.toString();
12
+ };
13
+
14
+ async function postForm(url, params) {
15
+ const response = await fetch(url, {
16
+ method: 'POST',
17
+ headers: {
18
+ 'Content-Type': 'application/x-www-form-urlencoded',
19
+ Accept: 'application/json',
20
+ },
21
+ body: encodeForm(params),
22
+ });
23
+
24
+ const payload = await response.json().catch(() => null);
25
+ if (!response.ok) {
26
+ const message = payload?.error_description || payload?.error || response.statusText;
27
+ const error = new Error(message || 'GitHub request failed');
28
+ error.status = response.status;
29
+ error.payload = payload;
30
+ throw error;
31
+ }
32
+ return payload;
33
+ }
34
+
35
+ export async function startDeviceFlow({ clientId, scope }) {
36
+ return postForm(DEVICE_CODE_URL, {
37
+ client_id: clientId,
38
+ scope,
39
+ });
40
+ }
41
+
42
+ export async function exchangeDeviceCode({ clientId, deviceCode }) {
43
+ // GitHub returns 200 with {error: 'authorization_pending'|...} for non-success states.
44
+ const payload = await postForm(ACCESS_TOKEN_URL, {
45
+ client_id: clientId,
46
+ device_code: deviceCode,
47
+ grant_type: DEVICE_GRANT_TYPE,
48
+ });
49
+ return payload;
50
+ }
@@ -0,0 +1,10 @@
1
+ import { Octokit } from '@octokit/rest';
2
+ import { getGitHubAuth } from './github-auth.js';
3
+
4
+ export function getOctokitOrNull() {
5
+ const auth = getGitHubAuth();
6
+ if (!auth?.accessToken) {
7
+ return null;
8
+ }
9
+ return new Octokit({ auth: auth.accessToken });
10
+ }
@@ -0,0 +1,55 @@
1
+ import { getRemoteUrl } from './git-service.js';
2
+
3
+ export const parseGitHubRemoteUrl = (raw) => {
4
+ if (typeof raw !== 'string') {
5
+ return null;
6
+ }
7
+ const value = raw.trim();
8
+ if (!value) {
9
+ return null;
10
+ }
11
+
12
+ // git@github.com:OWNER/REPO.git
13
+ if (value.startsWith('git@github.com:')) {
14
+ const rest = value.slice('git@github.com:'.length);
15
+ const cleaned = rest.endsWith('.git') ? rest.slice(0, -4) : rest;
16
+ const [owner, repo] = cleaned.split('/');
17
+ if (!owner || !repo) return null;
18
+ return { owner, repo, url: `https://github.com/${owner}/${repo}` };
19
+ }
20
+
21
+ // ssh://git@github.com/OWNER/REPO.git
22
+ if (value.startsWith('ssh://git@github.com/')) {
23
+ const rest = value.slice('ssh://git@github.com/'.length);
24
+ const cleaned = rest.endsWith('.git') ? rest.slice(0, -4) : rest;
25
+ const [owner, repo] = cleaned.split('/');
26
+ if (!owner || !repo) return null;
27
+ return { owner, repo, url: `https://github.com/${owner}/${repo}` };
28
+ }
29
+
30
+ // https://github.com/OWNER/REPO(.git)
31
+ try {
32
+ const url = new URL(value);
33
+ if (url.hostname !== 'github.com') {
34
+ return null;
35
+ }
36
+ const path = url.pathname.replace(/^\/+/, '').replace(/\/+$/, '');
37
+ const cleaned = path.endsWith('.git') ? path.slice(0, -4) : path;
38
+ const [owner, repo] = cleaned.split('/');
39
+ if (!owner || !repo) return null;
40
+ return { owner, repo, url: `https://github.com/${owner}/${repo}` };
41
+ } catch {
42
+ return null;
43
+ }
44
+ };
45
+
46
+ export async function resolveGitHubRepoFromDirectory(directory) {
47
+ const remoteUrl = await getRemoteUrl(directory).catch(() => null);
48
+ if (!remoteUrl) {
49
+ return { repo: null, remoteUrl: null };
50
+ }
51
+ return {
52
+ repo: parseGitHubRemoteUrl(remoteUrl),
53
+ remoteUrl,
54
+ };
55
+ }
@@ -1,2 +0,0 @@
1
- const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/vendor-.bun-BrcdMJ39.js","assets/vendor--Jn2c0Clh.css","assets/main-Co1JTQ7W.js","assets/main-C1GggQif.css"])))=>i.map(i=>d[i]);
2
- import{_ as x}from"./vendor-.bun-BrcdMJ39.js";(function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const n of document.querySelectorAll('link[rel="modulepreload"]'))o(n);new MutationObserver(n=>{for(const a of n)if(a.type==="childList")for(const c of a.addedNodes)c.tagName==="LINK"&&c.rel==="modulepreload"&&o(c)}).observe(document,{childList:!0,subtree:!0});function r(n){const a={};return n.integrity&&(a.integrity=n.integrity),n.referrerPolicy&&(a.referrerPolicy=n.referrerPolicy),n.crossOrigin==="use-credentials"?a.credentials="include":n.crossOrigin==="anonymous"?a.credentials="omit":a.credentials="same-origin",a}function o(n){if(n.ep)return;n.ep=!0;const a=r(n);fetch(n.href,a)}})();async function $(t){const e=await fetch("/api/terminal/create",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({cwd:t.cwd,cols:t.cols||80,rows:t.rows||24})});if(!e.ok){const r=await e.json().catch(()=>({error:"Failed to create terminal"}));throw new Error(r.error||"Failed to create terminal session")}return e.json()}function N(t,e,r,o={}){const{maxRetries:n=3,initialRetryDelay:a=1e3,maxRetryDelay:c=8e3,connectionTimeout:p=1e4}=o;let d=null,h=0,w=null,l=null,y=!1,T=!1,m=!1;const j=()=>{w&&(clearTimeout(w),w=null),l&&(clearTimeout(l),l=null)},b=()=>{y=!0,j(),d&&(d.close(),d=null)},S=()=>{if(!(y||m)){if(d&&d.readyState!==EventSource.CLOSED){console.warn("Attempted to create duplicate EventSource, skipping");return}T=!1,d=new EventSource(`/api/terminal/${t}/stream`),l=setTimeout(()=>{!T&&d?.readyState!==EventSource.OPEN&&(console.error("Terminal connection timeout"),d?.close(),k(new Error("Connection timeout"),!1))},p),d.onopen=()=>{T||(T=!0,h=0,j(),e({type:"connected"}))},d.onmessage=g=>{try{const f=JSON.parse(g.data);f.type==="exit"&&(m=!0,b()),e(f)}catch(f){console.error("Failed to parse terminal event:",f),r?.(f,!1)}},d.onerror=g=>{console.error("Terminal stream error:",g,"readyState:",d?.readyState),j();const f=m||d?.readyState===EventSource.CLOSED;d?.close(),d=null,m||k(new Error("Terminal stream connection error"),f)}}},k=(g,f)=>{if(!(y||m))if(h<n&&!f){h++;const O=Math.min(a*Math.pow(2,h-1),c);console.log(`Reconnecting to terminal stream (attempt ${h}/${n}) in ${O}ms`),e({type:"reconnecting",attempt:h,maxAttempts:n}),w=setTimeout(()=>{!y&&!m&&S()},O)}else console.error(`Terminal connection failed after ${h} attempts`),r?.(g,!0),b()};return S(),b}async function C(t,e){const r=await fetch(`/api/terminal/${t}/input`,{method:"POST",headers:{"Content-Type":"text/plain"},body:e});if(!r.ok){const o=await r.json().catch(()=>({error:"Failed to send input"}));throw new Error(o.error||"Failed to send terminal input")}}async function v(t,e,r){const o=await fetch(`/api/terminal/${t}/resize`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({cols:e,rows:r})});if(!o.ok){const n=await o.json().catch(()=>({error:"Failed to resize terminal"}));throw new Error(n.error||"Failed to resize terminal")}}async function I(t){const e=await fetch(`/api/terminal/${t}`,{method:"DELETE"});if(!e.ok){const r=await e.json().catch(()=>({error:"Failed to close terminal"}));throw new Error(r.error||"Failed to close terminal")}}async function A(t,e){const r=await fetch(`/api/terminal/${t}/restart`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({cwd:e.cwd,cols:e.cols??80,rows:e.rows??24})});if(!r.ok){const o=await r.json().catch(()=>({error:"Failed to restart terminal"}));throw new Error(o.error||"Failed to restart terminal")}return r.json()}async function G(t){const e=await fetch("/api/terminal/force-kill",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)});if(!e.ok){const r=await e.json().catch(()=>({error:"Failed to force kill terminal"}));throw new Error(r.error||"Failed to force kill terminal")}}const R=t=>{const e=t?.retry;return{maxRetries:e?.maxRetries??3,initialRetryDelay:e?.initialDelayMs??1e3,maxRetryDelay:e?.maxDelayMs??8e3,connectionTimeout:t?.connectionTimeoutMs??1e4}},D=()=>({async createSession(t){return $(t)},connect(t,e,r){const o=N(t,e.onEvent,e.onError,R(r));return{close:()=>o()}},async sendInput(t,e){await C(t,e)},async resize(t){await v(t.sessionId,t.cols,t.rows)},async close(t){await I(t)},async restartSession(t,e){return A(t,{cwd:e.cwd??"",cols:e.cols,rows:e.rows})},async forceKill(t){await G(t)}}),J=()=>{if(typeof window>"u")return"";const t=window.__OPENCHAMBER_DESKTOP_SERVER__?.origin;return t||window.location.origin},s="/api/git";function i(t,e,r){const o=new URL(t,J());if(e&&o.searchParams.set("directory",e),r)for(const[n,a]of Object.entries(r))a!==void 0&&o.searchParams.set(n,String(a));return o.toString()}async function L(t){const e=await fetch(i(`${s}/check`,t));if(!e.ok)throw new Error(`Failed to check git repository: ${e.statusText}`);return(await e.json()).isGitRepository}async function _(t){const e=await fetch(i(`${s}/status`,t));if(!e.ok)throw new Error(`Failed to get git status: ${e.statusText}`);return e.json()}async function W(t,e){const{path:r,staged:o,contextLines:n}=e;if(!r)throw new Error("path is required to fetch git diff");const a=await fetch(i(`${s}/diff`,t,{path:r,staged:o?"true":void 0,context:n}));if(!a.ok)throw new Error(`Failed to get git diff: ${a.statusText}`);return a.json()}async function B(t,e){const{path:r,staged:o}=e;if(!r)throw new Error("path is required to fetch git file diff");const n=await fetch(i(`${s}/file-diff`,t,{path:r,staged:o?"true":void 0}));if(!n.ok)throw new Error(`Failed to get git file diff: ${n.statusText}`);return n.json()}async function q(t,e){if(!e)throw new Error("path is required to revert git changes");const r=await fetch(i(`${s}/revert`,t),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:e})});if(!r.ok){const o=await r.json().catch(()=>({error:r.statusText}));throw new Error(o.error||"Failed to revert git changes")}}async function M(t){if(!t)return!1;const e=await fetch(i(`${s}/worktree-type`,t));if(!e.ok)throw new Error(`Failed to detect worktree type: ${e.statusText}`);return!!(await e.json()).linked}async function U(t){const e=await fetch(i(`${s}/branches`,t));if(!e.ok)throw new Error(`Failed to get branches: ${e.statusText}`);return e.json()}async function z(t,e){if(!e?.branch)throw new Error("branch is required to delete a branch");const r=await fetch(i(`${s}/branches`,t),{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});if(!r.ok){const o=await r.json().catch(()=>({error:r.statusText}));throw new Error(o.error||"Failed to delete branch")}return r.json()}async function V(t,e){if(!e?.branch)throw new Error("branch is required to delete remote branch");const r=await fetch(i(`${s}/remote-branches`,t),{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});if(!r.ok){const o=await r.json().catch(()=>({error:r.statusText}));throw new Error(o.error||"Failed to delete remote branch")}return r.json()}async function K(t,e){if(!Array.isArray(e)||e.length===0)throw new Error("No files provided to generate commit message");const r=await fetch(i(`${s}/commit-message`,t),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({files:e})});if(!r.ok){const c=await r.json().catch(()=>({error:r.statusText}));throw new Error(c.error||"Failed to generate commit message")}const o=await r.json();if(!o?.message||typeof o.message!="object")throw new Error("Malformed commit generation response");const n=typeof o.message.subject=="string"&&o.message.subject.trim().length>0?o.message.subject.trim():"",a=Array.isArray(o.message.highlights)?o.message.highlights.filter(c=>typeof c=="string"&&c.trim().length>0).map(c=>c.trim()):[];return{message:{subject:n,highlights:a}}}async function H(t){const e=await fetch(i(`${s}/worktrees`,t));if(!e.ok){const r=await e.json().catch(()=>({error:e.statusText}));throw new Error(r.error||"Failed to list worktrees")}return e.json()}async function Q(t,e){if(!e?.path||!e?.branch)throw new Error("path and branch are required to add a worktree");const r=await fetch(i(`${s}/worktrees`,t),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});if(!r.ok){const o=await r.json().catch(()=>({error:r.statusText}));throw new Error(o.error||"Failed to add worktree")}return r.json()}async function X(t,e){if(!e?.path)throw new Error("path is required to remove a worktree");const r=await fetch(i(`${s}/worktrees`,t),{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});if(!r.ok){const o=await r.json().catch(()=>({error:r.statusText}));throw new Error(o.error||"Failed to remove worktree")}return r.json()}async function Y(t){const e=await fetch(i(`${s}/ignore-openchamber`,t),{method:"POST"});if(!e.ok){const r=await e.json().catch(()=>({error:e.statusText}));throw new Error(r.error||"Failed to update git ignore")}}async function Z(t,e,r={}){const o=await fetch(i(`${s}/commit`,t),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({message:e,addAll:r.addAll??!1,files:r.files})});if(!o.ok){const n=await o.json().catch(()=>({error:o.statusText}));throw new Error(n.error||"Failed to create commit")}return o.json()}async function ee(t,e={}){const r=await fetch(i(`${s}/push`,t),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});if(!r.ok){const o=await r.json().catch(()=>({error:r.statusText}));throw new Error(o.error||"Failed to push")}return r.json()}async function te(t,e={}){const r=await fetch(i(`${s}/pull`,t),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});if(!r.ok){const o=await r.json().catch(()=>({error:r.statusText}));throw new Error(o.error||"Failed to pull")}return r.json()}async function re(t,e={}){const r=await fetch(i(`${s}/fetch`,t),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});if(!r.ok){const o=await r.json().catch(()=>({error:r.statusText}));throw new Error(o.error||"Failed to fetch")}return r.json()}async function oe(t,e){const r=await fetch(i(`${s}/checkout`,t),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({branch:e})});if(!r.ok){const o=await r.json().catch(()=>({error:r.statusText}));throw new Error(o.error||"Failed to checkout branch")}return r.json()}async function ne(t,e,r){const o=await fetch(i(`${s}/branches`,t),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,startPoint:r})});if(!o.ok){const n=await o.json().catch(()=>({error:o.statusText}));throw new Error(n.error||"Failed to create branch")}return o.json()}async function se(t,e,r){const o=await fetch(i(`${s}/branches/rename`,t),{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldName:e,newName:r})});if(!o.ok){const n=await o.json().catch(()=>({error:o.statusText}));throw new Error(n.error||"Failed to rename branch")}return o.json()}async function ie(t,e={}){const r=await fetch(i(`${s}/log`,t,{maxCount:e.maxCount,from:e.from,to:e.to,file:e.file}));if(!r.ok)throw new Error(`Failed to get git log: ${r.statusText}`);return r.json()}async function ae(t,e){const r=await fetch(i(`${s}/commit-files`,t,{hash:e}));if(!r.ok)throw new Error(`Failed to get commit files: ${r.statusText}`);return r.json()}async function ce(){const t=await fetch(i(`${s}/identities`,void 0));if(!t.ok)throw new Error(`Failed to get git identities: ${t.statusText}`);return t.json()}async function le(t){const e=await fetch(i(`${s}/identities`,void 0),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)});if(!e.ok){const r=await e.json().catch(()=>({error:e.statusText}));throw new Error(r.error||"Failed to create git identity")}return e.json()}async function de(t,e){const r=await fetch(i(`${s}/identities/${t}`,void 0),{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});if(!r.ok){const o=await r.json().catch(()=>({error:r.statusText}));throw new Error(o.error||"Failed to update git identity")}return r.json()}async function ue(t){const e=await fetch(i(`${s}/identities/${t}`,void 0),{method:"DELETE"});if(!e.ok){const r=await e.json().catch(()=>({error:e.statusText}));throw new Error(r.error||"Failed to delete git identity")}}async function he(t){if(!t)return null;const e=await fetch(i(`${s}/current-identity`,t));if(!e.ok)throw new Error(`Failed to get current git identity: ${e.statusText}`);const r=await e.json();return r?{userName:r.userName??null,userEmail:r.userEmail??null,sshCommand:r.sshCommand??null}:null}async function fe(t){if(!t)return!1;const e=await fetch(i(`${s}/has-local-identity`,t));if(!e.ok)throw new Error(`Failed to check local identity: ${e.statusText}`);return(await e.json().catch(()=>null))?.hasLocalIdentity===!0}async function xe(){const t=await fetch(i(`${s}/global-identity`,void 0));if(!t.ok)throw new Error(`Failed to get global git identity: ${t.statusText}`);const e=await t.json();return!e||!e.userName&&!e.userEmail?null:{userName:e.userName??null,userEmail:e.userEmail??null,sshCommand:e.sshCommand??null}}async function pe(t,e){const r=await fetch(i(`${s}/set-identity`,t),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({profileId:e})});if(!r.ok){const o=await r.json().catch(()=>({error:r.statusText}));throw new Error(o.error||"Failed to set git identity")}return r.json()}async function $e(){const t=await fetch(i(`${s}/discover-credentials`,void 0));if(!t.ok)throw new Error(`Failed to discover git credentials: ${t.statusText}`);return t.json()}const we=()=>({checkIsGitRepository:L,getGitStatus:_,getGitDiff:W,getGitFileDiff:B,revertGitFile:q,isLinkedWorktree:M,getGitBranches:U,deleteGitBranch:z,deleteRemoteBranch:V,generateCommitMessage:K,listGitWorktrees:H,addGitWorktree:Q,removeGitWorktree:X,ensureOpenChamberIgnored:Y,createGitCommit(t,e,r){return Z(t,e,r)},gitPush:ee,gitPull:te,gitFetch:re,checkoutBranch:oe,createBranch:ne,renameBranch:se,getGitLog(t,e){return ie(t,e)},getCommitFiles:ae,getCurrentGitIdentity:he,hasLocalIdentity:fe,setGitIdentity:pe,getGitIdentities:ce,createGitIdentity:le,updateGitIdentity:de,deleteGitIdentity:ue}),u=t=>t.replace(/\\/g,"/"),ye=(t,e)=>{const r=u(e?.directory||e?.path||t),o=Array.isArray(e?.entries)?e.entries:[];return{directory:r,entries:o.filter(n=>!!(n&&typeof n.name=="string"&&typeof n.path=="string")).map(n=>({name:n.name,path:u(n.path),isDirectory:!!n.isDirectory}))}},me=()=>({async listDirectory(t){const e=u(t),r=new URLSearchParams;e&&r.set("path",e);const o=await fetch(`/api/fs/list${r.toString()?`?${r.toString()}`:""}`);if(!o.ok){const a=await o.json().catch(()=>({error:o.statusText}));throw new Error(a.error||"Failed to list directory")}const n=await o.json();return ye(e,n)},async search(t){const e=new URLSearchParams,r=u(t.directory);r&&e.set("directory",r),e.set("q",t.query),typeof t.maxResults=="number"&&Number.isFinite(t.maxResults)&&e.set("limit",String(t.maxResults)),t.includeHidden&&e.set("includeHidden","true");const o=await fetch(`/api/fs/search?${e.toString()}`);if(!o.ok){const c=await o.json().catch(()=>({error:o.statusText}));throw new Error(c.error||"Failed to search files")}const n=await o.json();return(Array.isArray(n?.files)?n.files:[]).filter(c=>!!(c&&typeof c.path=="string")).map(c=>({path:u(c.path),preview:typeof c.relativePath=="string"&&c.relativePath.length>0?[u(c.relativePath)]:void 0}))},async createDirectory(t){const e=u(t),r=await fetch("/api/fs/mkdir",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:e})});if(!r.ok){const n=await r.json().catch(()=>({error:r.statusText}));throw new Error(n.error||"Failed to create directory")}const o=await r.json();return{success:!!o?.success,path:typeof o?.path=="string"?u(o.path):e}},async readFile(t){const e=u(t),r=await fetch(`/api/fs/read?path=${encodeURIComponent(e)}`);if(!r.ok){const n=await r.json().catch(()=>({error:r.statusText}));throw new Error(n.error||"Failed to read file")}return{content:await r.text(),path:e}},async writeFile(t,e){const r=u(t),o=await fetch("/api/fs/write",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:r,content:e})});if(!o.ok){const a=await o.json().catch(()=>({error:o.statusText}));throw new Error(a.error||"Failed to write file")}const n=await o.json().catch(()=>({}));return{success:!!n.success,path:typeof n.path=="string"?u(n.path):r}},async delete(t){const e=u(t),r=await fetch("/api/fs/delete",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:e})});if(!r.ok){const n=await r.json().catch(()=>({error:r.statusText}));throw new Error(n.error||"Failed to delete file")}return{success:!!(await r.json().catch(()=>({}))).success}},async rename(t,e){const r=await fetch("/api/fs/rename",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldPath:t,newPath:e})});if(!r.ok){const n=await r.json().catch(()=>({error:r.statusText}));throw new Error(n.error||"Failed to rename file")}const o=await r.json().catch(()=>({}));return{success:!!o.success,path:typeof o.path=="string"?u(o.path):e}}}),P="/api/config/settings",ge="/api/config/reload",F=t=>!t||typeof t!="object"?{}:t,Te=()=>({async load(){const t=await fetch(P,{method:"GET",headers:{Accept:"application/json"}});if(!t.ok)throw new Error(`Failed to load settings: ${t.statusText}`);return{settings:F(await t.json().catch(()=>({}))),source:"web"}},async save(t){const e=await fetch(P,{method:"PUT",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(t)});if(!e.ok){const o=await e.json().catch(()=>({error:e.statusText}));throw new Error(o.error||"Failed to save settings")}return F(await e.json().catch(()=>({})))},async restartOpenCode(){const t=await fetch(ge,{method:"POST"});if(!t.ok){const e=await t.json().catch(()=>({error:t.statusText}));throw new Error(e.error||"Failed to restart OpenCode")}return{restarted:!0}}}),Ee=()=>({async requestDirectoryAccess(t){return{success:!0,path:t.path}},async startAccessingDirectory(t){return{success:!0}},async stopAccessingDirectory(t){return{success:!0}}}),je=async t=>{if(typeof Notification>"u")return console.info("Notifications not supported in this environment",t),!1;if(Notification.permission==="default"&&await Notification.requestPermission()!=="granted"||Notification.permission!=="granted")return console.warn("Notification permission not granted"),!1;try{return new Notification(t?.title??"OpenChamber",{body:t?.body,tag:t?.tag}),!0}catch(e){return console.warn("Failed to send notification",e),!1}},be=()=>({async notifyAgentCompletion(t){return je(t)},canNotify:()=>typeof Notification<"u"?Notification.permission==="granted":!1}),Se=()=>({async getAvailableTools(){const t=await fetch("/api/experimental/tool/ids");if(!t.ok)throw new Error(`Tools API returned ${t.status} ${t.statusText}`);const e=await t.json();if(!Array.isArray(e))throw new Error("Tools API returned invalid data format");return e.filter(r=>typeof r=="string"&&r!=="invalid").sort()}}),E=async(t,e)=>{try{const r=await fetch(t,{...e,credentials:"include",headers:{Accept:"application/json",...e?.headers??{}}});return r.ok?await r.json():null}catch{return null}},ke=()=>({async getVapidPublicKey(){return E("/api/push/vapid-public-key")},async subscribe(t){return E("/api/push/subscribe",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})},async unsubscribe(t){return E("/api/push/subscribe",{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})},async setVisibility(t){return E("/api/push/visibility",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t),keepalive:!0})}}),Oe=()=>({runtime:{platform:"web",isDesktop:!1,isVSCode:!1,label:"web"},terminal:D(),git:we(),files:me(),settings:Te(),permissions:Ee(),notifications:be(),push:ke(),tools:Se()});function Pe(t={}){const{immediate:e=!1,onNeedRefresh:r,onOfflineReady:o,onRegistered:n,onRegisteredSW:a,onRegisterError:c}=t;let p,d;const h=async(l=!0)=>{await d};async function w(){if("serviceWorker"in navigator){if(p=await x(async()=>{const{Workbox:l}=await import("./vendor-.bun-BrcdMJ39.js").then(y=>y.da);return{Workbox:l}},__vite__mapDeps([0,1])).then(({Workbox:l})=>new l("/sw.js",{scope:"/",type:"classic"})).catch(l=>{c?.(l)}),!p)return;p.addEventListener("activated",l=>{(l.isUpdate||l.isExternal)&&window.location.reload()}),p.addEventListener("installed",l=>{l.isUpdate||o?.()}),p.register({immediate:e}).then(l=>{a?a("/sw.js",l):n?.(l)}).catch(l=>{c?.(l)})}}return d=w(),h}window.__OPENCHAMBER_RUNTIME_APIS__=Oe();Pe({onRegistered(t){t&&setInterval(()=>{t.update()},3600*1e3)},onRegisterError(t){console.warn("[PWA] service worker registration failed:",t)}});x(()=>import("./main-Co1JTQ7W.js").then(t=>t.m),__vite__mapDeps([2,0,1,3]));export{V as a,Q as b,L as c,z as d,ue as e,le as f,_ as g,$e as h,xe as i,ce as j,Y as k,H as l,U as m,X as r,de as u};