@mod-computer/cli 0.2.4 → 0.2.5

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.
Files changed (74) hide show
  1. package/package.json +3 -3
  2. package/dist/app.js +0 -227
  3. package/dist/cli.bundle.js.map +0 -7
  4. package/dist/cli.js +0 -132
  5. package/dist/commands/add.js +0 -245
  6. package/dist/commands/agents-run.js +0 -71
  7. package/dist/commands/auth.js +0 -259
  8. package/dist/commands/branch.js +0 -1411
  9. package/dist/commands/claude-sync.js +0 -772
  10. package/dist/commands/comment.js +0 -568
  11. package/dist/commands/diff.js +0 -182
  12. package/dist/commands/index.js +0 -73
  13. package/dist/commands/init.js +0 -597
  14. package/dist/commands/ls.js +0 -135
  15. package/dist/commands/members.js +0 -687
  16. package/dist/commands/mv.js +0 -282
  17. package/dist/commands/recover.js +0 -207
  18. package/dist/commands/rm.js +0 -257
  19. package/dist/commands/spec.js +0 -386
  20. package/dist/commands/status.js +0 -296
  21. package/dist/commands/sync.js +0 -119
  22. package/dist/commands/trace.js +0 -1752
  23. package/dist/commands/workspace.js +0 -447
  24. package/dist/components/conflict-resolution-ui.js +0 -120
  25. package/dist/components/messages.js +0 -5
  26. package/dist/components/thread.js +0 -8
  27. package/dist/config/features.js +0 -83
  28. package/dist/containers/branches-container.js +0 -140
  29. package/dist/containers/directory-container.js +0 -92
  30. package/dist/containers/thread-container.js +0 -214
  31. package/dist/containers/threads-container.js +0 -27
  32. package/dist/containers/workspaces-container.js +0 -27
  33. package/dist/daemon/conflict-resolution.js +0 -172
  34. package/dist/daemon/content-hash.js +0 -31
  35. package/dist/daemon/file-sync.js +0 -985
  36. package/dist/daemon/index.js +0 -203
  37. package/dist/daemon/mime-types.js +0 -166
  38. package/dist/daemon/offline-queue.js +0 -211
  39. package/dist/daemon/path-utils.js +0 -64
  40. package/dist/daemon/share-policy.js +0 -83
  41. package/dist/daemon/wasm-errors.js +0 -189
  42. package/dist/daemon/worker.js +0 -557
  43. package/dist/daemon-worker.js +0 -258
  44. package/dist/errors/workspace-errors.js +0 -48
  45. package/dist/lib/auth-server.js +0 -216
  46. package/dist/lib/browser.js +0 -35
  47. package/dist/lib/diff.js +0 -284
  48. package/dist/lib/formatters.js +0 -204
  49. package/dist/lib/git.js +0 -137
  50. package/dist/lib/local-fs.js +0 -201
  51. package/dist/lib/prompts.js +0 -56
  52. package/dist/lib/storage.js +0 -213
  53. package/dist/lib/trace-formatters.js +0 -314
  54. package/dist/services/add-service.js +0 -554
  55. package/dist/services/add-validation.js +0 -124
  56. package/dist/services/automatic-file-tracker.js +0 -303
  57. package/dist/services/cli-orchestrator.js +0 -227
  58. package/dist/services/feature-flags.js +0 -187
  59. package/dist/services/file-import-service.js +0 -283
  60. package/dist/services/file-transformation-service.js +0 -218
  61. package/dist/services/logger.js +0 -44
  62. package/dist/services/mod-config.js +0 -67
  63. package/dist/services/modignore-service.js +0 -328
  64. package/dist/services/sync-daemon.js +0 -244
  65. package/dist/services/thread-notification-service.js +0 -50
  66. package/dist/services/thread-service.js +0 -147
  67. package/dist/stores/use-directory-store.js +0 -96
  68. package/dist/stores/use-threads-store.js +0 -46
  69. package/dist/stores/use-workspaces-store.js +0 -54
  70. package/dist/types/add-types.js +0 -99
  71. package/dist/types/config.js +0 -16
  72. package/dist/types/index.js +0 -2
  73. package/dist/types/workspace-connection.js +0 -53
  74. package/dist/types.js +0 -1
@@ -1,258 +0,0 @@
1
- #!/usr/bin/env node
2
- import * as fs from 'fs/promises';
3
- import * as path from 'path';
4
- import { fileURLToPath } from 'url';
5
- import chokidar from 'chokidar';
6
- import { AutomaticFileTracker } from './services/automatic-file-tracker.js';
7
- import { createModWorkspace } from '@mod/mod-core';
8
- import { repo as getRepo } from '@mod/mod-core/repos/repo.node';
9
- import { readModConfig } from './services/mod-config.js';
10
- // TODO: Replace with new SyncDiagnostics when implemented
11
- const __filename = fileURLToPath(import.meta.url);
12
- const __dirname = path.dirname(__filename);
13
- class DaemonWorker {
14
- constructor(options) {
15
- this.isShuttingDown = false;
16
- this.options = options;
17
- this.stateFilePath = path.join(options.workingDir, '.mod', '.cache', 'sync-daemon.json');
18
- }
19
- async start() {
20
- try {
21
- process.on('SIGTERM', () => this.gracefulShutdown());
22
- process.on('SIGINT', () => this.gracefulShutdown());
23
- process.on('uncaughtException', (error) => this.handleError(error));
24
- process.on('unhandledRejection', (error) => this.handleError(error));
25
- await this.updateState({
26
- status: 'running',
27
- lastActivity: new Date().toISOString(),
28
- watchedFiles: 0
29
- });
30
- if (this.options.verbose) {
31
- console.log(`🚀 Daemon worker starting...`);
32
- console.log(`📁 Working directory: ${this.options.workingDir}`);
33
- console.log(`🔗 Workspace ID: ${this.options.workspaceId}`);
34
- console.log(`🌿 Active branch: ${this.options.activeBranchId}`);
35
- }
36
- // Initialize repo and workspace
37
- const repo = await this.initializeRepo();
38
- this.repo = repo;
39
- const modWorkspace = createModWorkspace(repo);
40
- const workspaceHandles = await modWorkspace.listWorkspaces();
41
- this.workspaceHandle = workspaceHandles.find(wh => wh.id === this.options.workspaceId);
42
- if (!this.workspaceHandle) {
43
- throw new Error(`Workspace ${this.options.workspaceId} not found`);
44
- }
45
- // Set active branch
46
- if (this.options.activeBranchId) {
47
- try {
48
- await this.workspaceHandle.branch.switchActive(this.options.activeBranchId);
49
- }
50
- catch (error) {
51
- console.warn(`⚠️ Failed to set active branch: ${error}`);
52
- }
53
- }
54
- await this.setupConfigWatcher();
55
- this.tracker = new AutomaticFileTracker(repo);
56
- await this.tracker.enableAutoTracking({
57
- workspaceId: this.options.workspaceId,
58
- workspaceHandle: this.workspaceHandle,
59
- watchDirectory: this.options.workingDir,
60
- debounceMs: 500,
61
- verbose: this.options.verbose || false,
62
- maxWatchedFiles: 2000,
63
- preFilterEnabled: true,
64
- comprehensiveMode: true,
65
- resourceMonitoring: true
66
- });
67
- const status = this.tracker.getTrackingStatus();
68
- await this.updateState({
69
- watchedFiles: status.watchedFiles,
70
- lastActivity: new Date().toISOString()
71
- });
72
- if (this.options.verbose) {
73
- console.log(`✅ Daemon worker running (PID: ${process.pid})`);
74
- console.log(`📊 Watching ${status.watchedFiles} files`);
75
- }
76
- await this.keepAlive();
77
- }
78
- catch (error) {
79
- await this.handleError(error);
80
- process.exit(1);
81
- }
82
- }
83
- async gracefulShutdown() {
84
- if (this.isShuttingDown)
85
- return;
86
- this.isShuttingDown = true;
87
- console.log('\n🛑 Daemon worker shutting down...');
88
- try {
89
- await this.updateState({
90
- status: 'stopping',
91
- lastActivity: new Date().toISOString()
92
- });
93
- if (this.tracker) {
94
- await this.tracker.disableAutoTracking();
95
- }
96
- if (this.configWatcher) {
97
- await this.configWatcher.close();
98
- }
99
- await this.updateState({
100
- status: 'stopped',
101
- lastActivity: new Date().toISOString()
102
- });
103
- console.log('✅ Daemon worker stopped gracefully');
104
- process.exit(0);
105
- }
106
- catch (error) {
107
- console.error('Error during graceful shutdown:', error);
108
- process.exit(1);
109
- }
110
- }
111
- async handleError(error) {
112
- console.error('💥 Daemon worker error:', error);
113
- try {
114
- await this.updateState({
115
- status: 'crashed',
116
- lastError: error.message || 'Unknown error',
117
- lastActivity: new Date().toISOString()
118
- });
119
- }
120
- catch (stateError) {
121
- console.error('Failed to update error state:', stateError);
122
- }
123
- // Let the main daemon handle restart logic
124
- process.exit(1);
125
- }
126
- async keepAlive() {
127
- while (!this.isShuttingDown) {
128
- try {
129
- await this.updateState({
130
- lastActivity: new Date().toISOString()
131
- });
132
- // Check if tracking is still active
133
- if (this.tracker) {
134
- const status = this.tracker.getTrackingStatus();
135
- if (status.watchedFiles !== undefined) {
136
- await this.updateState({ watchedFiles: status.watchedFiles });
137
- }
138
- }
139
- await new Promise(resolve => setTimeout(resolve, 10000)); // 10 second intervals
140
- }
141
- catch (error) {
142
- await this.handleError(error);
143
- break;
144
- }
145
- }
146
- }
147
- async updateState(updates) {
148
- try {
149
- let currentState = {};
150
- try {
151
- const data = await fs.readFile(this.stateFilePath, 'utf8');
152
- currentState = JSON.parse(data);
153
- }
154
- catch { /* State file doesn't exist yet */ }
155
- const newState = { ...currentState, ...updates, pid: process.pid };
156
- await fs.writeFile(this.stateFilePath, JSON.stringify(newState, null, 2), 'utf8');
157
- }
158
- catch (error) {
159
- console.error('Failed to update daemon state:', error);
160
- }
161
- }
162
- async setupConfigWatcher() {
163
- const configPath = path.join(this.options.workingDir, '.mod', 'config.json');
164
- this.configWatcher = chokidar.watch(configPath, {
165
- persistent: true,
166
- ignoreInitial: true
167
- });
168
- this.configWatcher.on('change', async () => {
169
- try {
170
- await this.handleConfigChange();
171
- }
172
- catch (error) {
173
- console.error('Error handling config change:', error);
174
- }
175
- });
176
- if (this.options.verbose) {
177
- console.log(`🔍 Watching config file: ${configPath}`);
178
- }
179
- }
180
- async handleConfigChange() {
181
- try {
182
- // Read the updated config
183
- const config = readModConfig();
184
- if (!config?.activeBranchId) {
185
- return;
186
- }
187
- // Check if the branch has actually changed
188
- if (config.activeBranchId === this.options.activeBranchId) {
189
- return;
190
- }
191
- if (this.options.verbose) {
192
- console.log(`🔄 Branch switch detected: ${this.options.activeBranchId} → ${config.activeBranchId}`);
193
- }
194
- if (this.workspaceHandle) {
195
- try {
196
- await this.workspaceHandle.branch.switchActive(config.activeBranchId);
197
- this.options.activeBranchId = config.activeBranchId;
198
- await this.updateState({
199
- activeBranchId: config.activeBranchId,
200
- lastActivity: new Date().toISOString()
201
- });
202
- if (this.options.verbose) {
203
- console.log(`✅ Switched to branch: ${config.activeBranchId}`);
204
- }
205
- }
206
- catch (error) {
207
- console.error(`❌ Failed to switch to branch ${config.activeBranchId}:`, error);
208
- }
209
- }
210
- }
211
- catch (error) {
212
- console.error('Error reading config during branch switch:', error);
213
- }
214
- }
215
- async initializeRepo() {
216
- // Use the same repo initialization as the main CLI
217
- return await getRepo();
218
- }
219
- }
220
- if (import.meta.url === `file://${process.argv[1]}`) {
221
- const args = process.argv.slice(2);
222
- const options = {
223
- workspaceId: '',
224
- activeBranchId: 'main',
225
- workingDir: process.cwd(),
226
- verbose: false
227
- };
228
- // Parse command line arguments
229
- for (let i = 0; i < args.length; i += 2) {
230
- const key = args[i];
231
- const value = args[i + 1];
232
- switch (key) {
233
- case '--workspace-id':
234
- options.workspaceId = value;
235
- break;
236
- case '--active-branch':
237
- options.activeBranchId = value;
238
- break;
239
- case '--working-dir':
240
- options.workingDir = value;
241
- break;
242
- case '--verbose':
243
- options.verbose = true;
244
- i--; // No value for this flag
245
- break;
246
- }
247
- }
248
- if (!options.workspaceId) {
249
- console.error('❌ Workspace ID is required');
250
- process.exit(1);
251
- }
252
- const worker = new DaemonWorker(options);
253
- worker.start().catch((error) => {
254
- console.error('❌ Failed to start daemon worker:', error);
255
- process.exit(1);
256
- });
257
- }
258
- export { DaemonWorker };
@@ -1,48 +0,0 @@
1
- // glassware[type="implementation", id="impl-cli-ws-errors--396f8074", requirements="requirement-cli-ws-error-connection--b7589ae2,requirement-cli-ws-error-storage--88e831ba,requirement-cli-ws-error-validation--192a0457"]
2
- // spec: packages/mod-cli/specs/workspaces.md
3
- /**
4
- * Error thrown when workspace connection operations fail
5
- */
6
- // glassware[type="implementation", id="impl-cli-ws-error-connection--8c8703d4", requirements="requirement-cli-ws-error-connection--b7589ae2"]
7
- export class WorkspaceConnectionError extends Error {
8
- constructor(path, reason) {
9
- super(`Workspace connection error at ${path}: ${reason}`);
10
- this.name = 'WorkspaceConnectionError';
11
- this.path = path;
12
- this.reason = reason;
13
- if (Error.captureStackTrace) {
14
- Error.captureStackTrace(this, WorkspaceConnectionError);
15
- }
16
- }
17
- }
18
- /**
19
- * Error thrown when workspace storage operations fail
20
- */
21
- // glassware[type="implementation", id="impl-cli-ws-error-storage--3aae8301", requirements="requirement-cli-ws-error-storage--88e831ba"]
22
- export class WorkspaceStorageError extends Error {
23
- constructor(operation, filePath, cause) {
24
- super(`Storage ${operation} failed for ${filePath}${cause ? `: ${cause.message}` : ''}`);
25
- this.name = 'WorkspaceStorageError';
26
- this.operation = operation;
27
- this.filePath = filePath;
28
- this.cause = cause;
29
- if (Error.captureStackTrace) {
30
- Error.captureStackTrace(this, WorkspaceStorageError);
31
- }
32
- }
33
- }
34
- /**
35
- * Error thrown when workspace connection validation fails
36
- */
37
- // glassware[type="implementation", id="impl-cli-ws-error-validation--36675fe8", requirements="requirement-cli-ws-error-validation--192a0457"]
38
- export class WorkspaceValidationError extends Error {
39
- constructor(errors) {
40
- const message = `Workspace validation failed: ${errors.map(e => e.message).join(', ')}`;
41
- super(message);
42
- this.name = 'WorkspaceValidationError';
43
- this.errors = errors;
44
- if (Error.captureStackTrace) {
45
- Error.captureStackTrace(this, WorkspaceValidationError);
46
- }
47
- }
48
- }
@@ -1,216 +0,0 @@
1
- // glassware[type="implementation", id="impl-auth-server--59aa04a6", specifications="specification-spec-localhost-server--6f8cb512,specification-spec-receive-callback--09de208c,specification-spec-server-timeout--163a7a48"]
2
- import http from 'http';
3
- import { URL } from 'url';
4
- const DEFAULT_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
5
- /**
6
- * Start a localhost HTTP server to receive OAuth callback.
7
- * Returns the port number and a promise that resolves with auth result.
8
- */
9
- export async function startAuthServer(options = {}) {
10
- const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
11
- let resolveAuth;
12
- let rejectAuth;
13
- const resultPromise = new Promise((resolve, reject) => {
14
- resolveAuth = resolve;
15
- rejectAuth = reject;
16
- });
17
- const server = http.createServer((req, res) => {
18
- const url = new URL(req.url || '/', `http://localhost`);
19
- if (url.pathname === '/callback') {
20
- const token = url.searchParams.get('token');
21
- const email = url.searchParams.get('email');
22
- const name = url.searchParams.get('name');
23
- const googleId = url.searchParams.get('googleId');
24
- if (token && email && googleId) {
25
- // Send success page to browser
26
- res.writeHead(200, { 'Content-Type': 'text/html' });
27
- res.end(getSuccessPage(name || email));
28
- // Resolve with auth result
29
- resolveAuth({
30
- googleIdToken: token,
31
- googleId,
32
- email,
33
- name: name || email.split('@')[0],
34
- });
35
- }
36
- else {
37
- // Missing parameters
38
- res.writeHead(400, { 'Content-Type': 'text/html' });
39
- res.end(getErrorPage('Missing authentication parameters'));
40
- rejectAuth(new Error('Missing authentication parameters in callback'));
41
- }
42
- }
43
- else {
44
- res.writeHead(404, { 'Content-Type': 'text/plain' });
45
- res.end('Not found');
46
- }
47
- });
48
- // Wait for server to start listening
49
- const port = await new Promise((resolve, reject) => {
50
- server.on('error', reject);
51
- server.listen(0, '127.0.0.1', () => {
52
- const address = server.address();
53
- const port = typeof address === 'object' && address ? address.port : 0;
54
- resolve(port);
55
- });
56
- });
57
- // Set timeout
58
- const timeoutId = setTimeout(() => {
59
- server.close();
60
- rejectAuth(new Error('Authentication timed out after 5 minutes'));
61
- }, timeoutMs);
62
- // Close server when auth completes
63
- resultPromise.finally(() => {
64
- clearTimeout(timeoutId);
65
- server.close();
66
- });
67
- return {
68
- port,
69
- result: resultPromise,
70
- close: () => {
71
- clearTimeout(timeoutId);
72
- server.close();
73
- rejectAuth(new Error('Authentication cancelled'));
74
- },
75
- };
76
- }
77
- function getSuccessPage(name) {
78
- const modLogo = `<svg width="48" height="54" viewBox="0 0 33 36" fill="none" xmlns="http://www.w3.org/2000/svg">
79
- <path opacity="0.8" d="M16.4287 5.04502C16.4287 2.76982 18.8623 1.32269 20.8613 2.40918L31.2899 8.07724C32.2559 8.60226 32.8573 9.61363 32.8573 10.7131V21.9875C32.8573 24.2715 30.4066 25.7178 28.4072 24.6138L17.9786 18.8558C17.0224 18.3278 16.4287 17.3218 16.4287 16.2295V5.04502Z" fill="#FF2B00"/>
80
- <path opacity="0.7" d="M8.14282 9.43857C8.14282 7.16338 10.5764 5.71625 12.5754 6.80274L23.004 12.4708C23.97 12.9958 24.5714 14.0072 24.5714 15.1066V26.3811C24.5714 28.6651 22.1208 30.1113 20.1213 29.0074L9.69275 23.2493C8.73652 22.7214 8.14282 21.7154 8.14282 20.6231V9.43857Z" fill="white"/>
81
- <path opacity="0.8" d="M0 13.9742C0 11.699 2.4336 10.2519 4.43261 11.3384L14.8612 17.0064C15.8271 17.5315 16.4286 18.5428 16.4286 19.6423V30.9167C16.4286 33.2007 13.9779 34.647 11.9785 33.543L1.54993 27.785C0.593696 27.257 0 26.251 0 25.1587V13.9742Z" fill="#3671F1"/>
82
- </svg>`;
83
- return `<!DOCTYPE html>
84
- <html>
85
- <head>
86
- <title>Signed in to Mod</title>
87
- <style>
88
- * { box-sizing: border-box; }
89
- body {
90
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
91
- display: flex;
92
- justify-content: center;
93
- align-items: center;
94
- min-height: 100vh;
95
- margin: 0;
96
- padding: 1rem;
97
- background: #09090b;
98
- color: #fafafa;
99
- }
100
- .card {
101
- width: 100%;
102
- max-width: 24rem;
103
- background: #18181b;
104
- border: 1px solid #27272a;
105
- border-radius: 0.5rem;
106
- padding: 1.5rem;
107
- box-shadow: 0 1px 3px rgba(0,0,0,0.3);
108
- }
109
- .logo-container {
110
- display: flex;
111
- flex-direction: column;
112
- align-items: center;
113
- }
114
- .logo {
115
- margin-bottom: 1rem;
116
- }
117
- h1 {
118
- font-size: 1.125rem;
119
- font-weight: 600;
120
- margin: 0 0 0.25rem 0;
121
- text-align: center;
122
- }
123
- .subtitle {
124
- color: #a1a1aa;
125
- font-size: 0.875rem;
126
- text-align: center;
127
- margin: 0;
128
- }
129
- </style>
130
- </head>
131
- <body>
132
- <div class="card">
133
- <div class="logo-container">
134
- <div class="logo">${modLogo}</div>
135
- <h1>Welcome, ${escapeHtml(name)}!</h1>
136
- <p class="subtitle">You can close this window and return to your terminal.</p>
137
- </div>
138
- </div>
139
- </body>
140
- </html>`;
141
- }
142
- function getErrorPage(message) {
143
- const modLogo = `<svg width="48" height="54" viewBox="0 0 33 36" fill="none" xmlns="http://www.w3.org/2000/svg">
144
- <path opacity="0.8" d="M16.4287 5.04502C16.4287 2.76982 18.8623 1.32269 20.8613 2.40918L31.2899 8.07724C32.2559 8.60226 32.8573 9.61363 32.8573 10.7131V21.9875C32.8573 24.2715 30.4066 25.7178 28.4072 24.6138L17.9786 18.8558C17.0224 18.3278 16.4287 17.3218 16.4287 16.2295V5.04502Z" fill="#FF2B00"/>
145
- <path opacity="0.7" d="M8.14282 9.43857C8.14282 7.16338 10.5764 5.71625 12.5754 6.80274L23.004 12.4708C23.97 12.9958 24.5714 14.0072 24.5714 15.1066V26.3811C24.5714 28.6651 22.1208 30.1113 20.1213 29.0074L9.69275 23.2493C8.73652 22.7214 8.14282 21.7154 8.14282 20.6231V9.43857Z" fill="white"/>
146
- <path opacity="0.8" d="M0 13.9742C0 11.699 2.4336 10.2519 4.43261 11.3384L14.8612 17.0064C15.8271 17.5315 16.4286 18.5428 16.4286 19.6423V30.9167C16.4286 33.2007 13.9779 34.647 11.9785 33.543L1.54993 27.785C0.593696 27.257 0 26.251 0 25.1587V13.9742Z" fill="#3671F1"/>
147
- </svg>`;
148
- return `<!DOCTYPE html>
149
- <html>
150
- <head>
151
- <title>Authentication Error</title>
152
- <style>
153
- * { box-sizing: border-box; }
154
- body {
155
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
156
- display: flex;
157
- justify-content: center;
158
- align-items: center;
159
- min-height: 100vh;
160
- margin: 0;
161
- padding: 1rem;
162
- background: #09090b;
163
- color: #fafafa;
164
- }
165
- .card {
166
- width: 100%;
167
- max-width: 24rem;
168
- background: #18181b;
169
- border: 1px solid #27272a;
170
- border-radius: 0.5rem;
171
- padding: 1.5rem;
172
- box-shadow: 0 1px 3px rgba(0,0,0,0.3);
173
- }
174
- .logo-container {
175
- display: flex;
176
- flex-direction: column;
177
- align-items: center;
178
- }
179
- .logo {
180
- margin-bottom: 1rem;
181
- }
182
- h1 {
183
- font-size: 1.125rem;
184
- font-weight: 600;
185
- margin: 0 0 0.5rem 0;
186
- color: #ef4444;
187
- text-align: center;
188
- }
189
- .message {
190
- color: #a1a1aa;
191
- font-size: 0.875rem;
192
- text-align: center;
193
- margin: 0 0 0.25rem 0;
194
- }
195
- </style>
196
- </head>
197
- <body>
198
- <div class="card">
199
- <div class="logo-container">
200
- <div class="logo">${modLogo}</div>
201
- <h1>Authentication Error</h1>
202
- <p class="message">${escapeHtml(message)}</p>
203
- <p class="message">Please try again from your terminal.</p>
204
- </div>
205
- </div>
206
- </body>
207
- </html>`;
208
- }
209
- function escapeHtml(str) {
210
- return str
211
- .replace(/&/g, '&amp;')
212
- .replace(/</g, '&lt;')
213
- .replace(/>/g, '&gt;')
214
- .replace(/"/g, '&quot;')
215
- .replace(/'/g, '&#039;');
216
- }
@@ -1,35 +0,0 @@
1
- // glassware[type="implementation", id="impl-browser-opener--15759c87", specifications="specification-spec-open-browser--397b6a28,specification-spec-manual-url--b37b2760"]
2
- import { exec } from 'child_process';
3
- import { platform } from 'os';
4
- /**
5
- * Open a URL in the default browser.
6
- * Returns true if successful, false if browser couldn't be opened.
7
- */
8
- export async function openBrowser(url) {
9
- const command = getBrowserCommand(url);
10
- return new Promise((resolve) => {
11
- exec(command, (error) => {
12
- if (error) {
13
- resolve(false);
14
- }
15
- else {
16
- resolve(true);
17
- }
18
- });
19
- });
20
- }
21
- /**
22
- * Get the platform-specific command to open a URL.
23
- */
24
- function getBrowserCommand(url) {
25
- const escapedUrl = url.replace(/"/g, '\\"');
26
- switch (platform()) {
27
- case 'darwin':
28
- return `open "${escapedUrl}"`;
29
- case 'win32':
30
- return `start "" "${escapedUrl}"`;
31
- default:
32
- // Linux and others - try xdg-open
33
- return `xdg-open "${escapedUrl}"`;
34
- }
35
- }