@openacp/cli 0.2.16 → 0.2.18
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/dist/chunk-3UGSZYSQ.js +278 -0
- package/dist/chunk-3UGSZYSQ.js.map +1 -0
- package/dist/{chunk-TKOYKVXH.js → chunk-HTXK4NLG.js} +654 -82
- package/dist/chunk-HTXK4NLG.js.map +1 -0
- package/dist/chunk-ZATQZUJT.js +193 -0
- package/dist/chunk-ZATQZUJT.js.map +1 -0
- package/dist/cli.js +5 -2
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +206 -15
- package/dist/index.js +10 -8
- package/dist/{main-C4QWS4PK.js → main-4H43GCXO.js} +16 -5
- package/dist/main-4H43GCXO.js.map +1 -0
- package/dist/{setup-5RQ7YLYW.js → setup-FB4DGR6R.js} +12 -2
- package/dist/{setup-5RQ7YLYW.js.map → setup-FB4DGR6R.js.map} +1 -1
- package/dist/tunnel-service-FPRPBPQ5.js +633 -0
- package/dist/tunnel-service-FPRPBPQ5.js.map +1 -0
- package/package.json +5 -1
- package/dist/chunk-HTUZOMIT.js +0 -421
- package/dist/chunk-HTUZOMIT.js.map +0 -1
- package/dist/chunk-TKOYKVXH.js.map +0 -1
- package/dist/main-C4QWS4PK.js.map +0 -1
|
@@ -0,0 +1,633 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createChildLogger
|
|
3
|
+
} from "./chunk-ZATQZUJT.js";
|
|
4
|
+
|
|
5
|
+
// src/tunnel/tunnel-service.ts
|
|
6
|
+
import { serve } from "@hono/node-server";
|
|
7
|
+
|
|
8
|
+
// src/tunnel/providers/cloudflare.ts
|
|
9
|
+
import { spawn } from "child_process";
|
|
10
|
+
var log = createChildLogger({ module: "cloudflare-tunnel" });
|
|
11
|
+
var CloudflareTunnelProvider = class {
|
|
12
|
+
child = null;
|
|
13
|
+
publicUrl = "";
|
|
14
|
+
options;
|
|
15
|
+
constructor(options = {}) {
|
|
16
|
+
this.options = options;
|
|
17
|
+
}
|
|
18
|
+
async start(localPort) {
|
|
19
|
+
const args = ["tunnel", "--url", `http://localhost:${localPort}`];
|
|
20
|
+
if (this.options.domain) {
|
|
21
|
+
args.push("--hostname", String(this.options.domain));
|
|
22
|
+
}
|
|
23
|
+
return new Promise((resolve2, reject) => {
|
|
24
|
+
const timeout = setTimeout(() => {
|
|
25
|
+
this.stop();
|
|
26
|
+
reject(new Error("Cloudflare tunnel timed out after 30s. Is cloudflared installed?"));
|
|
27
|
+
}, 3e4);
|
|
28
|
+
try {
|
|
29
|
+
this.child = spawn("cloudflared", args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
30
|
+
} catch {
|
|
31
|
+
clearTimeout(timeout);
|
|
32
|
+
reject(new Error(
|
|
33
|
+
"Failed to start cloudflared. Install it from https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation/"
|
|
34
|
+
));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const urlPattern = /https:\/\/[a-zA-Z0-9-]+\.trycloudflare\.com/;
|
|
38
|
+
const onData = (data) => {
|
|
39
|
+
const line = data.toString();
|
|
40
|
+
log.debug(line.trim());
|
|
41
|
+
const match = line.match(urlPattern);
|
|
42
|
+
if (match) {
|
|
43
|
+
clearTimeout(timeout);
|
|
44
|
+
this.publicUrl = match[0];
|
|
45
|
+
log.info({ url: this.publicUrl }, "Cloudflare tunnel ready");
|
|
46
|
+
resolve2(this.publicUrl);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
this.child.stdout?.on("data", onData);
|
|
50
|
+
this.child.stderr?.on("data", onData);
|
|
51
|
+
this.child.on("error", (err) => {
|
|
52
|
+
clearTimeout(timeout);
|
|
53
|
+
reject(new Error(
|
|
54
|
+
`cloudflared failed to start: ${err.message}. Install it from https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation/`
|
|
55
|
+
));
|
|
56
|
+
});
|
|
57
|
+
this.child.on("exit", (code) => {
|
|
58
|
+
if (!this.publicUrl) {
|
|
59
|
+
clearTimeout(timeout);
|
|
60
|
+
reject(new Error(`cloudflared exited with code ${code} before establishing tunnel`));
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
async stop() {
|
|
66
|
+
if (this.child) {
|
|
67
|
+
this.child.kill("SIGTERM");
|
|
68
|
+
this.child = null;
|
|
69
|
+
log.info("Cloudflare tunnel stopped");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
getPublicUrl() {
|
|
73
|
+
return this.publicUrl;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// src/tunnel/viewer-store.ts
|
|
78
|
+
import * as path from "path";
|
|
79
|
+
import { nanoid } from "nanoid";
|
|
80
|
+
var log2 = createChildLogger({ module: "viewer-store" });
|
|
81
|
+
var MAX_CONTENT_SIZE = 1e6;
|
|
82
|
+
var EXTENSION_LANGUAGE = {
|
|
83
|
+
".ts": "typescript",
|
|
84
|
+
".tsx": "typescript",
|
|
85
|
+
".js": "javascript",
|
|
86
|
+
".jsx": "javascript",
|
|
87
|
+
".py": "python",
|
|
88
|
+
".rs": "rust",
|
|
89
|
+
".go": "go",
|
|
90
|
+
".java": "java",
|
|
91
|
+
".kt": "kotlin",
|
|
92
|
+
".rb": "ruby",
|
|
93
|
+
".php": "php",
|
|
94
|
+
".c": "c",
|
|
95
|
+
".cpp": "cpp",
|
|
96
|
+
".h": "c",
|
|
97
|
+
".hpp": "cpp",
|
|
98
|
+
".cs": "csharp",
|
|
99
|
+
".swift": "swift",
|
|
100
|
+
".sh": "bash",
|
|
101
|
+
".zsh": "bash",
|
|
102
|
+
".bash": "bash",
|
|
103
|
+
".json": "json",
|
|
104
|
+
".yaml": "yaml",
|
|
105
|
+
".yml": "yaml",
|
|
106
|
+
".toml": "toml",
|
|
107
|
+
".xml": "xml",
|
|
108
|
+
".html": "html",
|
|
109
|
+
".css": "css",
|
|
110
|
+
".scss": "scss",
|
|
111
|
+
".sql": "sql",
|
|
112
|
+
".md": "markdown",
|
|
113
|
+
".dockerfile": "dockerfile",
|
|
114
|
+
".tf": "hcl",
|
|
115
|
+
".vue": "xml",
|
|
116
|
+
".svelte": "xml"
|
|
117
|
+
};
|
|
118
|
+
var ViewerStore = class {
|
|
119
|
+
entries = /* @__PURE__ */ new Map();
|
|
120
|
+
cleanupTimer;
|
|
121
|
+
ttlMs;
|
|
122
|
+
constructor(ttlMinutes = 60) {
|
|
123
|
+
this.ttlMs = ttlMinutes * 60 * 1e3;
|
|
124
|
+
this.cleanupTimer = setInterval(() => this.cleanup(), 5 * 60 * 1e3);
|
|
125
|
+
}
|
|
126
|
+
storeFile(sessionId, filePath, content, workingDirectory) {
|
|
127
|
+
if (!this.isPathAllowed(filePath, workingDirectory)) {
|
|
128
|
+
log2.warn({ filePath, workingDirectory }, "Path outside workspace, rejecting");
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
if (content.length > MAX_CONTENT_SIZE) {
|
|
132
|
+
log2.debug({ filePath, size: content.length }, "File too large for viewer");
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
const id = nanoid(12);
|
|
136
|
+
const now = Date.now();
|
|
137
|
+
this.entries.set(id, {
|
|
138
|
+
id,
|
|
139
|
+
type: "file",
|
|
140
|
+
filePath,
|
|
141
|
+
content,
|
|
142
|
+
language: this.detectLanguage(filePath),
|
|
143
|
+
sessionId,
|
|
144
|
+
workingDirectory,
|
|
145
|
+
createdAt: now,
|
|
146
|
+
expiresAt: now + this.ttlMs
|
|
147
|
+
});
|
|
148
|
+
log2.debug({ id, filePath }, "Stored file for viewing");
|
|
149
|
+
return id;
|
|
150
|
+
}
|
|
151
|
+
storeDiff(sessionId, filePath, oldContent, newContent, workingDirectory) {
|
|
152
|
+
if (!this.isPathAllowed(filePath, workingDirectory)) {
|
|
153
|
+
log2.warn({ filePath, workingDirectory }, "Path outside workspace, rejecting");
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
const combined = oldContent.length + newContent.length;
|
|
157
|
+
if (combined > MAX_CONTENT_SIZE) {
|
|
158
|
+
log2.debug({ filePath, size: combined }, "Diff content too large for viewer");
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
const id = nanoid(12);
|
|
162
|
+
const now = Date.now();
|
|
163
|
+
this.entries.set(id, {
|
|
164
|
+
id,
|
|
165
|
+
type: "diff",
|
|
166
|
+
filePath,
|
|
167
|
+
content: newContent,
|
|
168
|
+
oldContent,
|
|
169
|
+
language: this.detectLanguage(filePath),
|
|
170
|
+
sessionId,
|
|
171
|
+
workingDirectory,
|
|
172
|
+
createdAt: now,
|
|
173
|
+
expiresAt: now + this.ttlMs
|
|
174
|
+
});
|
|
175
|
+
log2.debug({ id, filePath }, "Stored diff for viewing");
|
|
176
|
+
return id;
|
|
177
|
+
}
|
|
178
|
+
get(id) {
|
|
179
|
+
const entry = this.entries.get(id);
|
|
180
|
+
if (!entry) return void 0;
|
|
181
|
+
if (Date.now() > entry.expiresAt) {
|
|
182
|
+
this.entries.delete(id);
|
|
183
|
+
return void 0;
|
|
184
|
+
}
|
|
185
|
+
return entry;
|
|
186
|
+
}
|
|
187
|
+
cleanup() {
|
|
188
|
+
const now = Date.now();
|
|
189
|
+
let removed = 0;
|
|
190
|
+
for (const [id, entry] of this.entries) {
|
|
191
|
+
if (now > entry.expiresAt) {
|
|
192
|
+
this.entries.delete(id);
|
|
193
|
+
removed++;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (removed > 0) {
|
|
197
|
+
log2.debug({ removed, remaining: this.entries.size }, "Cleaned up expired viewer entries");
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
isPathAllowed(filePath, workingDirectory) {
|
|
201
|
+
const resolved = path.resolve(workingDirectory, filePath);
|
|
202
|
+
return resolved.startsWith(path.resolve(workingDirectory));
|
|
203
|
+
}
|
|
204
|
+
detectLanguage(filePath) {
|
|
205
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
206
|
+
return EXTENSION_LANGUAGE[ext];
|
|
207
|
+
}
|
|
208
|
+
destroy() {
|
|
209
|
+
clearInterval(this.cleanupTimer);
|
|
210
|
+
this.entries.clear();
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
// src/tunnel/server.ts
|
|
215
|
+
import { Hono } from "hono";
|
|
216
|
+
|
|
217
|
+
// src/tunnel/templates/file-viewer.ts
|
|
218
|
+
function escapeHtml(text) {
|
|
219
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
220
|
+
}
|
|
221
|
+
var MONACO_LANGUAGE = {
|
|
222
|
+
typescript: "typescript",
|
|
223
|
+
javascript: "javascript",
|
|
224
|
+
python: "python",
|
|
225
|
+
rust: "rust",
|
|
226
|
+
go: "go",
|
|
227
|
+
java: "java",
|
|
228
|
+
kotlin: "kotlin",
|
|
229
|
+
ruby: "ruby",
|
|
230
|
+
php: "php",
|
|
231
|
+
c: "c",
|
|
232
|
+
cpp: "cpp",
|
|
233
|
+
csharp: "csharp",
|
|
234
|
+
swift: "swift",
|
|
235
|
+
bash: "shell",
|
|
236
|
+
json: "json",
|
|
237
|
+
yaml: "yaml",
|
|
238
|
+
toml: "ini",
|
|
239
|
+
xml: "xml",
|
|
240
|
+
html: "html",
|
|
241
|
+
css: "css",
|
|
242
|
+
scss: "scss",
|
|
243
|
+
sql: "sql",
|
|
244
|
+
markdown: "markdown",
|
|
245
|
+
dockerfile: "dockerfile",
|
|
246
|
+
hcl: "hcl",
|
|
247
|
+
plaintext: "plaintext"
|
|
248
|
+
};
|
|
249
|
+
function getMonacoLang(lang) {
|
|
250
|
+
if (!lang) return "plaintext";
|
|
251
|
+
return MONACO_LANGUAGE[lang] || "plaintext";
|
|
252
|
+
}
|
|
253
|
+
function renderFileViewer(entry) {
|
|
254
|
+
const fileName = entry.filePath || "untitled";
|
|
255
|
+
const lang = getMonacoLang(entry.language);
|
|
256
|
+
const safeContent = JSON.stringify(entry.content).replace(/<\//g, "<\\/");
|
|
257
|
+
return `<!DOCTYPE html>
|
|
258
|
+
<html lang="en">
|
|
259
|
+
<head>
|
|
260
|
+
<meta charset="UTF-8">
|
|
261
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
262
|
+
<title>${escapeHtml(fileName)} - OpenACP</title>
|
|
263
|
+
<style>
|
|
264
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
265
|
+
body { background: #1e1e1e; color: #d4d4d4; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; display: flex; flex-direction: column; height: 100vh; }
|
|
266
|
+
.header { background: #252526; border-bottom: 1px solid #3c3c3c; padding: 8px 16px; display: flex; align-items: center; justify-content: space-between; flex-shrink: 0; z-index: 10; }
|
|
267
|
+
.file-info { display: flex; align-items: center; gap: 8px; font-size: 13px; min-width: 0; }
|
|
268
|
+
.file-icon { font-size: 14px; flex-shrink: 0; }
|
|
269
|
+
.file-path { color: #969696; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
270
|
+
.file-name { color: #e0e0e0; font-weight: 500; flex-shrink: 0; }
|
|
271
|
+
.actions { display: flex; gap: 6px; flex-shrink: 0; }
|
|
272
|
+
.btn { background: #3c3c3c; color: #d4d4d4; border: 1px solid #505050; padding: 4px 10px; border-radius: 4px; cursor: pointer; font-size: 12px; transition: background 0.15s; }
|
|
273
|
+
.btn:hover { background: #505050; }
|
|
274
|
+
.btn.active { background: #0e639c; border-color: #1177bb; }
|
|
275
|
+
#editor-container { flex: 1; overflow: hidden; }
|
|
276
|
+
.status-bar { background: #007acc; color: #fff; padding: 2px 16px; font-size: 12px; display: flex; justify-content: space-between; flex-shrink: 0; }
|
|
277
|
+
</style>
|
|
278
|
+
</head>
|
|
279
|
+
<body>
|
|
280
|
+
<div class="header">
|
|
281
|
+
<div class="file-info">
|
|
282
|
+
<span class="file-icon">\u{1F4C4}</span>
|
|
283
|
+
${formatBreadcrumb(fileName)}
|
|
284
|
+
</div>
|
|
285
|
+
<div class="actions">
|
|
286
|
+
<button class="btn" onclick="toggleWordWrap()" id="btn-wrap">Wrap</button>
|
|
287
|
+
<button class="btn" onclick="toggleMinimap()" id="btn-minimap">Minimap</button>
|
|
288
|
+
<button class="btn" onclick="toggleTheme()" id="btn-theme">Light</button>
|
|
289
|
+
<button class="btn" onclick="copyCode()">Copy</button>
|
|
290
|
+
</div>
|
|
291
|
+
</div>
|
|
292
|
+
<div id="editor-container"></div>
|
|
293
|
+
<div class="status-bar">
|
|
294
|
+
<span>${escapeHtml(entry.language || "plaintext")} | ${entry.content.split("\n").length} lines</span>
|
|
295
|
+
<span>OpenACP Viewer (read-only)</span>
|
|
296
|
+
</div>
|
|
297
|
+
|
|
298
|
+
<script src="https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/vs/loader.js"></script>
|
|
299
|
+
<script>
|
|
300
|
+
const content = ${safeContent};
|
|
301
|
+
const lang = ${JSON.stringify(lang)};
|
|
302
|
+
let editor;
|
|
303
|
+
let isDark = true;
|
|
304
|
+
let wordWrap = false;
|
|
305
|
+
let minimap = true;
|
|
306
|
+
|
|
307
|
+
require.config({ paths: { vs: 'https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/vs' } });
|
|
308
|
+
require(['vs/editor/editor.main'], function () {
|
|
309
|
+
editor = monaco.editor.create(document.getElementById('editor-container'), {
|
|
310
|
+
value: content,
|
|
311
|
+
language: lang,
|
|
312
|
+
theme: 'vs-dark',
|
|
313
|
+
readOnly: true,
|
|
314
|
+
automaticLayout: true,
|
|
315
|
+
minimap: { enabled: true },
|
|
316
|
+
scrollBeyondLastLine: false,
|
|
317
|
+
fontSize: 13,
|
|
318
|
+
fontFamily: "'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace",
|
|
319
|
+
lineNumbers: 'on',
|
|
320
|
+
renderLineHighlight: 'all',
|
|
321
|
+
wordWrap: 'off',
|
|
322
|
+
padding: { top: 8 },
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// Handle line range from URL hash: #L42 or #L42-L55
|
|
326
|
+
function highlightFromHash() {
|
|
327
|
+
const hash = location.hash.slice(1);
|
|
328
|
+
const match = hash.match(/^L(\\d+)(?:-L?(\\d+))?$/);
|
|
329
|
+
if (!match) return;
|
|
330
|
+
const startLine = parseInt(match[1], 10);
|
|
331
|
+
const endLine = match[2] ? parseInt(match[2], 10) : startLine;
|
|
332
|
+
editor.revealLineInCenter(startLine);
|
|
333
|
+
editor.setSelection(new monaco.Selection(startLine, 1, endLine + 1, 1));
|
|
334
|
+
}
|
|
335
|
+
highlightFromHash();
|
|
336
|
+
window.addEventListener('hashchange', highlightFromHash);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
function toggleTheme() {
|
|
340
|
+
isDark = !isDark;
|
|
341
|
+
monaco.editor.setTheme(isDark ? 'vs-dark' : 'vs');
|
|
342
|
+
document.body.style.background = isDark ? '#1e1e1e' : '#ffffff';
|
|
343
|
+
document.querySelector('.header').style.background = isDark ? '#252526' : '#f3f3f3';
|
|
344
|
+
document.querySelector('.header').style.borderColor = isDark ? '#3c3c3c' : '#e0e0e0';
|
|
345
|
+
document.getElementById('btn-theme').textContent = isDark ? 'Light' : 'Dark';
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function toggleWordWrap() {
|
|
349
|
+
wordWrap = !wordWrap;
|
|
350
|
+
editor.updateOptions({ wordWrap: wordWrap ? 'on' : 'off' });
|
|
351
|
+
document.getElementById('btn-wrap').classList.toggle('active', wordWrap);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function toggleMinimap() {
|
|
355
|
+
minimap = !minimap;
|
|
356
|
+
editor.updateOptions({ minimap: { enabled: minimap } });
|
|
357
|
+
document.getElementById('btn-minimap').classList.toggle('active', !minimap);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function copyCode() {
|
|
361
|
+
navigator.clipboard.writeText(content).then(() => {
|
|
362
|
+
const btn = event.target;
|
|
363
|
+
btn.textContent = 'Copied!';
|
|
364
|
+
setTimeout(() => btn.textContent = 'Copy', 2000);
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
</script>
|
|
368
|
+
</body>
|
|
369
|
+
</html>`;
|
|
370
|
+
}
|
|
371
|
+
function formatBreadcrumb(filePath) {
|
|
372
|
+
const parts = filePath.split("/");
|
|
373
|
+
if (parts.length <= 1) return `<span class="file-name">${escapeHtml(filePath)}</span>`;
|
|
374
|
+
const dir = parts.slice(0, -1).join(" / ");
|
|
375
|
+
const name = parts[parts.length - 1];
|
|
376
|
+
return `<span class="file-path">${escapeHtml(dir)} /</span> <span class="file-name">${escapeHtml(name)}</span>`;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// src/tunnel/templates/diff-viewer.ts
|
|
380
|
+
import { createPatch } from "diff";
|
|
381
|
+
function escapeHtml2(text) {
|
|
382
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
383
|
+
}
|
|
384
|
+
var MONACO_LANGUAGE2 = {
|
|
385
|
+
typescript: "typescript",
|
|
386
|
+
javascript: "javascript",
|
|
387
|
+
python: "python",
|
|
388
|
+
rust: "rust",
|
|
389
|
+
go: "go",
|
|
390
|
+
java: "java",
|
|
391
|
+
kotlin: "kotlin",
|
|
392
|
+
ruby: "ruby",
|
|
393
|
+
php: "php",
|
|
394
|
+
c: "c",
|
|
395
|
+
cpp: "cpp",
|
|
396
|
+
csharp: "csharp",
|
|
397
|
+
swift: "swift",
|
|
398
|
+
bash: "shell",
|
|
399
|
+
json: "json",
|
|
400
|
+
yaml: "yaml",
|
|
401
|
+
toml: "ini",
|
|
402
|
+
xml: "xml",
|
|
403
|
+
html: "html",
|
|
404
|
+
css: "css",
|
|
405
|
+
scss: "scss",
|
|
406
|
+
sql: "sql",
|
|
407
|
+
markdown: "markdown",
|
|
408
|
+
dockerfile: "dockerfile",
|
|
409
|
+
hcl: "hcl",
|
|
410
|
+
plaintext: "plaintext"
|
|
411
|
+
};
|
|
412
|
+
function getMonacoLang2(lang) {
|
|
413
|
+
if (!lang) return "plaintext";
|
|
414
|
+
return MONACO_LANGUAGE2[lang] || "plaintext";
|
|
415
|
+
}
|
|
416
|
+
function renderDiffViewer(entry) {
|
|
417
|
+
const fileName = entry.filePath || "untitled";
|
|
418
|
+
const lang = getMonacoLang2(entry.language);
|
|
419
|
+
const oldContent = entry.oldContent || "";
|
|
420
|
+
const newContent = entry.content;
|
|
421
|
+
const patch = createPatch(fileName, oldContent, newContent, "before", "after");
|
|
422
|
+
const adds = (patch.match(/^\+[^+]/gm) || []).length;
|
|
423
|
+
const dels = (patch.match(/^-[^-]/gm) || []).length;
|
|
424
|
+
const safeOld = JSON.stringify(oldContent).replace(/<\//g, "<\\/");
|
|
425
|
+
const safeNew = JSON.stringify(newContent).replace(/<\//g, "<\\/");
|
|
426
|
+
return `<!DOCTYPE html>
|
|
427
|
+
<html lang="en">
|
|
428
|
+
<head>
|
|
429
|
+
<meta charset="UTF-8">
|
|
430
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
431
|
+
<title>${escapeHtml2(fileName)} (diff) - OpenACP</title>
|
|
432
|
+
<style>
|
|
433
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
434
|
+
body { background: #1e1e1e; color: #d4d4d4; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; display: flex; flex-direction: column; height: 100vh; }
|
|
435
|
+
.header { background: #252526; border-bottom: 1px solid #3c3c3c; padding: 8px 16px; display: flex; align-items: center; justify-content: space-between; flex-shrink: 0; z-index: 10; }
|
|
436
|
+
.file-info { display: flex; align-items: center; gap: 8px; font-size: 13px; }
|
|
437
|
+
.file-icon { font-size: 14px; }
|
|
438
|
+
.file-name { color: #e0e0e0; font-weight: 500; }
|
|
439
|
+
.stats { font-size: 12px; margin-left: 12px; }
|
|
440
|
+
.stats .add { color: #4ec9b0; }
|
|
441
|
+
.stats .del { color: #f14c4c; }
|
|
442
|
+
.actions { display: flex; gap: 6px; }
|
|
443
|
+
.btn { background: #3c3c3c; color: #d4d4d4; border: 1px solid #505050; padding: 4px 10px; border-radius: 4px; cursor: pointer; font-size: 12px; transition: background 0.15s; }
|
|
444
|
+
.btn:hover { background: #505050; }
|
|
445
|
+
.btn.active { background: #0e639c; border-color: #1177bb; }
|
|
446
|
+
#editor-container { flex: 1; overflow: hidden; }
|
|
447
|
+
.status-bar { background: #007acc; color: #fff; padding: 2px 16px; font-size: 12px; display: flex; justify-content: space-between; flex-shrink: 0; }
|
|
448
|
+
</style>
|
|
449
|
+
</head>
|
|
450
|
+
<body>
|
|
451
|
+
<div class="header">
|
|
452
|
+
<div class="file-info">
|
|
453
|
+
<span class="file-icon">\u{1F4DD}</span>
|
|
454
|
+
<span class="file-name">${escapeHtml2(fileName)}</span>
|
|
455
|
+
<span class="stats"><span class="add">+${adds}</span> / <span class="del">-${dels}</span></span>
|
|
456
|
+
</div>
|
|
457
|
+
<div class="actions">
|
|
458
|
+
<button class="btn active" id="btn-side" onclick="setView('side')">Side by Side</button>
|
|
459
|
+
<button class="btn" id="btn-inline" onclick="setView('inline')">Inline</button>
|
|
460
|
+
<button class="btn" onclick="toggleTheme()" id="btn-theme">Light</button>
|
|
461
|
+
</div>
|
|
462
|
+
</div>
|
|
463
|
+
<div id="editor-container"></div>
|
|
464
|
+
<div class="status-bar">
|
|
465
|
+
<span>${escapeHtml2(entry.language || "plaintext")} | <span class="add">+${adds}</span> <span class="del">-${dels}</span></span>
|
|
466
|
+
<span>OpenACP Diff Viewer</span>
|
|
467
|
+
</div>
|
|
468
|
+
|
|
469
|
+
<script src="https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/vs/loader.js"></script>
|
|
470
|
+
<script>
|
|
471
|
+
const oldContent = ${safeOld};
|
|
472
|
+
const newContent = ${safeNew};
|
|
473
|
+
const lang = ${JSON.stringify(lang)};
|
|
474
|
+
let diffEditor;
|
|
475
|
+
let isDark = true;
|
|
476
|
+
let renderSideBySide = true;
|
|
477
|
+
|
|
478
|
+
require.config({ paths: { vs: 'https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/vs' } });
|
|
479
|
+
require(['vs/editor/editor.main'], function () {
|
|
480
|
+
const originalModel = monaco.editor.createModel(oldContent, lang);
|
|
481
|
+
const modifiedModel = monaco.editor.createModel(newContent, lang);
|
|
482
|
+
|
|
483
|
+
diffEditor = monaco.editor.createDiffEditor(document.getElementById('editor-container'), {
|
|
484
|
+
theme: 'vs-dark',
|
|
485
|
+
readOnly: true,
|
|
486
|
+
automaticLayout: true,
|
|
487
|
+
renderSideBySide: true,
|
|
488
|
+
scrollBeyondLastLine: false,
|
|
489
|
+
fontSize: 13,
|
|
490
|
+
fontFamily: "'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace",
|
|
491
|
+
padding: { top: 8 },
|
|
492
|
+
enableSplitViewResizing: true,
|
|
493
|
+
renderOverviewRuler: true,
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
diffEditor.setModel({ original: originalModel, modified: modifiedModel });
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
function setView(mode) {
|
|
500
|
+
renderSideBySide = mode === 'side';
|
|
501
|
+
diffEditor.updateOptions({ renderSideBySide });
|
|
502
|
+
document.getElementById('btn-side').classList.toggle('active', renderSideBySide);
|
|
503
|
+
document.getElementById('btn-inline').classList.toggle('active', !renderSideBySide);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function toggleTheme() {
|
|
507
|
+
isDark = !isDark;
|
|
508
|
+
monaco.editor.setTheme(isDark ? 'vs-dark' : 'vs');
|
|
509
|
+
document.body.style.background = isDark ? '#1e1e1e' : '#ffffff';
|
|
510
|
+
document.querySelector('.header').style.background = isDark ? '#252526' : '#f3f3f3';
|
|
511
|
+
document.querySelector('.header').style.borderColor = isDark ? '#3c3c3c' : '#e0e0e0';
|
|
512
|
+
document.getElementById('btn-theme').textContent = isDark ? 'Light' : 'Dark';
|
|
513
|
+
}
|
|
514
|
+
</script>
|
|
515
|
+
</body>
|
|
516
|
+
</html>`;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// src/tunnel/server.ts
|
|
520
|
+
function notFoundPage() {
|
|
521
|
+
return `<!DOCTYPE html>
|
|
522
|
+
<html><head><meta charset="UTF-8"><title>Not Found - OpenACP</title>
|
|
523
|
+
<style>body{background:#0d1117;color:#c9d1d9;font-family:sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh;margin:0}
|
|
524
|
+
.box{text-align:center;padding:40px}.code{font-size:72px;font-weight:bold;color:#484f58}p{margin-top:16px;color:#8b949e}</style>
|
|
525
|
+
</head><body><div class="box"><div class="code">404</div><p>This viewer link has expired or does not exist.</p></div></body></html>`;
|
|
526
|
+
}
|
|
527
|
+
function createTunnelServer(store, authToken) {
|
|
528
|
+
const app = new Hono();
|
|
529
|
+
if (authToken) {
|
|
530
|
+
app.use("*", async (c, next) => {
|
|
531
|
+
if (c.req.path === "/health") return next();
|
|
532
|
+
const bearer = c.req.header("Authorization")?.replace("Bearer ", "");
|
|
533
|
+
const query = c.req.query("token");
|
|
534
|
+
if (bearer !== authToken && query !== authToken) {
|
|
535
|
+
return c.text("Unauthorized", 401);
|
|
536
|
+
}
|
|
537
|
+
return next();
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
app.get("/health", (c) => c.json({ status: "ok" }));
|
|
541
|
+
app.get("/view/:id", (c) => {
|
|
542
|
+
const entry = store.get(c.req.param("id"));
|
|
543
|
+
if (!entry || entry.type !== "file") {
|
|
544
|
+
return c.html(notFoundPage(), 404);
|
|
545
|
+
}
|
|
546
|
+
return c.html(renderFileViewer(entry));
|
|
547
|
+
});
|
|
548
|
+
app.get("/diff/:id", (c) => {
|
|
549
|
+
const entry = store.get(c.req.param("id"));
|
|
550
|
+
if (!entry || entry.type !== "diff") {
|
|
551
|
+
return c.html(notFoundPage(), 404);
|
|
552
|
+
}
|
|
553
|
+
return c.html(renderDiffViewer(entry));
|
|
554
|
+
});
|
|
555
|
+
app.get("/api/file/:id", (c) => {
|
|
556
|
+
const entry = store.get(c.req.param("id"));
|
|
557
|
+
if (!entry || entry.type !== "file") {
|
|
558
|
+
return c.json({ error: "not found" }, 404);
|
|
559
|
+
}
|
|
560
|
+
return c.json({ filePath: entry.filePath, content: entry.content, language: entry.language });
|
|
561
|
+
});
|
|
562
|
+
app.get("/api/diff/:id", (c) => {
|
|
563
|
+
const entry = store.get(c.req.param("id"));
|
|
564
|
+
if (!entry || entry.type !== "diff") {
|
|
565
|
+
return c.json({ error: "not found" }, 404);
|
|
566
|
+
}
|
|
567
|
+
return c.json({ filePath: entry.filePath, oldContent: entry.oldContent, newContent: entry.content, language: entry.language });
|
|
568
|
+
});
|
|
569
|
+
return app;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// src/tunnel/tunnel-service.ts
|
|
573
|
+
var log3 = createChildLogger({ module: "tunnel" });
|
|
574
|
+
var TunnelService = class {
|
|
575
|
+
provider;
|
|
576
|
+
store;
|
|
577
|
+
server = null;
|
|
578
|
+
publicUrl = "";
|
|
579
|
+
config;
|
|
580
|
+
constructor(config) {
|
|
581
|
+
this.config = config;
|
|
582
|
+
this.store = new ViewerStore(config.storeTtlMinutes);
|
|
583
|
+
this.provider = this.createProvider(config.provider, config.options);
|
|
584
|
+
}
|
|
585
|
+
async start() {
|
|
586
|
+
const authToken = this.config.auth.enabled ? this.config.auth.token : void 0;
|
|
587
|
+
const app = createTunnelServer(this.store, authToken);
|
|
588
|
+
this.server = serve({ fetch: app.fetch, port: this.config.port });
|
|
589
|
+
log3.info({ port: this.config.port }, "Tunnel HTTP server started");
|
|
590
|
+
try {
|
|
591
|
+
this.publicUrl = await this.provider.start(this.config.port);
|
|
592
|
+
log3.info({ url: this.publicUrl }, "Tunnel public URL ready");
|
|
593
|
+
} catch (err) {
|
|
594
|
+
log3.warn({ err }, "Tunnel provider failed to start, running without public URL");
|
|
595
|
+
this.publicUrl = `http://localhost:${this.config.port}`;
|
|
596
|
+
}
|
|
597
|
+
return this.publicUrl;
|
|
598
|
+
}
|
|
599
|
+
async stop() {
|
|
600
|
+
await this.provider.stop();
|
|
601
|
+
if (this.server) {
|
|
602
|
+
this.server.close();
|
|
603
|
+
this.server = null;
|
|
604
|
+
}
|
|
605
|
+
this.store.destroy();
|
|
606
|
+
log3.info("Tunnel service stopped");
|
|
607
|
+
}
|
|
608
|
+
getPublicUrl() {
|
|
609
|
+
return this.publicUrl;
|
|
610
|
+
}
|
|
611
|
+
getStore() {
|
|
612
|
+
return this.store;
|
|
613
|
+
}
|
|
614
|
+
fileUrl(entryId) {
|
|
615
|
+
return `${this.publicUrl}/view/${entryId}`;
|
|
616
|
+
}
|
|
617
|
+
diffUrl(entryId) {
|
|
618
|
+
return `${this.publicUrl}/diff/${entryId}`;
|
|
619
|
+
}
|
|
620
|
+
createProvider(name, options) {
|
|
621
|
+
switch (name) {
|
|
622
|
+
case "cloudflare":
|
|
623
|
+
return new CloudflareTunnelProvider(options);
|
|
624
|
+
default:
|
|
625
|
+
log3.warn({ provider: name }, "Unknown tunnel provider, falling back to cloudflare");
|
|
626
|
+
return new CloudflareTunnelProvider(options);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
export {
|
|
631
|
+
TunnelService
|
|
632
|
+
};
|
|
633
|
+
//# sourceMappingURL=tunnel-service-FPRPBPQ5.js.map
|