@openchamber/web 1.11.6 → 1.11.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.
- package/README.md +6 -0
- package/bin/cli.js +443 -2
- package/dist/assets/{MarkdownRendererImpl-COdbjw73.js → MarkdownRendererImpl-DaF15QNC.js} +3 -3
- package/dist/assets/{MultiRunWindow-BKSHxjMq.js → MultiRunWindow-Cl7wS_CB.js} +1 -1
- package/dist/assets/{OnboardingScreen-Chjg337p.js → OnboardingScreen-DTv6YJI1.js} +2 -2
- package/dist/assets/{SettingsWindow-C0lRRW8M.js → SettingsWindow-_c3TTL2z.js} +1 -1
- package/dist/assets/{TerminalView-Bvil3j1u.js → TerminalView-CuXkDROt.js} +3 -3
- package/dist/assets/es-CYoUf2D-.js +15 -0
- package/dist/assets/{index-B9LvUHdG.js → index-3WXrN3AX.js} +1 -1
- package/dist/assets/index-BREIbhcb.css +1 -0
- package/dist/assets/ko-2tM0fIna.js +15 -0
- package/dist/assets/main-BF3kWAJ9.js +239 -0
- package/dist/assets/{main-Blhx9Fp5.js → main-o8ZERrmU.js} +2 -2
- package/dist/assets/miniChat-BZQjpK23.js +2 -0
- package/dist/assets/{modelPrefsAutoSave-DRJSYigo.js → modelPrefsAutoSave-wwnbqBk7.js} +110 -108
- package/dist/assets/pl-Dq8uAotM.js +15 -0
- package/dist/assets/pt-BR-nh9s9DFT.js +15 -0
- package/dist/assets/{renderElectronMiniChatApp-BxZRI73j.js → renderElectronMiniChatApp-C-Ezew9P.js} +2 -2
- package/dist/assets/uk-BZtz0wUV.js +15 -0
- package/dist/assets/{vendor-.bun-Bum-iBXX.js → vendor-.bun-CV3tusA8.js} +1 -1
- package/dist/assets/zh-CN-j_nYMchE.js +15 -0
- package/dist/assets/zh-TW-B11UpkDJ.js +15 -0
- package/dist/index.html +11 -28
- package/dist/mini-chat.html +4 -4
- package/package.json +1 -1
- package/server/lib/fs/routes.js +5 -0
- package/server/lib/fs/routes.test.js +61 -1
- package/server/lib/git/DOCUMENTATION.md +1 -0
- package/server/lib/git/routes.js +82 -1
- package/server/lib/git/service.js +338 -19
- package/server/lib/git/service.test.js +414 -8
- package/server/lib/opencode/env-runtime.js +52 -4
- package/server/lib/opencode/env-runtime.test.js +82 -6
- package/server/lib/opencode/openchamber-routes.js +9 -7
- package/server/lib/opencode/settings-helpers.js +3 -0
- package/server/lib/opencode/settings-runtime.js +39 -1
- package/server/lib/opencode/settings-runtime.test.js +39 -0
- package/server/lib/skills-catalog/source.js +1 -1
- package/dist/assets/es-BZIAUghG.js +0 -15
- package/dist/assets/index-UcCH2KN9.css +0 -1
- package/dist/assets/ko-DU9l-zox.js +0 -15
- package/dist/assets/main-d2-dY4er.js +0 -232
- package/dist/assets/miniChat-CJ7-rZFl.js +0 -2
- package/dist/assets/pl-CdqzokG-.js +0 -15
- package/dist/assets/pt-BR-Bknbr_Y3.js +0 -15
- package/dist/assets/uk-Be4E8ZNO.js +0 -15
- package/dist/assets/zh-CN-qpPiaZMg.js +0 -15
package/dist/index.html
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<html lang="en" class="h-full">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover, interactive-widget=resizes-content" />
|
|
6
6
|
|
|
7
7
|
<!-- Favicon -->
|
|
8
8
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
@@ -57,46 +57,29 @@
|
|
|
57
57
|
return fallback;
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
-
const normalizeMobileKeyboardMode = (value, fallback = '
|
|
60
|
+
const normalizeMobileKeyboardMode = (value, fallback = 'resize-content') => {
|
|
61
61
|
if (value === 'native' || value === 'resize-content') {
|
|
62
62
|
return value;
|
|
63
63
|
}
|
|
64
64
|
return fallback;
|
|
65
65
|
};
|
|
66
66
|
|
|
67
|
-
const supportsMobileKeyboardResizeContent = () => {
|
|
68
|
-
const userAgent = navigator.userAgent || '';
|
|
69
|
-
const platform = navigator.platform || '';
|
|
70
|
-
const maxTouchPoints = navigator.maxTouchPoints || 0;
|
|
71
|
-
const isIOS = /iPhone|iPad|iPod/i.test(userAgent)
|
|
72
|
-
|| ((/Macintosh|MacIntel/i.test(userAgent) || /MacIntel/i.test(platform)) && maxTouchPoints > 1);
|
|
73
|
-
|
|
74
|
-
return !isIOS;
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
const getSupportedMobileKeyboardMode = (mode) => {
|
|
78
|
-
if (mode === 'resize-content' && !supportsMobileKeyboardResizeContent()) {
|
|
79
|
-
return 'native';
|
|
80
|
-
}
|
|
81
|
-
return mode;
|
|
82
|
-
};
|
|
83
|
-
|
|
84
67
|
const getViewportContentForMobileKeyboardMode = (value) => {
|
|
85
|
-
const mode =
|
|
68
|
+
const mode = normalizeMobileKeyboardMode(value, 'resize-content');
|
|
86
69
|
return mode === 'resize-content'
|
|
87
70
|
? `${viewportBaseContent}, interactive-widget=resizes-content`
|
|
88
71
|
: viewportBaseContent;
|
|
89
72
|
};
|
|
90
73
|
|
|
91
74
|
const applyStoredMobileKeyboardMode = () => {
|
|
92
|
-
let mode = '
|
|
75
|
+
let mode = 'resize-content';
|
|
93
76
|
try {
|
|
94
|
-
mode =
|
|
95
|
-
if (mode === '
|
|
77
|
+
mode = normalizeMobileKeyboardMode(localStorage.getItem(mobileKeyboardModeStorageKey), 'resize-content');
|
|
78
|
+
if (mode === 'resize-content') {
|
|
96
79
|
localStorage.removeItem(mobileKeyboardModeStorageKey);
|
|
97
80
|
}
|
|
98
81
|
} catch {
|
|
99
|
-
mode = '
|
|
82
|
+
mode = 'resize-content';
|
|
100
83
|
}
|
|
101
84
|
|
|
102
85
|
document.documentElement.setAttribute('data-oc-mobile-keyboard-mode', mode);
|
|
@@ -532,10 +515,10 @@
|
|
|
532
515
|
pointer-events: none;
|
|
533
516
|
}
|
|
534
517
|
</style>
|
|
535
|
-
<script type="module" crossorigin src="/assets/main-
|
|
536
|
-
<link rel="modulepreload" crossorigin href="/assets/index-
|
|
537
|
-
<link rel="modulepreload" crossorigin href="/assets/vendor-.bun-
|
|
538
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
518
|
+
<script type="module" crossorigin src="/assets/main-o8ZERrmU.js"></script>
|
|
519
|
+
<link rel="modulepreload" crossorigin href="/assets/index-3WXrN3AX.js">
|
|
520
|
+
<link rel="modulepreload" crossorigin href="/assets/vendor-.bun-CV3tusA8.js">
|
|
521
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BREIbhcb.css">
|
|
539
522
|
<link rel="stylesheet" crossorigin href="/assets/vendor--V65Sl9C2.css">
|
|
540
523
|
</head>
|
|
541
524
|
<body class="h-full bg-background text-foreground">
|
package/dist/mini-chat.html
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
|
|
6
6
|
<title>OpenChamber Mini Chat</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/miniChat-
|
|
8
|
-
<link rel="modulepreload" crossorigin href="/assets/index-
|
|
9
|
-
<link rel="modulepreload" crossorigin href="/assets/vendor-.bun-
|
|
10
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/miniChat-BZQjpK23.js"></script>
|
|
8
|
+
<link rel="modulepreload" crossorigin href="/assets/index-3WXrN3AX.js">
|
|
9
|
+
<link rel="modulepreload" crossorigin href="/assets/vendor-.bun-CV3tusA8.js">
|
|
10
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BREIbhcb.css">
|
|
11
11
|
<link rel="stylesheet" crossorigin href="/assets/vendor--V65Sl9C2.css">
|
|
12
12
|
</head>
|
|
13
13
|
<body class="h-full bg-background text-foreground">
|
package/package.json
CHANGED
package/server/lib/fs/routes.js
CHANGED
|
@@ -772,6 +772,11 @@ export const registerFsRoutes = (app, dependencies) => {
|
|
|
772
772
|
return res.status(400).json({ error: resolved.error });
|
|
773
773
|
}
|
|
774
774
|
|
|
775
|
+
const existing = await fsPromises.readFile(resolved.resolved, 'utf8').catch(() => null);
|
|
776
|
+
if (existing === content) {
|
|
777
|
+
return res.json({ success: true, path: resolved.resolved });
|
|
778
|
+
}
|
|
779
|
+
|
|
775
780
|
await fsPromises.mkdir(path.dirname(resolved.resolved), { recursive: true });
|
|
776
781
|
await fsPromises.writeFile(resolved.resolved, content, 'utf8');
|
|
777
782
|
return res.json({ success: true, path: resolved.resolved });
|
|
@@ -90,7 +90,10 @@ const registerExec = ({ spawn }) => {
|
|
|
90
90
|
registerFsRoutes(app, {
|
|
91
91
|
os: { homedir: () => '/home/user' },
|
|
92
92
|
path,
|
|
93
|
-
fsPromises: {
|
|
93
|
+
fsPromises: {
|
|
94
|
+
realpath: async (targetPath) => targetPath,
|
|
95
|
+
stat: async () => ({ isDirectory: () => true }),
|
|
96
|
+
},
|
|
94
97
|
spawn,
|
|
95
98
|
crypto: { randomUUID: (() => { let n = 0; return () => `job-${n++}`; })() },
|
|
96
99
|
normalizeDirectoryPath: (p) => p,
|
|
@@ -102,12 +105,69 @@ const registerExec = ({ spawn }) => {
|
|
|
102
105
|
return getRoute('POST', '/api/fs/exec');
|
|
103
106
|
};
|
|
104
107
|
|
|
108
|
+
const registerWrite = (fsPromises) => {
|
|
109
|
+
const { app, getRoute } = createRouteRegistry();
|
|
110
|
+
registerFsRoutes(app, {
|
|
111
|
+
os: { homedir: () => '/home/user' },
|
|
112
|
+
path: path.posix,
|
|
113
|
+
fsPromises: {
|
|
114
|
+
realpath: async (targetPath) => targetPath,
|
|
115
|
+
...fsPromises,
|
|
116
|
+
},
|
|
117
|
+
spawn: vi.fn(),
|
|
118
|
+
crypto: { randomUUID: () => 'job-0' },
|
|
119
|
+
normalizeDirectoryPath: (p) => p,
|
|
120
|
+
resolveProjectDirectory: async () => ({ directory: '/repo' }),
|
|
121
|
+
buildAugmentedPath: () => '/usr/bin',
|
|
122
|
+
resolveGitBinaryForSpawn: () => 'git',
|
|
123
|
+
openchamberUserConfigRoot: '/home/user/.config',
|
|
124
|
+
});
|
|
125
|
+
return getRoute('POST', '/api/fs/write');
|
|
126
|
+
};
|
|
127
|
+
|
|
105
128
|
const callExec = async (handler, body) => {
|
|
106
129
|
const res = createMockResponse();
|
|
107
130
|
await handler({ body }, res);
|
|
108
131
|
return res;
|
|
109
132
|
};
|
|
110
133
|
|
|
134
|
+
const callWrite = async (handler, body) => {
|
|
135
|
+
const res = createMockResponse();
|
|
136
|
+
await handler({ body }, res);
|
|
137
|
+
return res;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
describe('fs write', () => {
|
|
141
|
+
it('does not rewrite a file when content is unchanged', async () => {
|
|
142
|
+
const fsPromises = {
|
|
143
|
+
readFile: vi.fn(async () => 'same'),
|
|
144
|
+
mkdir: vi.fn(async () => undefined),
|
|
145
|
+
writeFile: vi.fn(async () => undefined),
|
|
146
|
+
};
|
|
147
|
+
const handler = registerWrite(fsPromises);
|
|
148
|
+
|
|
149
|
+
const res = await callWrite(handler, { path: '/repo/file.txt', content: 'same' });
|
|
150
|
+
|
|
151
|
+
expect(res.body).toEqual({ success: true, path: '/repo/file.txt' });
|
|
152
|
+
expect(fsPromises.writeFile).not.toHaveBeenCalled();
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('writes a file when content changed', async () => {
|
|
156
|
+
const fsPromises = {
|
|
157
|
+
readFile: vi.fn(async () => 'old'),
|
|
158
|
+
mkdir: vi.fn(async () => undefined),
|
|
159
|
+
writeFile: vi.fn(async () => undefined),
|
|
160
|
+
};
|
|
161
|
+
const handler = registerWrite(fsPromises);
|
|
162
|
+
|
|
163
|
+
const res = await callWrite(handler, { path: '/repo/file.txt', content: 'new' });
|
|
164
|
+
|
|
165
|
+
expect(res.body).toEqual({ success: true, path: '/repo/file.txt' });
|
|
166
|
+
expect(fsPromises.mkdir).toHaveBeenCalledWith('/repo', { recursive: true });
|
|
167
|
+
expect(fsPromises.writeFile).toHaveBeenCalledWith('/repo/file.txt', 'new', 'utf8');
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
111
171
|
describe('fs exec git-read cache', () => {
|
|
112
172
|
beforeEach(() => {
|
|
113
173
|
delete process.env.OPENCHAMBER_GIT_READ_CACHE_TTL_MS;
|
|
@@ -101,6 +101,7 @@ The following functions are internal helpers used by exported functions:
|
|
|
101
101
|
- `tracking`: Upstream branch (e.g., 'origin/main').
|
|
102
102
|
- `ahead`: Number of commits ahead of upstream.
|
|
103
103
|
- `behind`: Number of commits behind upstream.
|
|
104
|
+
- `upstreamComparison`: Optional comparison against `upstream/<current-branch>`, with `{ remote, branch, ahead, behind }`.
|
|
104
105
|
- `files`: Array of file objects with `path`, `index`, `working_dir` status codes.
|
|
105
106
|
- `isClean`: Boolean indicating if working tree is clean.
|
|
106
107
|
- `diffStats`: Object mapping file paths to `{ insertions, deletions }`.
|
package/server/lib/git/routes.js
CHANGED
|
@@ -766,6 +766,85 @@ export function registerGitRoutes(app) {
|
|
|
766
766
|
}
|
|
767
767
|
});
|
|
768
768
|
|
|
769
|
+
app.post('/api/git/checkout-commit', async (req, res) => {
|
|
770
|
+
const { checkoutCommit } = await getGitLibraries();
|
|
771
|
+
try {
|
|
772
|
+
const directory = req.query.directory;
|
|
773
|
+
if (!directory) {
|
|
774
|
+
return res.status(400).json({ error: 'directory parameter is required' });
|
|
775
|
+
}
|
|
776
|
+
const { hash } = req.body;
|
|
777
|
+
if (!req.body.hash || typeof req.body.hash !== 'string' || !/^[0-9a-fA-F]{7,40}$/.test(req.body.hash)) {
|
|
778
|
+
return res.status(400).json({ error: 'Invalid commit hash' });
|
|
779
|
+
}
|
|
780
|
+
const result = await checkoutCommit(directory, hash);
|
|
781
|
+
res.json(result);
|
|
782
|
+
} catch (error) {
|
|
783
|
+
console.error('Failed to checkout commit:', error);
|
|
784
|
+
res.status(500).json({ error: error.message || 'Failed to checkout commit' });
|
|
785
|
+
}
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
app.post('/api/git/cherry-pick', async (req, res) => {
|
|
789
|
+
const { cherryPick } = await getGitLibraries();
|
|
790
|
+
try {
|
|
791
|
+
const directory = req.query.directory;
|
|
792
|
+
if (!directory) {
|
|
793
|
+
return res.status(400).json({ error: 'directory parameter is required' });
|
|
794
|
+
}
|
|
795
|
+
const { hash } = req.body;
|
|
796
|
+
if (!req.body.hash || typeof req.body.hash !== 'string' || !/^[0-9a-fA-F]{7,40}$/.test(req.body.hash)) {
|
|
797
|
+
return res.status(400).json({ error: 'Invalid commit hash' });
|
|
798
|
+
}
|
|
799
|
+
const result = await cherryPick(directory, hash);
|
|
800
|
+
res.json(result);
|
|
801
|
+
} catch (error) {
|
|
802
|
+
console.error('Failed to cherry-pick:', error);
|
|
803
|
+
res.status(500).json({ error: error.message || 'Failed to cherry-pick' });
|
|
804
|
+
}
|
|
805
|
+
});
|
|
806
|
+
|
|
807
|
+
app.post('/api/git/revert-commit', async (req, res) => {
|
|
808
|
+
const { revertCommit } = await getGitLibraries();
|
|
809
|
+
try {
|
|
810
|
+
const directory = req.query.directory;
|
|
811
|
+
if (!directory) {
|
|
812
|
+
return res.status(400).json({ error: 'directory parameter is required' });
|
|
813
|
+
}
|
|
814
|
+
const { hash } = req.body;
|
|
815
|
+
if (!req.body.hash || typeof req.body.hash !== 'string' || !/^[0-9a-fA-F]{7,40}$/.test(req.body.hash)) {
|
|
816
|
+
return res.status(400).json({ error: 'Invalid commit hash' });
|
|
817
|
+
}
|
|
818
|
+
const result = await revertCommit(directory, hash);
|
|
819
|
+
res.json(result);
|
|
820
|
+
} catch (error) {
|
|
821
|
+
console.error('Failed to revert commit:', error);
|
|
822
|
+
res.status(500).json({ error: error.message || 'Failed to revert commit' });
|
|
823
|
+
}
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
app.post('/api/git/reset-to-commit', async (req, res) => {
|
|
827
|
+
const { resetToCommit } = await getGitLibraries();
|
|
828
|
+
try {
|
|
829
|
+
const directory = req.query.directory;
|
|
830
|
+
if (!directory) {
|
|
831
|
+
return res.status(400).json({ error: 'directory parameter is required' });
|
|
832
|
+
}
|
|
833
|
+
const { hash, mode, force } = req.body;
|
|
834
|
+
if (!req.body.hash || typeof req.body.hash !== 'string' || !/^[0-9a-fA-F]{7,40}$/.test(req.body.hash)) {
|
|
835
|
+
return res.status(400).json({ error: 'Invalid commit hash' });
|
|
836
|
+
}
|
|
837
|
+
if (!['soft', 'mixed', 'hard'].includes(mode)) {
|
|
838
|
+
return res.status(400).json({ error: 'mode must be soft, mixed, or hard' });
|
|
839
|
+
}
|
|
840
|
+
const result = await resetToCommit(directory, hash, mode, force === true);
|
|
841
|
+
res.json(result);
|
|
842
|
+
} catch (error) {
|
|
843
|
+
console.error('Failed to reset to commit:', error);
|
|
844
|
+
res.status(500).json({ error: error.message || 'Failed to reset' });
|
|
845
|
+
}
|
|
846
|
+
});
|
|
847
|
+
|
|
769
848
|
app.get('/api/git/worktrees', async (req, res) => {
|
|
770
849
|
const { getWorktrees } = await getGitLibraries();
|
|
771
850
|
try {
|
|
@@ -956,11 +1035,13 @@ export function registerGitRoutes(app) {
|
|
|
956
1035
|
}
|
|
957
1036
|
|
|
958
1037
|
const { maxCount, from, to, file } = req.query;
|
|
1038
|
+
const all = req.query.all === 'true';
|
|
959
1039
|
const log = await getLog(directory, {
|
|
960
1040
|
maxCount: maxCount ? parseInt(maxCount) : undefined,
|
|
961
1041
|
from,
|
|
962
1042
|
to,
|
|
963
|
-
file
|
|
1043
|
+
file,
|
|
1044
|
+
all
|
|
964
1045
|
});
|
|
965
1046
|
res.json(log);
|
|
966
1047
|
} catch (error) {
|