teleportation-cli 1.0.0 → 1.0.1
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/.claude/hooks/heartbeat.mjs +67 -2
- package/.claude/hooks/permission_request.mjs +55 -26
- package/.claude/hooks/pre_tool_use.mjs +29 -2
- package/.claude/hooks/session-register.mjs +64 -5
- package/.claude/hooks/user_prompt_submit.mjs +54 -0
- package/README.md +36 -12
- package/lib/auth/claude-key-extractor.js +196 -0
- package/lib/auth/credentials.js +7 -2
- package/lib/cli/remote-commands.js +649 -0
- package/lib/install/installer.js +22 -7
- package/lib/remote/code-sync.js +213 -0
- package/lib/remote/init-script-robust.js +187 -0
- package/lib/remote/liveport-client.js +417 -0
- package/lib/remote/orchestrator.js +480 -0
- package/lib/remote/pr-creator.js +382 -0
- package/lib/remote/providers/base-provider.js +407 -0
- package/lib/remote/providers/daytona-provider.js +506 -0
- package/lib/remote/providers/fly-provider.js +611 -0
- package/lib/remote/providers/provider-factory.js +228 -0
- package/lib/remote/results-delivery.js +333 -0
- package/lib/remote/session-manager.js +273 -0
- package/lib/remote/state-capture.js +324 -0
- package/lib/remote/vault-client.js +478 -0
- package/lib/session/metadata.js +80 -49
- package/lib/session/mute-checker.js +2 -1
- package/lib/utils/vault-errors.js +353 -0
- package/package.json +5 -5
- package/teleportation-cli.cjs +417 -7
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vault Error Utilities
|
|
3
|
+
*
|
|
4
|
+
* Custom error classes for Vault operations with actionable suggestions
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Base class for all Vault-related errors
|
|
9
|
+
*/
|
|
10
|
+
export class VaultError extends Error {
|
|
11
|
+
constructor(message, options = {}) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = this.constructor.name;
|
|
14
|
+
this.code = options.code;
|
|
15
|
+
this.statusCode = options.statusCode;
|
|
16
|
+
this.suggestion = options.suggestion;
|
|
17
|
+
this.context = options.context || {};
|
|
18
|
+
|
|
19
|
+
// Maintain proper stack trace for where error was thrown (V8 only)
|
|
20
|
+
if (Error.captureStackTrace) {
|
|
21
|
+
Error.captureStackTrace(this, this.constructor);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
toJSON() {
|
|
26
|
+
return {
|
|
27
|
+
name: this.name,
|
|
28
|
+
message: this.message,
|
|
29
|
+
code: this.code,
|
|
30
|
+
statusCode: this.statusCode,
|
|
31
|
+
suggestion: this.suggestion,
|
|
32
|
+
context: this.context,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
toString() {
|
|
37
|
+
let str = `${this.name}: ${this.message}`;
|
|
38
|
+
if (this.suggestion) {
|
|
39
|
+
str += `\n\nSuggestion: ${this.suggestion}`;
|
|
40
|
+
}
|
|
41
|
+
return str;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Authentication failed error
|
|
47
|
+
* Thrown when API key or app ID is invalid or missing
|
|
48
|
+
*/
|
|
49
|
+
export class VaultAuthError extends VaultError {
|
|
50
|
+
constructor(message, options = {}) {
|
|
51
|
+
const defaultMessage = 'Authentication failed: Invalid API key or credentials';
|
|
52
|
+
const defaultSuggestion = `
|
|
53
|
+
Please verify your credentials:
|
|
54
|
+
1. Check that MECH_API_KEY is set correctly
|
|
55
|
+
2. Check that MECH_APP_ID is set correctly
|
|
56
|
+
3. Ensure your API key has not expired
|
|
57
|
+
4. Run 'teleportation login' to refresh credentials
|
|
58
|
+
`.trim();
|
|
59
|
+
|
|
60
|
+
super(message || defaultMessage, {
|
|
61
|
+
code: 'VAULT_AUTH_FAILED',
|
|
62
|
+
statusCode: 401,
|
|
63
|
+
suggestion: options.suggestion || defaultSuggestion,
|
|
64
|
+
...options,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Access denied error
|
|
71
|
+
* Thrown when user doesn't have permission to access resource
|
|
72
|
+
*/
|
|
73
|
+
export class VaultAccessDeniedError extends VaultError {
|
|
74
|
+
constructor(message, options = {}) {
|
|
75
|
+
const defaultMessage = 'Access denied: Insufficient permissions';
|
|
76
|
+
const defaultSuggestion = `
|
|
77
|
+
This operation requires additional permissions:
|
|
78
|
+
1. Verify your app has the required scopes in Mech Vault dashboard
|
|
79
|
+
2. Check if the namespace belongs to your app
|
|
80
|
+
3. Contact your administrator if you need elevated permissions
|
|
81
|
+
`.trim();
|
|
82
|
+
|
|
83
|
+
super(message || defaultMessage, {
|
|
84
|
+
code: 'VAULT_ACCESS_DENIED',
|
|
85
|
+
statusCode: 403,
|
|
86
|
+
suggestion: options.suggestion || defaultSuggestion,
|
|
87
|
+
...options,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Secret not found error
|
|
94
|
+
* Thrown when requested secret doesn't exist
|
|
95
|
+
*/
|
|
96
|
+
export class SecretNotFoundError extends VaultError {
|
|
97
|
+
constructor(secretId, options = {}) {
|
|
98
|
+
const message = options.message || `Secret not found: ${secretId || 'unknown'}`;
|
|
99
|
+
const suggestion = options.suggestion || `
|
|
100
|
+
The requested secret does not exist or has been deleted:
|
|
101
|
+
1. Verify the secret ID is correct
|
|
102
|
+
2. Check if the secret has expired
|
|
103
|
+
3. Ensure you're using the correct namespace
|
|
104
|
+
4. The secret may have been cleaned up automatically
|
|
105
|
+
`.trim();
|
|
106
|
+
|
|
107
|
+
super(message, {
|
|
108
|
+
code: 'SECRET_NOT_FOUND',
|
|
109
|
+
statusCode: 404,
|
|
110
|
+
suggestion,
|
|
111
|
+
context: { secretId, ...options.context },
|
|
112
|
+
...options,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Network error
|
|
119
|
+
* Thrown when network request fails (timeout, connection refused, etc.)
|
|
120
|
+
*/
|
|
121
|
+
export class VaultNetworkError extends VaultError {
|
|
122
|
+
constructor(message, options = {}) {
|
|
123
|
+
const defaultMessage = 'Network error: Unable to reach Vault API';
|
|
124
|
+
const defaultSuggestion = `
|
|
125
|
+
Failed to connect to Vault API:
|
|
126
|
+
1. Check your internet connection
|
|
127
|
+
2. Verify MECH_VAULT_URL is correct (${options.context?.vaultUrl || 'https://vault.mechdna.net/api'})
|
|
128
|
+
3. Check if Vault service is operational (https://status.mechdna.net)
|
|
129
|
+
4. Ensure no firewall is blocking the connection
|
|
130
|
+
5. Try again in a few moments
|
|
131
|
+
`.trim();
|
|
132
|
+
|
|
133
|
+
super(message || defaultMessage, {
|
|
134
|
+
code: 'VAULT_NETWORK_ERROR',
|
|
135
|
+
suggestion: options.suggestion || defaultSuggestion,
|
|
136
|
+
...options,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Server error
|
|
143
|
+
* Thrown when Vault API returns 5xx error
|
|
144
|
+
*/
|
|
145
|
+
export class VaultServerError extends VaultError {
|
|
146
|
+
constructor(message, options = {}) {
|
|
147
|
+
const defaultMessage = 'Vault server error: Service temporarily unavailable';
|
|
148
|
+
const defaultSuggestion = `
|
|
149
|
+
The Vault service encountered an error:
|
|
150
|
+
1. The service may be temporarily unavailable
|
|
151
|
+
2. Check Vault status at https://status.mechdna.net
|
|
152
|
+
3. Retry your request in a few moments
|
|
153
|
+
4. If the problem persists, contact support with the error details
|
|
154
|
+
`.trim();
|
|
155
|
+
|
|
156
|
+
super(message || defaultMessage, {
|
|
157
|
+
code: 'VAULT_SERVER_ERROR',
|
|
158
|
+
statusCode: options.statusCode || 500,
|
|
159
|
+
suggestion: options.suggestion || defaultSuggestion,
|
|
160
|
+
...options,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Validation error
|
|
167
|
+
* Thrown when input validation fails
|
|
168
|
+
*/
|
|
169
|
+
export class VaultValidationError extends VaultError {
|
|
170
|
+
constructor(message, options = {}) {
|
|
171
|
+
const defaultSuggestion = `
|
|
172
|
+
Input validation failed:
|
|
173
|
+
1. Check that all required fields are provided
|
|
174
|
+
2. Verify field formats match requirements
|
|
175
|
+
3. Ensure values are not empty or whitespace-only
|
|
176
|
+
`.trim();
|
|
177
|
+
|
|
178
|
+
super(message, {
|
|
179
|
+
code: 'VAULT_VALIDATION_ERROR',
|
|
180
|
+
statusCode: 400,
|
|
181
|
+
suggestion: options.suggestion || defaultSuggestion,
|
|
182
|
+
...options,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Retry exhausted error
|
|
189
|
+
* Thrown when all retry attempts fail
|
|
190
|
+
*/
|
|
191
|
+
export class VaultRetryExhaustedError extends VaultError {
|
|
192
|
+
constructor(originalError, retryCount, options = {}) {
|
|
193
|
+
const message = `Failed after ${retryCount} retries: ${originalError.message}`;
|
|
194
|
+
const suggestion = options.suggestion || `
|
|
195
|
+
All retry attempts have been exhausted:
|
|
196
|
+
1. Check the underlying error for details
|
|
197
|
+
2. Verify network connectivity
|
|
198
|
+
3. Ensure Vault service is operational
|
|
199
|
+
4. Consider increasing retry limits if transient failures are expected
|
|
200
|
+
`.trim();
|
|
201
|
+
|
|
202
|
+
super(message, {
|
|
203
|
+
code: 'VAULT_RETRY_EXHAUSTED',
|
|
204
|
+
suggestion,
|
|
205
|
+
context: {
|
|
206
|
+
originalError: originalError.message,
|
|
207
|
+
retryCount,
|
|
208
|
+
...options.context,
|
|
209
|
+
},
|
|
210
|
+
...options,
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
this.originalError = originalError;
|
|
214
|
+
this.retryCount = retryCount;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Format error for display to user
|
|
220
|
+
* @param {Error} error - Error to format
|
|
221
|
+
* @returns {string} Formatted error message
|
|
222
|
+
*/
|
|
223
|
+
export function formatVaultError(error) {
|
|
224
|
+
if (error instanceof VaultError) {
|
|
225
|
+
return error.toString();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Handle generic errors
|
|
229
|
+
return `Error: ${error.message}`;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Create appropriate VaultError from HTTP response
|
|
234
|
+
* @param {Response} response - Fetch response object
|
|
235
|
+
* @param {Object} context - Additional context
|
|
236
|
+
* @returns {VaultError} Appropriate error instance
|
|
237
|
+
*/
|
|
238
|
+
export function createVaultErrorFromResponse(response, context = {}) {
|
|
239
|
+
const status = response.status;
|
|
240
|
+
const statusText = response.statusText;
|
|
241
|
+
|
|
242
|
+
let detailsText = '';
|
|
243
|
+
const responseBody = context && typeof context === 'object' ? context.responseBody : undefined;
|
|
244
|
+
if (responseBody && typeof responseBody === 'string') {
|
|
245
|
+
try {
|
|
246
|
+
const parsed = JSON.parse(responseBody);
|
|
247
|
+
const msg = parsed?.error?.message || parsed?.message;
|
|
248
|
+
const details = parsed?.error?.details;
|
|
249
|
+
const combined = details || msg;
|
|
250
|
+
if (combined && typeof combined === 'string') {
|
|
251
|
+
detailsText = `: ${combined}`;
|
|
252
|
+
}
|
|
253
|
+
} catch {
|
|
254
|
+
const trimmed = responseBody.trim();
|
|
255
|
+
if (trimmed) {
|
|
256
|
+
detailsText = `: ${trimmed.slice(0, 200)}`;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (status === 401) {
|
|
262
|
+
return new VaultAuthError(
|
|
263
|
+
`Authentication failed: ${statusText}${detailsText}`,
|
|
264
|
+
{ statusCode: status, context }
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (status === 403) {
|
|
269
|
+
return new VaultAccessDeniedError(
|
|
270
|
+
`Access denied: ${statusText}${detailsText}`,
|
|
271
|
+
{ statusCode: status, context }
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (status === 404) {
|
|
276
|
+
return new SecretNotFoundError(
|
|
277
|
+
context.secretId || 'unknown',
|
|
278
|
+
{ statusCode: status, context }
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (status >= 500) {
|
|
283
|
+
return new VaultServerError(
|
|
284
|
+
`Server error: ${statusText}${detailsText}`,
|
|
285
|
+
{ statusCode: status, context }
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (status >= 400 && status < 500) {
|
|
290
|
+
return new VaultValidationError(
|
|
291
|
+
`Request failed: ${statusText}${detailsText}`,
|
|
292
|
+
{ statusCode: status, context }
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return new VaultError(
|
|
297
|
+
`Unexpected error: ${status} ${statusText}`,
|
|
298
|
+
{ statusCode: status, context }
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Wrap network errors with VaultNetworkError
|
|
304
|
+
* @param {Error} error - Original network error
|
|
305
|
+
* @param {Object} context - Additional context
|
|
306
|
+
* @returns {VaultNetworkError} Network error with context
|
|
307
|
+
*/
|
|
308
|
+
export function wrapNetworkError(error, context = {}) {
|
|
309
|
+
return new VaultNetworkError(
|
|
310
|
+
`Network error: ${error.message}`,
|
|
311
|
+
{ context: { ...context, originalError: error.message } }
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Check if error is retryable
|
|
317
|
+
* @param {Error} error - Error to check
|
|
318
|
+
* @returns {boolean} True if error should be retried
|
|
319
|
+
*/
|
|
320
|
+
export function isRetryableError(error) {
|
|
321
|
+
// Network errors are retryable
|
|
322
|
+
if (error instanceof VaultNetworkError) {
|
|
323
|
+
return true;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Server errors (5xx) are retryable
|
|
327
|
+
if (error instanceof VaultServerError) {
|
|
328
|
+
return true;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// VaultError with 5xx status code
|
|
332
|
+
if (error instanceof VaultError && error.statusCode >= 500) {
|
|
333
|
+
return true;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Generic network errors
|
|
337
|
+
if (error.message && (
|
|
338
|
+
error.message.includes('timeout') ||
|
|
339
|
+
error.message.includes('ECONNREFUSED') ||
|
|
340
|
+
error.message.includes('ETIMEDOUT') ||
|
|
341
|
+
error.message.includes('ENOTFOUND') ||
|
|
342
|
+
error.message.includes('Network')
|
|
343
|
+
)) {
|
|
344
|
+
return true;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Client errors (4xx) are not retryable
|
|
348
|
+
if (error instanceof VaultError && error.statusCode >= 400 && error.statusCode < 500) {
|
|
349
|
+
return false;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return false;
|
|
353
|
+
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "teleportation-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Remote approval system for Claude Code - approve AI coding changes from your phone",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "teleportation-cli.cjs",
|
|
7
7
|
"bin": {
|
|
8
|
-
"teleportation": "
|
|
8
|
+
"teleportation": "teleportation-cli.cjs"
|
|
9
9
|
},
|
|
10
10
|
"engines": {
|
|
11
11
|
"bun": "^1.3.0"
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"license": "MIT",
|
|
14
14
|
"repository": {
|
|
15
15
|
"type": "git",
|
|
16
|
-
"url": "https://github.com/dundas/teleportation-private.git"
|
|
16
|
+
"url": "git+https://github.com/dundas/teleportation-private.git"
|
|
17
17
|
},
|
|
18
18
|
"homepage": "https://github.com/dundas/teleportation-private#readme",
|
|
19
19
|
"bugs": {
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
],
|
|
32
32
|
"author": "Dundas",
|
|
33
33
|
"scripts": {
|
|
34
|
-
"test": "bun --bun vitest run
|
|
34
|
+
"test": "bun --bun vitest run",
|
|
35
35
|
"test:watch": "bun --bun vitest",
|
|
36
36
|
"test:coverage": "bun --bun vitest --coverage",
|
|
37
37
|
"test:e2e": "bun --bun vitest run tests/e2e",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"dev:relay": "cd relay && bun run dev",
|
|
41
41
|
"dev:mobile": "cd mobile-ui && bun run dev",
|
|
42
42
|
"dev:all": "bun run dev:relay & bun run dev:mobile",
|
|
43
|
-
"prepublishOnly": "
|
|
43
|
+
"prepublishOnly": "bun --bun vitest run"
|
|
44
44
|
},
|
|
45
45
|
"files": [
|
|
46
46
|
"lib/**/*.js",
|