@mod-computer/cli 0.2.3 → 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 (75) hide show
  1. package/dist/cli.bundle.js +216 -36371
  2. package/package.json +3 -3
  3. package/dist/app.js +0 -227
  4. package/dist/cli.bundle.js.map +0 -7
  5. package/dist/cli.js +0 -132
  6. package/dist/commands/add.js +0 -245
  7. package/dist/commands/agents-run.js +0 -71
  8. package/dist/commands/auth.js +0 -259
  9. package/dist/commands/branch.js +0 -1411
  10. package/dist/commands/claude-sync.js +0 -772
  11. package/dist/commands/comment.js +0 -568
  12. package/dist/commands/diff.js +0 -182
  13. package/dist/commands/index.js +0 -73
  14. package/dist/commands/init.js +0 -597
  15. package/dist/commands/ls.js +0 -135
  16. package/dist/commands/members.js +0 -687
  17. package/dist/commands/mv.js +0 -282
  18. package/dist/commands/recover.js +0 -207
  19. package/dist/commands/rm.js +0 -257
  20. package/dist/commands/spec.js +0 -386
  21. package/dist/commands/status.js +0 -296
  22. package/dist/commands/sync.js +0 -119
  23. package/dist/commands/trace.js +0 -1752
  24. package/dist/commands/workspace.js +0 -447
  25. package/dist/components/conflict-resolution-ui.js +0 -120
  26. package/dist/components/messages.js +0 -5
  27. package/dist/components/thread.js +0 -8
  28. package/dist/config/features.js +0 -83
  29. package/dist/containers/branches-container.js +0 -140
  30. package/dist/containers/directory-container.js +0 -92
  31. package/dist/containers/thread-container.js +0 -214
  32. package/dist/containers/threads-container.js +0 -27
  33. package/dist/containers/workspaces-container.js +0 -27
  34. package/dist/daemon/conflict-resolution.js +0 -172
  35. package/dist/daemon/content-hash.js +0 -31
  36. package/dist/daemon/file-sync.js +0 -985
  37. package/dist/daemon/index.js +0 -203
  38. package/dist/daemon/mime-types.js +0 -166
  39. package/dist/daemon/offline-queue.js +0 -211
  40. package/dist/daemon/path-utils.js +0 -64
  41. package/dist/daemon/share-policy.js +0 -83
  42. package/dist/daemon/wasm-errors.js +0 -189
  43. package/dist/daemon/worker.js +0 -557
  44. package/dist/daemon-worker.js +0 -258
  45. package/dist/errors/workspace-errors.js +0 -48
  46. package/dist/lib/auth-server.js +0 -216
  47. package/dist/lib/browser.js +0 -35
  48. package/dist/lib/diff.js +0 -284
  49. package/dist/lib/formatters.js +0 -204
  50. package/dist/lib/git.js +0 -137
  51. package/dist/lib/local-fs.js +0 -201
  52. package/dist/lib/prompts.js +0 -56
  53. package/dist/lib/storage.js +0 -213
  54. package/dist/lib/trace-formatters.js +0 -314
  55. package/dist/services/add-service.js +0 -554
  56. package/dist/services/add-validation.js +0 -124
  57. package/dist/services/automatic-file-tracker.js +0 -303
  58. package/dist/services/cli-orchestrator.js +0 -227
  59. package/dist/services/feature-flags.js +0 -187
  60. package/dist/services/file-import-service.js +0 -283
  61. package/dist/services/file-transformation-service.js +0 -218
  62. package/dist/services/logger.js +0 -44
  63. package/dist/services/mod-config.js +0 -67
  64. package/dist/services/modignore-service.js +0 -328
  65. package/dist/services/sync-daemon.js +0 -244
  66. package/dist/services/thread-notification-service.js +0 -50
  67. package/dist/services/thread-service.js +0 -147
  68. package/dist/stores/use-directory-store.js +0 -96
  69. package/dist/stores/use-threads-store.js +0 -46
  70. package/dist/stores/use-workspaces-store.js +0 -54
  71. package/dist/types/add-types.js +0 -99
  72. package/dist/types/config.js +0 -16
  73. package/dist/types/index.js +0 -2
  74. package/dist/types/workspace-connection.js +0 -53
  75. 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
- }