@crewx/app 0.1.0-rc.6 → 0.1.0-rc.61
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/SKILL.md +25 -4
- package/build-app/index.d.mts +64 -0
- package/build-app/index.mjs +7 -0
- package/build-app/load.cjs +1 -0
- package/build-app/manifest.mjs +1 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +3 -91
- package/dist/src/engine.d.ts +5 -2
- package/dist/src/engine.js +7 -1200
- package/dist/src/receipt-wait.d.ts +24 -0
- package/dist/src/receipt-wait.js +1 -0
- package/dist/src/usage.d.ts +27 -0
- package/dist/src/usage.js +8 -0
- package/package.json +6 -1
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/src/engine.d.ts.map +0 -1
- package/dist/src/engine.js.map +0 -1
package/dist/src/engine.js
CHANGED
|
@@ -1,1200 +1,7 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.executeAppCommand = executeAppCommand;
|
|
37
|
-
const node_crypto_1 = require("node:crypto");
|
|
38
|
-
const node_child_process_1 = require("node:child_process");
|
|
39
|
-
const fs = __importStar(require("node:fs"));
|
|
40
|
-
const http = __importStar(require("node:http"));
|
|
41
|
-
const https = __importStar(require("node:https"));
|
|
42
|
-
const path = __importStar(require("node:path"));
|
|
43
|
-
const node_url_1 = require("node:url");
|
|
44
|
-
const crewx_api_credential_1 = require("@crewx/shared/crewx-api-credential");
|
|
45
|
-
const REQUEST_TIMEOUT_MS = 15000;
|
|
46
|
-
const DEV_ACTION_TIMEOUT_MS = 60000;
|
|
47
|
-
const MAX_BUILD_OUTPUT_BYTES = 64 * 1024;
|
|
48
|
-
const APP_ID_PREFIX = 'uic_';
|
|
49
|
-
const APP_DEV_API_PREFIX = '/api/v1';
|
|
50
|
-
const APP_DEV_CAPABILITIES_PATH = '/api/v1/app-dev/capabilities';
|
|
51
|
-
const APP_DEV_SESSIONS_PATH = '/api/v1/app-dev/sessions';
|
|
52
|
-
const APP_DEV_TOO_OLD_EXIT = 12;
|
|
53
|
-
const APP_DEV_CHECK_FAILED_EXIT = 13;
|
|
54
|
-
const REQUEST_ID_PATTERN = /^[A-Za-z0-9_-]{8,128}$/;
|
|
55
|
-
const LOOPBACK_HOSTS = new Set(['127.0.0.1', '[::1]', 'localhost']);
|
|
56
|
-
const DEFAULT_APP_DEV_ENDPOINT = 'http://localhost:8150';
|
|
57
|
-
const BLOCKED_ROUTE_MESSAGE = 'Do not work around this by clicking the screen or using another browser. Report that you could not use the App route.';
|
|
58
|
-
const UNAUTHORIZED_MESSAGE = 'Do not retry. Report the failure.';
|
|
59
|
-
function isRecord(value) {
|
|
60
|
-
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
61
|
-
}
|
|
62
|
-
function failure(exitCode, code, status, message) {
|
|
63
|
-
let stderr = message;
|
|
64
|
-
if (!stderr && (exitCode === 3 || exitCode === 9 || exitCode === 10 || exitCode === 11)) {
|
|
65
|
-
stderr = BLOCKED_ROUTE_MESSAGE;
|
|
66
|
-
}
|
|
67
|
-
else if (!stderr && exitCode === 4) {
|
|
68
|
-
stderr = UNAUTHORIZED_MESSAGE;
|
|
69
|
-
}
|
|
70
|
-
return {
|
|
71
|
-
exitCode,
|
|
72
|
-
payload: { ok: false, exit: exitCode, error: { code, status } },
|
|
73
|
-
...(stderr ? { stderr } : {}),
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
function success(payload) {
|
|
77
|
-
return { exitCode: 0, payload };
|
|
78
|
-
}
|
|
79
|
-
function usageFailure() {
|
|
80
|
-
return failure(2, 'USAGE', null);
|
|
81
|
-
}
|
|
82
|
-
function parseCommand(args) {
|
|
83
|
-
const command = args[0];
|
|
84
|
-
if (command !== 'list' && command !== 'open' && command !== 'state' && command !== 'run' && command !== 'receipt') {
|
|
85
|
-
return null;
|
|
86
|
-
}
|
|
87
|
-
const positional = [];
|
|
88
|
-
const options = new Map();
|
|
89
|
-
for (let index = 1; index < args.length; index += 1) {
|
|
90
|
-
const arg = args[index];
|
|
91
|
-
if (!arg.startsWith('--')) {
|
|
92
|
-
positional.push(arg);
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
const separator = arg.indexOf('=');
|
|
96
|
-
const key = separator >= 0 ? arg.slice(2, separator) : arg.slice(2);
|
|
97
|
-
const inlineValue = separator >= 0 ? arg.slice(separator + 1) : undefined;
|
|
98
|
-
if ((key !== 'input' && key !== 'request-id') || options.has(key))
|
|
99
|
-
return null;
|
|
100
|
-
const value = inlineValue ?? args[index + 1];
|
|
101
|
-
if (value === undefined)
|
|
102
|
-
return null;
|
|
103
|
-
if (inlineValue === undefined)
|
|
104
|
-
index += 1;
|
|
105
|
-
options.set(key, value);
|
|
106
|
-
}
|
|
107
|
-
if ((command === 'list' && positional.length !== 0)
|
|
108
|
-
|| (command === 'open' && positional.length !== 1)
|
|
109
|
-
|| (command === 'state' && (positional.length !== 1 || !positional[0].startsWith(APP_ID_PREFIX)))
|
|
110
|
-
|| (command === 'run' && positional.length !== 2)
|
|
111
|
-
|| (command === 'receipt' && positional.length !== 1)) {
|
|
112
|
-
return null;
|
|
113
|
-
}
|
|
114
|
-
if (command !== 'run' && (options.has('input') || options.has('request-id')))
|
|
115
|
-
return null;
|
|
116
|
-
let input = {};
|
|
117
|
-
if (options.has('input')) {
|
|
118
|
-
const raw = options.get('input');
|
|
119
|
-
try {
|
|
120
|
-
const parsed = JSON.parse(raw === '-' ? fs.readFileSync(0, 'utf8') : raw);
|
|
121
|
-
if (!isRecord(parsed))
|
|
122
|
-
return null;
|
|
123
|
-
input = parsed;
|
|
124
|
-
}
|
|
125
|
-
catch {
|
|
126
|
-
return null;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
const requestId = options.get('request-id');
|
|
130
|
-
if (requestId !== undefined && !REQUEST_ID_PATTERN.test(requestId))
|
|
131
|
-
return null;
|
|
132
|
-
return {
|
|
133
|
-
command,
|
|
134
|
-
positional,
|
|
135
|
-
input,
|
|
136
|
-
...(requestId !== undefined ? { requestId } : {}),
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
function getEndpoint(raw) {
|
|
140
|
-
if (!raw)
|
|
141
|
-
return null;
|
|
142
|
-
let endpoint;
|
|
143
|
-
try {
|
|
144
|
-
endpoint = new node_url_1.URL(raw);
|
|
145
|
-
}
|
|
146
|
-
catch {
|
|
147
|
-
return null;
|
|
148
|
-
}
|
|
149
|
-
if (!LOOPBACK_HOSTS.has(endpoint.hostname)
|
|
150
|
-
|| (endpoint.protocol !== 'http:' && endpoint.protocol !== 'https:')
|
|
151
|
-
|| endpoint.username !== ''
|
|
152
|
-
|| endpoint.password !== ''
|
|
153
|
-
|| endpoint.search !== ''
|
|
154
|
-
|| endpoint.hash !== ''
|
|
155
|
-
|| (endpoint.pathname !== '' && endpoint.pathname !== '/')) {
|
|
156
|
-
return null;
|
|
157
|
-
}
|
|
158
|
-
return endpoint;
|
|
159
|
-
}
|
|
160
|
-
function segment(value) {
|
|
161
|
-
return encodeURIComponent(value);
|
|
162
|
-
}
|
|
163
|
-
function basePath(workspaceSlug, threadId) {
|
|
164
|
-
return [
|
|
165
|
-
'api', 'v1', 'ws', segment(workspaceSlug), 'threads', segment(threadId),
|
|
166
|
-
].join('/');
|
|
167
|
-
}
|
|
168
|
-
function pathFor(context, ...segments) {
|
|
169
|
-
return `/${context.basePath}/${segments.map(segment).join('/')}`;
|
|
170
|
-
}
|
|
171
|
-
function responseErrorCode(data, fallback) {
|
|
172
|
-
if (!isRecord(data) || !isRecord(data['error']))
|
|
173
|
-
return fallback;
|
|
174
|
-
const code = data['error']['code'];
|
|
175
|
-
return typeof code === 'string' && code.length > 0 ? code : fallback;
|
|
176
|
-
}
|
|
177
|
-
function classifyHttpFailure(response) {
|
|
178
|
-
const { status, data } = response;
|
|
179
|
-
if (status >= 300 && status < 400)
|
|
180
|
-
return failure(9, 'ENDPOINT_REJECTED', status);
|
|
181
|
-
if (status === 401)
|
|
182
|
-
return failure(4, responseErrorCode(data, 'UNAUTHORIZED'), status);
|
|
183
|
-
if (status === 403)
|
|
184
|
-
return failure(5, responseErrorCode(data, 'FORBIDDEN'), status);
|
|
185
|
-
if (status === 404)
|
|
186
|
-
return failure(6, responseErrorCode(data, 'NOT_FOUND'), status);
|
|
187
|
-
if (status === 409) {
|
|
188
|
-
const code = responseErrorCode(data, 'CONFLICT');
|
|
189
|
-
if (code === 'APPROVAL_ALREADY_PENDING') {
|
|
190
|
-
const error = isRecord(data) && isRecord(data['error']) ? data['error'] : undefined;
|
|
191
|
-
const details = error && isRecord(error['details']) ? error['details'] : undefined;
|
|
192
|
-
const receiptId = error?.['receiptId'] ?? details?.['receiptId']
|
|
193
|
-
?? (isRecord(data) ? data['receiptId'] : undefined);
|
|
194
|
-
return {
|
|
195
|
-
exitCode: 8,
|
|
196
|
-
payload: {
|
|
197
|
-
ok: false,
|
|
198
|
-
exit: 8,
|
|
199
|
-
error: { code, status },
|
|
200
|
-
...(typeof receiptId === 'string' ? { receiptId } : {}),
|
|
201
|
-
},
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
return failure(7, code, status);
|
|
205
|
-
}
|
|
206
|
-
return failure(1, responseErrorCode(data, 'ERROR'), status);
|
|
207
|
-
}
|
|
208
|
-
function networkFailure(error) {
|
|
209
|
-
const errorCode = isRecord(error) && typeof error['code'] === 'string'
|
|
210
|
-
? error['code']
|
|
211
|
-
: '';
|
|
212
|
-
const codexBlocked = process.env['CODEX_SANDBOX_NETWORK_DISABLED'] === '1';
|
|
213
|
-
const errnoBlocked = !codexBlocked && (errorCode === 'EPERM' || errorCode === 'EACCES');
|
|
214
|
-
return codexBlocked || errnoBlocked
|
|
215
|
-
? failure(11, 'SANDBOX_BLOCKED', null)
|
|
216
|
-
: failure(10, 'SERVER_UNREACHABLE', null);
|
|
217
|
-
}
|
|
218
|
-
function requestJson(endpoint, authorizationToken, method, requestPath, body, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
219
|
-
const requestFunction = endpoint.protocol === 'https:' ? https.request : http.request;
|
|
220
|
-
const hostname = endpoint.hostname === '[::1]'
|
|
221
|
-
? '::1'
|
|
222
|
-
: endpoint.hostname;
|
|
223
|
-
const port = endpoint.port ? Number(endpoint.port) : undefined;
|
|
224
|
-
const bodyText = body === undefined ? undefined : JSON.stringify(body);
|
|
225
|
-
const request = requestFunction({
|
|
226
|
-
protocol: endpoint.protocol,
|
|
227
|
-
hostname,
|
|
228
|
-
...(port !== undefined ? { port } : {}),
|
|
229
|
-
method,
|
|
230
|
-
path: requestPath,
|
|
231
|
-
headers: {
|
|
232
|
-
Authorization: `Bearer ${authorizationToken}`,
|
|
233
|
-
Accept: 'application/json',
|
|
234
|
-
...(bodyText !== undefined ? { 'Content-Type': 'application/json' } : {}),
|
|
235
|
-
},
|
|
236
|
-
}, (response) => {
|
|
237
|
-
const status = response.statusCode ?? 0;
|
|
238
|
-
if (status >= 300 && status < 400) {
|
|
239
|
-
response.destroy();
|
|
240
|
-
clearTimeout(timeout);
|
|
241
|
-
resolveResponse({ status, data: undefined, invalidJson: false, rawText: '' });
|
|
242
|
-
return;
|
|
243
|
-
}
|
|
244
|
-
const chunks = [];
|
|
245
|
-
response.setEncoding('utf8');
|
|
246
|
-
response.on('data', (chunk) => chunks.push(chunk));
|
|
247
|
-
response.once('error', (error) => failResponse(error));
|
|
248
|
-
response.once('end', () => {
|
|
249
|
-
clearTimeout(timeout);
|
|
250
|
-
const raw = chunks.join('');
|
|
251
|
-
try {
|
|
252
|
-
resolveResponse({ status, data: JSON.parse(raw), invalidJson: false, rawText: raw });
|
|
253
|
-
}
|
|
254
|
-
catch {
|
|
255
|
-
resolveResponse({ status, data: undefined, invalidJson: true, rawText: raw });
|
|
256
|
-
}
|
|
257
|
-
});
|
|
258
|
-
});
|
|
259
|
-
let socketError;
|
|
260
|
-
let resolveResponse;
|
|
261
|
-
let failResponse;
|
|
262
|
-
const result = new Promise((resolve, reject) => {
|
|
263
|
-
resolveResponse = resolve;
|
|
264
|
-
failResponse = reject;
|
|
265
|
-
});
|
|
266
|
-
const timeout = setTimeout(() => {
|
|
267
|
-
const error = new Error('Request timed out');
|
|
268
|
-
error.code = 'ETIMEDOUT';
|
|
269
|
-
request.destroy(error);
|
|
270
|
-
}, timeoutMs);
|
|
271
|
-
request.on('socket', (socket) => {
|
|
272
|
-
socket.once('error', (error) => {
|
|
273
|
-
socketError = error;
|
|
274
|
-
});
|
|
275
|
-
});
|
|
276
|
-
request.once('error', (error) => {
|
|
277
|
-
clearTimeout(timeout);
|
|
278
|
-
failResponse(socketError ?? error);
|
|
279
|
-
});
|
|
280
|
-
if (bodyText !== undefined)
|
|
281
|
-
request.write(bodyText);
|
|
282
|
-
request.end();
|
|
283
|
-
return result;
|
|
284
|
-
}
|
|
285
|
-
async function performRequest(context, method, requestPath, body) {
|
|
286
|
-
try {
|
|
287
|
-
const response = await requestJson(context.endpoint, context.ticket, method, requestPath, body);
|
|
288
|
-
if (response.status < 200 || response.status >= 300) {
|
|
289
|
-
return { ok: false, outcome: classifyHttpFailure(response) };
|
|
290
|
-
}
|
|
291
|
-
if (response.invalidJson || !isRecord(response.data) || response.data['success'] !== true) {
|
|
292
|
-
return {
|
|
293
|
-
ok: false,
|
|
294
|
-
outcome: failure(1, responseErrorCode(response.data, 'ERROR'), response.status),
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
const data = response.data['data'];
|
|
298
|
-
if (!isRecord(data)) {
|
|
299
|
-
return { ok: false, outcome: failure(1, 'ERROR', response.status) };
|
|
300
|
-
}
|
|
301
|
-
return { ok: true, value: data, status: response.status };
|
|
302
|
-
}
|
|
303
|
-
catch (error) {
|
|
304
|
-
return { ok: false, outcome: networkFailure(error) };
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
function appDevTooOld(status) {
|
|
308
|
-
return failure(APP_DEV_TOO_OLD_EXIT, 'SERVER_TOO_OLD', status, 'The running CrewX does not support App dev mode.');
|
|
309
|
-
}
|
|
310
|
-
function appDevCredentialFailure(error) {
|
|
311
|
-
if (error instanceof crewx_api_credential_1.CrewxCredentialError) {
|
|
312
|
-
return failure(error.exitCode, error.code, null, error.message);
|
|
313
|
-
}
|
|
314
|
-
return failure(1, 'ERROR', null);
|
|
315
|
-
}
|
|
316
|
-
function appDevEndpoint() {
|
|
317
|
-
const raw = process.env['CREWX_ENDPOINT']?.trim() || DEFAULT_APP_DEV_ENDPOINT;
|
|
318
|
-
return getEndpoint(raw);
|
|
319
|
-
}
|
|
320
|
-
function isDirectory(directory) {
|
|
321
|
-
try {
|
|
322
|
-
return fs.statSync(directory).isDirectory();
|
|
323
|
-
}
|
|
324
|
-
catch {
|
|
325
|
-
return false;
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
function isFile(filePath) {
|
|
329
|
-
try {
|
|
330
|
-
return fs.statSync(filePath).isFile();
|
|
331
|
-
}
|
|
332
|
-
catch {
|
|
333
|
-
return false;
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
function resolveWorkspaceRoot(startDirectory = process.cwd()) {
|
|
337
|
-
let current = path.resolve(startDirectory);
|
|
338
|
-
while (true) {
|
|
339
|
-
if (isDirectory(path.join(current, 'apps')) || isFile(path.join(current, 'crewx.yaml'))) {
|
|
340
|
-
try {
|
|
341
|
-
return fs.realpathSync(current);
|
|
342
|
-
}
|
|
343
|
-
catch {
|
|
344
|
-
return undefined;
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
const parent = path.dirname(current);
|
|
348
|
-
if (parent === current)
|
|
349
|
-
return undefined;
|
|
350
|
-
current = parent;
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
function readAppManifest(directory) {
|
|
354
|
-
try {
|
|
355
|
-
const parsed = JSON.parse(fs.readFileSync(path.join(directory, 'ui-plugin.json'), 'utf8'));
|
|
356
|
-
return isRecord(parsed) ? parsed : undefined;
|
|
357
|
-
}
|
|
358
|
-
catch {
|
|
359
|
-
return undefined;
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
function isPathInside(parentDirectory, candidate) {
|
|
363
|
-
const relative = path.relative(parentDirectory, candidate);
|
|
364
|
-
return relative !== '' && relative !== '..' && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
|
|
365
|
-
}
|
|
366
|
-
function resolveWorkspaceApp(workspacePath, appSelector) {
|
|
367
|
-
if (!appSelector || appSelector === '.' || appSelector === '..' || /[\\/]/.test(appSelector)) {
|
|
368
|
-
return failure(2, 'USAGE', null, 'App must be a folder name or pluginId.');
|
|
369
|
-
}
|
|
370
|
-
const appsDirectory = path.join(workspacePath, 'apps');
|
|
371
|
-
const directDirectory = path.join(appsDirectory, appSelector);
|
|
372
|
-
let appDirectory = isDirectory(directDirectory) ? directDirectory : undefined;
|
|
373
|
-
if (!appDirectory && isDirectory(appsDirectory)) {
|
|
374
|
-
try {
|
|
375
|
-
for (const item of fs.readdirSync(appsDirectory, { withFileTypes: true })) {
|
|
376
|
-
if (!item.isDirectory())
|
|
377
|
-
continue;
|
|
378
|
-
const candidate = path.join(appsDirectory, item.name);
|
|
379
|
-
const manifest = readAppManifest(candidate);
|
|
380
|
-
if (manifest?.['pluginId'] === appSelector) {
|
|
381
|
-
appDirectory = candidate;
|
|
382
|
-
break;
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
catch {
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
let appId = appSelector;
|
|
390
|
-
if (appDirectory) {
|
|
391
|
-
try {
|
|
392
|
-
appDirectory = fs.realpathSync(appDirectory);
|
|
393
|
-
}
|
|
394
|
-
catch {
|
|
395
|
-
return failure(6, 'APP_INVALID', null);
|
|
396
|
-
}
|
|
397
|
-
if (!isPathInside(fs.realpathSync(appsDirectory), appDirectory)) {
|
|
398
|
-
return failure(2, 'USAGE', null, 'App folder must be inside the workspace apps directory.');
|
|
399
|
-
}
|
|
400
|
-
const manifest = readAppManifest(appDirectory);
|
|
401
|
-
if (typeof manifest?.['pluginId'] === 'string' && manifest['pluginId'].length > 0) {
|
|
402
|
-
appId = manifest['pluginId'];
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
else {
|
|
406
|
-
appDirectory = directDirectory;
|
|
407
|
-
}
|
|
408
|
-
return { appId, appDirectory };
|
|
409
|
-
}
|
|
410
|
-
function successEnvelope(response) {
|
|
411
|
-
if (response.status < 200 || response.status >= 300) {
|
|
412
|
-
return { ok: false, outcome: classifyHttpFailure(response) };
|
|
413
|
-
}
|
|
414
|
-
if (response.invalidJson || !isRecord(response.data) || response.data['success'] !== true
|
|
415
|
-
|| !isRecord(response.data['data'])) {
|
|
416
|
-
return { ok: false, outcome: failure(1, responseErrorCode(response.data, 'ERROR'), response.status) };
|
|
417
|
-
}
|
|
418
|
-
return {
|
|
419
|
-
ok: true,
|
|
420
|
-
value: response.data,
|
|
421
|
-
status: response.status,
|
|
422
|
-
rawText: response.rawText,
|
|
423
|
-
};
|
|
424
|
-
}
|
|
425
|
-
async function checkAppDevCapabilities(endpoint, token) {
|
|
426
|
-
try {
|
|
427
|
-
const response = await requestJson(endpoint, token, 'GET', APP_DEV_CAPABILITIES_PATH);
|
|
428
|
-
if (response.status === 404 || (response.status >= 300 && response.status < 400)
|
|
429
|
-
|| response.invalidJson) {
|
|
430
|
-
return { ok: false, outcome: appDevTooOld(response.status) };
|
|
431
|
-
}
|
|
432
|
-
if (response.status < 200 || response.status >= 300) {
|
|
433
|
-
return { ok: false, outcome: classifyHttpFailure(response) };
|
|
434
|
-
}
|
|
435
|
-
if (!isRecord(response.data) || response.data['success'] !== true
|
|
436
|
-
|| !isRecord(response.data['data']) || response.data['data']['appDev'] !== 1) {
|
|
437
|
-
return { ok: false, outcome: appDevTooOld(response.status) };
|
|
438
|
-
}
|
|
439
|
-
return {
|
|
440
|
-
ok: true,
|
|
441
|
-
value: response.data,
|
|
442
|
-
status: response.status,
|
|
443
|
-
rawText: response.rawText,
|
|
444
|
-
};
|
|
445
|
-
}
|
|
446
|
-
catch (error) {
|
|
447
|
-
return { ok: false, outcome: networkFailure(error) };
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
async function prepareAppDevContext(appSelector) {
|
|
451
|
-
const endpoint = appDevEndpoint();
|
|
452
|
-
if (!endpoint)
|
|
453
|
-
return { ok: false, outcome: failure(9, 'ENDPOINT_REJECTED', null) };
|
|
454
|
-
let token;
|
|
455
|
-
try {
|
|
456
|
-
token = (0, crewx_api_credential_1.resolveCrewxApiCredential)('app-dev').token;
|
|
457
|
-
}
|
|
458
|
-
catch (error) {
|
|
459
|
-
return { ok: false, outcome: appDevCredentialFailure(error) };
|
|
460
|
-
}
|
|
461
|
-
const capabilities = await checkAppDevCapabilities(endpoint, token);
|
|
462
|
-
if (!capabilities.ok)
|
|
463
|
-
return { ok: false, outcome: capabilities.outcome };
|
|
464
|
-
const workspacePath = resolveWorkspaceRoot();
|
|
465
|
-
if (!workspacePath) {
|
|
466
|
-
return {
|
|
467
|
-
ok: false,
|
|
468
|
-
outcome: failure(2, 'WORKSPACE_NOT_FOUND', null, 'No workspace root with apps/ or crewx.yaml was found.'),
|
|
469
|
-
};
|
|
470
|
-
}
|
|
471
|
-
const app = resolveWorkspaceApp(workspacePath, appSelector);
|
|
472
|
-
if ('exitCode' in app)
|
|
473
|
-
return { ok: false, outcome: app };
|
|
474
|
-
let response;
|
|
475
|
-
try {
|
|
476
|
-
response = await requestJson(endpoint, token, 'POST', APP_DEV_SESSIONS_PATH, {
|
|
477
|
-
workspacePath,
|
|
478
|
-
appId: app.appId,
|
|
479
|
-
});
|
|
480
|
-
}
|
|
481
|
-
catch (error) {
|
|
482
|
-
return { ok: false, outcome: networkFailure(error) };
|
|
483
|
-
}
|
|
484
|
-
const opened = successEnvelope(response);
|
|
485
|
-
if (!opened.ok)
|
|
486
|
-
return { ok: false, outcome: opened.outcome };
|
|
487
|
-
const data = opened.value['data'];
|
|
488
|
-
if (!isRecord(data)
|
|
489
|
-
|| typeof data['workspaceSlug'] !== 'string'
|
|
490
|
-
|| typeof data['appId'] !== 'string'
|
|
491
|
-
|| typeof data['devPath'] !== 'string'
|
|
492
|
-
|| !data['devPath'].startsWith('/ws/')) {
|
|
493
|
-
return { ok: false, outcome: failure(1, 'ERROR', response.status) };
|
|
494
|
-
}
|
|
495
|
-
if (!isDirectory(app.appDirectory)) {
|
|
496
|
-
return { ok: false, outcome: failure(6, 'APP_INVALID', response.status) };
|
|
497
|
-
}
|
|
498
|
-
return {
|
|
499
|
-
ok: true,
|
|
500
|
-
context: {
|
|
501
|
-
endpoint,
|
|
502
|
-
token,
|
|
503
|
-
workspacePath,
|
|
504
|
-
appId: data['appId'],
|
|
505
|
-
appDirectory: app.appDirectory,
|
|
506
|
-
devPath: data['devPath'],
|
|
507
|
-
},
|
|
508
|
-
};
|
|
509
|
-
}
|
|
510
|
-
function appDevPath(context, ...segments) {
|
|
511
|
-
return `${APP_DEV_API_PREFIX}${context.devPath}${segments.length > 0 ? `/${segments.map(segment).join('/')}` : ''}`;
|
|
512
|
-
}
|
|
513
|
-
async function performAppDevRequest(context, method, requestPath, body, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
514
|
-
try {
|
|
515
|
-
const response = await requestJson(context.endpoint, context.token, method, requestPath, body, timeoutMs);
|
|
516
|
-
return successEnvelope(response);
|
|
517
|
-
}
|
|
518
|
-
catch (error) {
|
|
519
|
-
return { ok: false, outcome: networkFailure(error) };
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
function appIdFrom(value) {
|
|
523
|
-
const direct = value['appId'] ?? value['id'];
|
|
524
|
-
if (typeof direct === 'string' && direct.length > 0)
|
|
525
|
-
return direct;
|
|
526
|
-
const definition = value['definition'];
|
|
527
|
-
if (isRecord(definition) && typeof definition['id'] === 'string')
|
|
528
|
-
return definition['id'];
|
|
529
|
-
return undefined;
|
|
530
|
-
}
|
|
531
|
-
function hashFrom(value) {
|
|
532
|
-
const direct = value['definitionHash'];
|
|
533
|
-
if (typeof direct === 'string' && direct.length > 0)
|
|
534
|
-
return direct;
|
|
535
|
-
const definition = value['definition'];
|
|
536
|
-
if (isRecord(definition) && typeof definition['hash'] === 'string') {
|
|
537
|
-
return definition['hash'];
|
|
538
|
-
}
|
|
539
|
-
return undefined;
|
|
540
|
-
}
|
|
541
|
-
function projectLauncher(value) {
|
|
542
|
-
if (!isRecord(value))
|
|
543
|
-
return undefined;
|
|
544
|
-
const appId = appIdFrom(value);
|
|
545
|
-
const definitionHash = hashFrom(value);
|
|
546
|
-
if (!appId || !definitionHash)
|
|
547
|
-
return undefined;
|
|
548
|
-
const actions = value['agentActions'];
|
|
549
|
-
return {
|
|
550
|
-
appId,
|
|
551
|
-
name: typeof value['name'] === 'string' ? value['name'] : '',
|
|
552
|
-
description: typeof value['description'] === 'string' ? value['description'] : '',
|
|
553
|
-
definitionHash,
|
|
554
|
-
agentActions: Array.isArray(actions) ? actions : [],
|
|
555
|
-
};
|
|
556
|
-
}
|
|
557
|
-
async function loadLaunchers(context) {
|
|
558
|
-
const response = await performRequest(context, 'GET', pathFor(context, 'ui-instances', 'apps'));
|
|
559
|
-
if (!response.ok)
|
|
560
|
-
return response;
|
|
561
|
-
const apps = response.value['apps'];
|
|
562
|
-
if (!Array.isArray(apps)) {
|
|
563
|
-
return { ok: false, outcome: failure(1, 'ERROR', response.status) };
|
|
564
|
-
}
|
|
565
|
-
const projected = apps.map(projectLauncher);
|
|
566
|
-
if (projected.some((item) => item === undefined)) {
|
|
567
|
-
return { ok: false, outcome: failure(1, 'ERROR', response.status) };
|
|
568
|
-
}
|
|
569
|
-
return { ok: true, value: projected, status: response.status };
|
|
570
|
-
}
|
|
571
|
-
function findLauncher(apps, appId) {
|
|
572
|
-
return apps.find((app) => app.appId === appId);
|
|
573
|
-
}
|
|
574
|
-
async function openLauncher(context, launcher) {
|
|
575
|
-
const response = await performRequest(context, 'POST', pathFor(context, 'ui-instances', 'open'), {
|
|
576
|
-
appId: launcher.appId,
|
|
577
|
-
expectedDefinitionHash: launcher.definitionHash,
|
|
578
|
-
});
|
|
579
|
-
if (!response.ok)
|
|
580
|
-
return response;
|
|
581
|
-
const instance = isRecord(response.value['instance']) ? response.value['instance'] : response.value;
|
|
582
|
-
if (!isRecord(instance)) {
|
|
583
|
-
return { ok: false, outcome: failure(1, 'ERROR', response.status) };
|
|
584
|
-
}
|
|
585
|
-
const instanceId = instance['instanceId'] ?? instance['id'];
|
|
586
|
-
const revision = instance['revision'];
|
|
587
|
-
if (typeof instanceId !== 'string' || typeof revision !== 'number') {
|
|
588
|
-
return { ok: false, outcome: failure(1, 'ERROR', response.status) };
|
|
589
|
-
}
|
|
590
|
-
const ai = isRecord(response.value['ai'])
|
|
591
|
-
? response.value['ai']
|
|
592
|
-
: isRecord(instance['ai']) ? instance['ai'] : undefined;
|
|
593
|
-
return {
|
|
594
|
-
ok: true,
|
|
595
|
-
status: response.status,
|
|
596
|
-
value: {
|
|
597
|
-
appId: launcher.appId,
|
|
598
|
-
definitionHash: launcher.definitionHash,
|
|
599
|
-
instanceId,
|
|
600
|
-
revision,
|
|
601
|
-
state: ai && Object.prototype.hasOwnProperty.call(ai, 'state') ? ai['state'] : null,
|
|
602
|
-
},
|
|
603
|
-
};
|
|
604
|
-
}
|
|
605
|
-
async function readInstanceState(context, instanceId) {
|
|
606
|
-
const response = await performRequest(context, 'GET', pathFor(context, 'ui-instances', instanceId));
|
|
607
|
-
if (!response.ok)
|
|
608
|
-
return response;
|
|
609
|
-
const value = response.value;
|
|
610
|
-
const responseInstanceId = value['instanceId'];
|
|
611
|
-
const revision = value['revision'];
|
|
612
|
-
if (typeof responseInstanceId !== 'string' || typeof revision !== 'number') {
|
|
613
|
-
return { ok: false, outcome: failure(1, 'ERROR', response.status) };
|
|
614
|
-
}
|
|
615
|
-
const ai = isRecord(value['ai']) ? value['ai'] : undefined;
|
|
616
|
-
const passthrough = (key) => {
|
|
617
|
-
if (Object.prototype.hasOwnProperty.call(value, key))
|
|
618
|
-
return { [key]: value[key] };
|
|
619
|
-
if (ai && Object.prototype.hasOwnProperty.call(ai, key))
|
|
620
|
-
return { [key]: ai[key] };
|
|
621
|
-
return {};
|
|
622
|
-
};
|
|
623
|
-
return {
|
|
624
|
-
ok: true,
|
|
625
|
-
status: response.status,
|
|
626
|
-
value: {
|
|
627
|
-
instanceId: responseInstanceId,
|
|
628
|
-
revision,
|
|
629
|
-
state: ai && Object.prototype.hasOwnProperty.call(ai, 'state') ? ai['state'] : null,
|
|
630
|
-
buttons: ai && Object.prototype.hasOwnProperty.call(ai, 'buttons') ? ai['buttons'] : null,
|
|
631
|
-
...passthrough('agentAccess'),
|
|
632
|
-
...passthrough('modelContext'),
|
|
633
|
-
...passthrough('pendingApproval'),
|
|
634
|
-
},
|
|
635
|
-
};
|
|
636
|
-
}
|
|
637
|
-
async function runAction(context, instanceId, actionId, requestId, input) {
|
|
638
|
-
const response = await performRequest(context, 'POST', pathFor(context, 'ui-instances', instanceId, 'actions'), { actionId, requestId, input });
|
|
639
|
-
if (!response.ok)
|
|
640
|
-
return response;
|
|
641
|
-
const receipt = response.value['receipt'];
|
|
642
|
-
if (!isRecord(receipt)) {
|
|
643
|
-
return { ok: false, outcome: failure(1, 'ERROR', response.status) };
|
|
644
|
-
}
|
|
645
|
-
const receiptId = receipt['receiptId'] ?? receipt['id'];
|
|
646
|
-
const approval = response.value['approval'];
|
|
647
|
-
if (response.status === 202 && isRecord(approval)) {
|
|
648
|
-
const approvalId = approval['approvalId'];
|
|
649
|
-
const expiresAt = approval['expiresAt'];
|
|
650
|
-
const resolvedRequestId = typeof receipt['requestId'] === 'string'
|
|
651
|
-
? receipt['requestId']
|
|
652
|
-
: typeof approval['requestId'] === 'string'
|
|
653
|
-
? approval['requestId']
|
|
654
|
-
: requestId;
|
|
655
|
-
if (typeof receiptId !== 'string' || typeof approvalId !== 'string'
|
|
656
|
-
|| (typeof expiresAt !== 'string' && typeof expiresAt !== 'number')) {
|
|
657
|
-
return { ok: false, outcome: failure(1, 'ERROR', response.status) };
|
|
658
|
-
}
|
|
659
|
-
return {
|
|
660
|
-
ok: false,
|
|
661
|
-
outcome: {
|
|
662
|
-
exitCode: 8,
|
|
663
|
-
payload: {
|
|
664
|
-
ok: false,
|
|
665
|
-
exit: 8,
|
|
666
|
-
error: { code: 'APPROVAL_PENDING', status: 202 },
|
|
667
|
-
receiptId,
|
|
668
|
-
requestId: resolvedRequestId,
|
|
669
|
-
approvalId,
|
|
670
|
-
expiresAt,
|
|
671
|
-
},
|
|
672
|
-
},
|
|
673
|
-
};
|
|
674
|
-
}
|
|
675
|
-
const status = receipt['status'] ?? (response.status === 202 ? 'requested' : 'settled');
|
|
676
|
-
const resolve = response.value['resolve'];
|
|
677
|
-
const ai = isRecord(resolve) && isRecord(resolve['ai']) ? resolve['ai'] : undefined;
|
|
678
|
-
return {
|
|
679
|
-
ok: true,
|
|
680
|
-
status: response.status,
|
|
681
|
-
value: {
|
|
682
|
-
receiptId: typeof receiptId === 'string' ? receiptId : '',
|
|
683
|
-
requestId: typeof receipt['requestId'] === 'string' ? receipt['requestId'] : requestId,
|
|
684
|
-
instanceId,
|
|
685
|
-
actionId: typeof receipt['actionId'] === 'string' ? receipt['actionId'] : actionId,
|
|
686
|
-
status,
|
|
687
|
-
outcome: Object.prototype.hasOwnProperty.call(receipt, 'outcome') ? receipt['outcome'] : null,
|
|
688
|
-
state: ai && Object.prototype.hasOwnProperty.call(ai, 'state') ? ai['state'] : null,
|
|
689
|
-
},
|
|
690
|
-
};
|
|
691
|
-
}
|
|
692
|
-
function contextFromEnvironment() {
|
|
693
|
-
const ticket = process.env['CREWX_TASK_TICKET'] ?? '';
|
|
694
|
-
const workspaceSlug = process.env['CREWX_WORKSPACE_SLUG'] ?? '';
|
|
695
|
-
const threadId = process.env['CREWX_THREAD_ID'] ?? '';
|
|
696
|
-
if (!ticket.startsWith('crewxtk_') || !workspaceSlug || !threadId) {
|
|
697
|
-
return failure(3, 'NO_TICKET', null);
|
|
698
|
-
}
|
|
699
|
-
const endpoint = getEndpoint(process.env['CREWX_ENDPOINT']);
|
|
700
|
-
if (!endpoint)
|
|
701
|
-
return failure(9, 'ENDPOINT_REJECTED', null);
|
|
702
|
-
return {
|
|
703
|
-
ticket,
|
|
704
|
-
workspaceSlug,
|
|
705
|
-
threadId,
|
|
706
|
-
endpoint,
|
|
707
|
-
basePath: basePath(workspaceSlug, threadId),
|
|
708
|
-
};
|
|
709
|
-
}
|
|
710
|
-
function isAppCommandOutcome(value) {
|
|
711
|
-
return 'exitCode' in value;
|
|
712
|
-
}
|
|
713
|
-
function parseSingleAppCommand(args, command) {
|
|
714
|
-
if (args[0] !== command)
|
|
715
|
-
return null;
|
|
716
|
-
const positional = [];
|
|
717
|
-
let flag = false;
|
|
718
|
-
for (const arg of args.slice(1)) {
|
|
719
|
-
if (arg === (command === 'dev' ? '--no-open' : '--json')) {
|
|
720
|
-
if (flag)
|
|
721
|
-
return null;
|
|
722
|
-
flag = true;
|
|
723
|
-
}
|
|
724
|
-
else if (arg.startsWith('--')) {
|
|
725
|
-
return null;
|
|
726
|
-
}
|
|
727
|
-
else {
|
|
728
|
-
positional.push(arg);
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
if (positional.length !== 1)
|
|
732
|
-
return null;
|
|
733
|
-
return { appSelector: positional[0], flag };
|
|
734
|
-
}
|
|
735
|
-
function standardJsonOutput(outcome) {
|
|
736
|
-
return `${JSON.stringify(outcome.payload)}\n`;
|
|
737
|
-
}
|
|
738
|
-
async function executeAppCheck(args) {
|
|
739
|
-
const parsed = parseSingleAppCommand(args, 'check');
|
|
740
|
-
if (!parsed)
|
|
741
|
-
return usageFailure();
|
|
742
|
-
const prepared = await prepareAppDevContext(parsed.appSelector);
|
|
743
|
-
if (!prepared.ok)
|
|
744
|
-
return prepared.outcome;
|
|
745
|
-
const response = await performAppDevRequest(prepared.context, 'GET', appDevPath(prepared.context, 'check'));
|
|
746
|
-
if (!response.ok)
|
|
747
|
-
return response.outcome;
|
|
748
|
-
const data = response.value['data'];
|
|
749
|
-
if (!isRecord(data))
|
|
750
|
-
return failure(1, 'ERROR', response.status);
|
|
751
|
-
const summary = isRecord(data['summary']) ? data['summary'] : {};
|
|
752
|
-
const items = Array.isArray(data['items']) ? data['items'] : [];
|
|
753
|
-
const errors = typeof summary['errors'] === 'number'
|
|
754
|
-
? summary['errors']
|
|
755
|
-
: items.filter((item) => isRecord(item) && item['severity'] === 'error').length;
|
|
756
|
-
const warnings = typeof summary['warnings'] === 'number'
|
|
757
|
-
? summary['warnings']
|
|
758
|
-
: items.filter((item) => isRecord(item) && item['severity'] === 'warning').length;
|
|
759
|
-
const exitCode = errors === 0 ? 0 : APP_DEV_CHECK_FAILED_EXIT;
|
|
760
|
-
if (parsed.flag) {
|
|
761
|
-
return {
|
|
762
|
-
exitCode,
|
|
763
|
-
payload: response.value,
|
|
764
|
-
stdout: `${response.rawText ?? standardJsonOutput({ exitCode, payload: response.value })}`
|
|
765
|
-
+ (response.rawText?.endsWith('\n') ? '' : '\n'),
|
|
766
|
-
suppressOutput: true,
|
|
767
|
-
};
|
|
768
|
-
}
|
|
769
|
-
const lines = items.flatMap((item) => {
|
|
770
|
-
if (!isRecord(item))
|
|
771
|
-
return [];
|
|
772
|
-
const severity = item['severity'];
|
|
773
|
-
const marker = severity === 'error' ? '✕' : severity === 'ok' ? '✓' : '!';
|
|
774
|
-
const where = typeof item['where'] === 'string' ? item['where'] : '';
|
|
775
|
-
const code = typeof item['code'] === 'string' ? item['code'] : '';
|
|
776
|
-
return [`${marker} ${where} ${code}`];
|
|
777
|
-
});
|
|
778
|
-
lines.push(`Summary: ${errors} errors, ${warnings} warnings`);
|
|
779
|
-
return {
|
|
780
|
-
exitCode,
|
|
781
|
-
payload: {},
|
|
782
|
-
stdout: `${lines.join('\n')}\n`,
|
|
783
|
-
suppressOutput: true,
|
|
784
|
-
};
|
|
785
|
-
}
|
|
786
|
-
async function getPairingDevUrl(context) {
|
|
787
|
-
let pairingToken;
|
|
788
|
-
try {
|
|
789
|
-
pairingToken = (0, crewx_api_credential_1.resolveCrewxApiCredential)('pairing').token;
|
|
790
|
-
}
|
|
791
|
-
catch (error) {
|
|
792
|
-
return appDevCredentialFailure(error);
|
|
793
|
-
}
|
|
794
|
-
let response;
|
|
795
|
-
try {
|
|
796
|
-
response = await requestJson(context.endpoint, pairingToken, 'POST', '/api/v1/auth/local/code');
|
|
797
|
-
}
|
|
798
|
-
catch (error) {
|
|
799
|
-
return networkFailure(error);
|
|
800
|
-
}
|
|
801
|
-
if (response.status < 200 || response.status >= 300) {
|
|
802
|
-
return classifyHttpFailure(response);
|
|
803
|
-
}
|
|
804
|
-
if (response.invalidJson || !isRecord(response.data) || typeof response.data['url'] !== 'string') {
|
|
805
|
-
return failure(1, 'PAIRING_CODE_INVALID', response.status);
|
|
806
|
-
}
|
|
807
|
-
try {
|
|
808
|
-
const pairingUrl = new node_url_1.URL(response.data['url']);
|
|
809
|
-
const pairingEndpoint = getEndpoint(pairingUrl.origin);
|
|
810
|
-
const match = pairingUrl.hash.match(/^#pair=(.+)$/);
|
|
811
|
-
if (!pairingEndpoint || !match)
|
|
812
|
-
return failure(1, 'PAIRING_CODE_INVALID', response.status);
|
|
813
|
-
return `${pairingEndpoint.origin}${context.devPath}#pair=${encodeURIComponent(match[1])}`;
|
|
814
|
-
}
|
|
815
|
-
catch {
|
|
816
|
-
return failure(1, 'PAIRING_CODE_INVALID', response.status);
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
async function openPlatformUrl(url) {
|
|
820
|
-
const command = process.platform === 'darwin'
|
|
821
|
-
? 'open'
|
|
822
|
-
: process.platform === 'win32' ? 'explorer.exe' : 'xdg-open';
|
|
823
|
-
const child = (0, node_child_process_1.spawn)(command, [url], { detached: true, stdio: 'ignore' });
|
|
824
|
-
await new Promise((resolve, reject) => {
|
|
825
|
-
child.once('error', reject);
|
|
826
|
-
child.once('spawn', () => {
|
|
827
|
-
child.unref();
|
|
828
|
-
resolve();
|
|
829
|
-
});
|
|
830
|
-
});
|
|
831
|
-
}
|
|
832
|
-
async function executeAppDevAction(args) {
|
|
833
|
-
const parsed = parseCommand(['run', ...args.slice(2)]);
|
|
834
|
-
if (!parsed || parsed.command !== 'run')
|
|
835
|
-
return usageFailure();
|
|
836
|
-
const [appSelector, actionId] = parsed.positional;
|
|
837
|
-
const prepared = await prepareAppDevContext(appSelector);
|
|
838
|
-
if (!prepared.ok)
|
|
839
|
-
return prepared.outcome;
|
|
840
|
-
const state = await performAppDevRequest(prepared.context, 'GET', appDevPath(prepared.context, 'state'));
|
|
841
|
-
if (!state.ok)
|
|
842
|
-
return state.outcome;
|
|
843
|
-
const stateData = state.value['data'];
|
|
844
|
-
if (!isRecord(stateData) || typeof stateData['revision'] !== 'number') {
|
|
845
|
-
return failure(1, 'APP_DEV_RESPONSE_INVALID', state.status);
|
|
846
|
-
}
|
|
847
|
-
const requestId = parsed.requestId ?? `cxa_${(0, node_crypto_1.randomBytes)(16).toString('base64url')}`;
|
|
848
|
-
const response = await performAppDevRequest(prepared.context, 'POST', appDevPath(prepared.context, 'actions'), {
|
|
849
|
-
actionId,
|
|
850
|
-
requestId,
|
|
851
|
-
input: parsed.input,
|
|
852
|
-
expectedRevision: stateData['revision'],
|
|
853
|
-
}, DEV_ACTION_TIMEOUT_MS);
|
|
854
|
-
if (!response.ok)
|
|
855
|
-
return response.outcome;
|
|
856
|
-
const responseData = response.value['data'];
|
|
857
|
-
const receipt = isRecord(responseData) && isRecord(responseData['receipt'])
|
|
858
|
-
? responseData['receipt']
|
|
859
|
-
: undefined;
|
|
860
|
-
return {
|
|
861
|
-
exitCode: receipt?.['outcome'] === 'succeeded' ? 0 : 1,
|
|
862
|
-
payload: response.value,
|
|
863
|
-
stdout: `${response.rawText ?? standardJsonOutput({ exitCode: 0, payload: response.value })}`
|
|
864
|
-
+ (response.rawText?.endsWith('\n') ? '' : '\n'),
|
|
865
|
-
suppressOutput: true,
|
|
866
|
-
};
|
|
867
|
-
}
|
|
868
|
-
function hasBuildScript(appDirectory) {
|
|
869
|
-
try {
|
|
870
|
-
const packageJson = JSON.parse(fs.readFileSync(path.join(appDirectory, 'package.json'), 'utf8'));
|
|
871
|
-
if (!isRecord(packageJson) || !isRecord(packageJson['scripts']))
|
|
872
|
-
return false;
|
|
873
|
-
const build = packageJson['scripts']['build'];
|
|
874
|
-
return typeof build === 'string' && build.trim().length > 0;
|
|
875
|
-
}
|
|
876
|
-
catch {
|
|
877
|
-
return false;
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
function buildOutputPaths(appDirectory) {
|
|
881
|
-
const result = new Set([path.normalize('ui-plugin.json')]);
|
|
882
|
-
const manifest = readAppManifest(appDirectory);
|
|
883
|
-
const entry = manifest?.['entry'];
|
|
884
|
-
if (typeof entry === 'string' && entry.length > 0 && !path.isAbsolute(entry)) {
|
|
885
|
-
const normalized = path.normalize(entry);
|
|
886
|
-
if (normalized !== '..' && !normalized.startsWith(`..${path.sep}`))
|
|
887
|
-
result.add(normalized);
|
|
888
|
-
}
|
|
889
|
-
return result;
|
|
890
|
-
}
|
|
891
|
-
function outputFingerprint(appDirectory, relativePath) {
|
|
892
|
-
try {
|
|
893
|
-
const stats = fs.statSync(path.join(appDirectory, relativePath));
|
|
894
|
-
return `${stats.size}:${stats.mtimeMs}:${stats.ino}`;
|
|
895
|
-
}
|
|
896
|
-
catch {
|
|
897
|
-
return null;
|
|
898
|
-
}
|
|
899
|
-
}
|
|
900
|
-
function snapshotBuildOutputs(appDirectory, paths) {
|
|
901
|
-
return new Map([...paths].map((relativePath) => [
|
|
902
|
-
relativePath,
|
|
903
|
-
outputFingerprint(appDirectory, relativePath),
|
|
904
|
-
]));
|
|
905
|
-
}
|
|
906
|
-
function runNpmBuild(appDirectory, onChild) {
|
|
907
|
-
const startedAt = Date.now();
|
|
908
|
-
let output = '';
|
|
909
|
-
let settled = false;
|
|
910
|
-
const appendOutput = (chunk) => {
|
|
911
|
-
const next = Buffer.from(output + (Buffer.isBuffer(chunk) ? chunk.toString('utf8') : chunk), 'utf8');
|
|
912
|
-
output = next.length > MAX_BUILD_OUTPUT_BYTES
|
|
913
|
-
? next.subarray(next.length - MAX_BUILD_OUTPUT_BYTES).toString('utf8')
|
|
914
|
-
: next.toString('utf8');
|
|
915
|
-
};
|
|
916
|
-
return new Promise((resolve) => {
|
|
917
|
-
const child = (0, node_child_process_1.spawn)('npm', ['run', 'build'], {
|
|
918
|
-
cwd: appDirectory,
|
|
919
|
-
env: process.env,
|
|
920
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
921
|
-
});
|
|
922
|
-
onChild(child);
|
|
923
|
-
child.stdout?.on('data', appendOutput);
|
|
924
|
-
child.stderr?.on('data', appendOutput);
|
|
925
|
-
const finish = (ok) => {
|
|
926
|
-
if (settled)
|
|
927
|
-
return;
|
|
928
|
-
settled = true;
|
|
929
|
-
onChild(undefined);
|
|
930
|
-
resolve({
|
|
931
|
-
ok,
|
|
932
|
-
ms: Math.max(0, Date.now() - startedAt),
|
|
933
|
-
output,
|
|
934
|
-
});
|
|
935
|
-
};
|
|
936
|
-
child.once('error', (error) => {
|
|
937
|
-
appendOutput(`${error.message}\n`);
|
|
938
|
-
finish(false);
|
|
939
|
-
});
|
|
940
|
-
child.once('close', (code) => finish(code === 0));
|
|
941
|
-
});
|
|
942
|
-
}
|
|
943
|
-
function responseCode(outcome) {
|
|
944
|
-
const error = outcome.payload['error'];
|
|
945
|
-
return isRecord(error) && typeof error['code'] === 'string' ? error['code'] : 'ERROR';
|
|
946
|
-
}
|
|
947
|
-
async function postAppDevReload(context, body) {
|
|
948
|
-
const response = await performAppDevRequest(context, 'POST', appDevPath(context, 'reload'), body);
|
|
949
|
-
if (!response.ok)
|
|
950
|
-
return { errorCode: responseCode(response.outcome) };
|
|
951
|
-
const data = response.value['data'];
|
|
952
|
-
return isRecord(data) && typeof data['generation'] === 'number'
|
|
953
|
-
? { generation: data['generation'] }
|
|
954
|
-
: { errorCode: 'APP_DEV_RESPONSE_INVALID' };
|
|
955
|
-
}
|
|
956
|
-
async function runAppDevWatcher(context, buildEnabled) {
|
|
957
|
-
const parentPid = process.ppid;
|
|
958
|
-
let watcher;
|
|
959
|
-
try {
|
|
960
|
-
watcher = fs.watch(context.appDirectory, { recursive: true }, (_event, filename) => {
|
|
961
|
-
onFileChange(filename);
|
|
962
|
-
});
|
|
963
|
-
}
|
|
964
|
-
catch (error) {
|
|
965
|
-
return failure(1, 'WATCH_FAILED', null, error instanceof Error ? error.message : 'Unable to watch the App folder.');
|
|
966
|
-
}
|
|
967
|
-
let closed = false;
|
|
968
|
-
let stopRequested = false;
|
|
969
|
-
let cycleRunning = false;
|
|
970
|
-
let queuedChange = false;
|
|
971
|
-
let queuedPath = '';
|
|
972
|
-
let pendingPath = '';
|
|
973
|
-
let debounceTimer;
|
|
974
|
-
let parentCheckTimer;
|
|
975
|
-
let activeBuild;
|
|
976
|
-
let finalOutcome = { exitCode: 0, payload: {}, suppressOutput: true };
|
|
977
|
-
const recentOutputEvents = new Map();
|
|
978
|
-
let resolveRun;
|
|
979
|
-
const result = new Promise((resolve) => {
|
|
980
|
-
resolveRun = resolve;
|
|
981
|
-
});
|
|
982
|
-
const writeLine = (line) => {
|
|
983
|
-
process.stdout.write(`${line}\n`);
|
|
984
|
-
};
|
|
985
|
-
const finish = () => {
|
|
986
|
-
if (closed)
|
|
987
|
-
return;
|
|
988
|
-
closed = true;
|
|
989
|
-
if (debounceTimer)
|
|
990
|
-
clearTimeout(debounceTimer);
|
|
991
|
-
if (parentCheckTimer)
|
|
992
|
-
clearInterval(parentCheckTimer);
|
|
993
|
-
process.removeListener('SIGINT', onSignal);
|
|
994
|
-
process.removeListener('SIGTERM', onSignal);
|
|
995
|
-
resolveRun(finalOutcome);
|
|
996
|
-
};
|
|
997
|
-
const onSignal = () => {
|
|
998
|
-
if (stopRequested)
|
|
999
|
-
return;
|
|
1000
|
-
stopRequested = true;
|
|
1001
|
-
watcher.close();
|
|
1002
|
-
if (debounceTimer)
|
|
1003
|
-
clearTimeout(debounceTimer);
|
|
1004
|
-
if (activeBuild && activeBuild.exitCode === null)
|
|
1005
|
-
activeBuild.kill('SIGTERM');
|
|
1006
|
-
if (!cycleRunning)
|
|
1007
|
-
finish();
|
|
1008
|
-
};
|
|
1009
|
-
const onFileChange = (filename) => {
|
|
1010
|
-
if (stopRequested || closed)
|
|
1011
|
-
return;
|
|
1012
|
-
const relativePath = filename === null ? '' : path.normalize(filename.toString());
|
|
1013
|
-
const parts = relativePath.split(path.sep);
|
|
1014
|
-
if (parts.some((part) => part === 'node_modules' || part === '.crewx' || part === 'dist'))
|
|
1015
|
-
return;
|
|
1016
|
-
const outputPaths = buildEnabled ? buildOutputPaths(context.appDirectory) : new Set();
|
|
1017
|
-
if (activeBuild && outputPaths.has(relativePath))
|
|
1018
|
-
return;
|
|
1019
|
-
const suppressedUntil = recentOutputEvents.get(relativePath);
|
|
1020
|
-
if (suppressedUntil !== undefined) {
|
|
1021
|
-
if (Date.now() <= suppressedUntil)
|
|
1022
|
-
return;
|
|
1023
|
-
recentOutputEvents.delete(relativePath);
|
|
1024
|
-
}
|
|
1025
|
-
if (cycleRunning) {
|
|
1026
|
-
queuedChange = true;
|
|
1027
|
-
queuedPath = relativePath || 'App files';
|
|
1028
|
-
return;
|
|
1029
|
-
}
|
|
1030
|
-
pendingPath = relativePath || 'App files';
|
|
1031
|
-
if (debounceTimer)
|
|
1032
|
-
clearTimeout(debounceTimer);
|
|
1033
|
-
debounceTimer = setTimeout(() => {
|
|
1034
|
-
debounceTimer = undefined;
|
|
1035
|
-
const changedPath = pendingPath || 'App files';
|
|
1036
|
-
pendingPath = '';
|
|
1037
|
-
void runCycle(changedPath, false);
|
|
1038
|
-
}, 200);
|
|
1039
|
-
};
|
|
1040
|
-
const reloadLabel = async (body) => {
|
|
1041
|
-
const reload = await postAppDevReload(context, body);
|
|
1042
|
-
return reload.generation === undefined ? `failed ${reload.errorCode ?? 'ERROR'}` : String(reload.generation);
|
|
1043
|
-
};
|
|
1044
|
-
const runCycle = async (changedPath, initial) => {
|
|
1045
|
-
if (stopRequested || closed)
|
|
1046
|
-
return;
|
|
1047
|
-
cycleRunning = true;
|
|
1048
|
-
if (buildEnabled) {
|
|
1049
|
-
const outputPaths = buildOutputPaths(context.appDirectory);
|
|
1050
|
-
const before = snapshotBuildOutputs(context.appDirectory, outputPaths);
|
|
1051
|
-
const build = await runNpmBuild(context.appDirectory, (child) => {
|
|
1052
|
-
activeBuild = child;
|
|
1053
|
-
});
|
|
1054
|
-
const after = snapshotBuildOutputs(context.appDirectory, outputPaths);
|
|
1055
|
-
for (const [outputPath, fingerprint] of after) {
|
|
1056
|
-
if (before.get(outputPath) !== fingerprint) {
|
|
1057
|
-
recentOutputEvents.set(outputPath, Date.now() + 1000);
|
|
1058
|
-
}
|
|
1059
|
-
}
|
|
1060
|
-
if (stopRequested) {
|
|
1061
|
-
cycleRunning = false;
|
|
1062
|
-
finish();
|
|
1063
|
-
return;
|
|
1064
|
-
}
|
|
1065
|
-
const reloadBody = {
|
|
1066
|
-
reason: 'build',
|
|
1067
|
-
build: {
|
|
1068
|
-
ok: build.ok,
|
|
1069
|
-
ms: build.ms,
|
|
1070
|
-
...(!build.ok ? { output: build.output } : {}),
|
|
1071
|
-
},
|
|
1072
|
-
};
|
|
1073
|
-
const generationLabel = await reloadLabel(reloadBody);
|
|
1074
|
-
writeLine(`${initial ? 'initial' : changedPath} -> build ${build.ok ? 'ok' : 'failed'} ${build.ms}ms -> reload ${generationLabel}`);
|
|
1075
|
-
}
|
|
1076
|
-
else if (!initial) {
|
|
1077
|
-
const generationLabel = await reloadLabel({ reason: 'manual' });
|
|
1078
|
-
writeLine(`${changedPath} -> reload ${generationLabel}`);
|
|
1079
|
-
}
|
|
1080
|
-
cycleRunning = false;
|
|
1081
|
-
if (stopRequested) {
|
|
1082
|
-
finish();
|
|
1083
|
-
return;
|
|
1084
|
-
}
|
|
1085
|
-
if (queuedChange) {
|
|
1086
|
-
const nextPath = queuedPath || 'App files';
|
|
1087
|
-
queuedChange = false;
|
|
1088
|
-
queuedPath = '';
|
|
1089
|
-
void runCycle(nextPath, false);
|
|
1090
|
-
return;
|
|
1091
|
-
}
|
|
1092
|
-
if (stopRequested)
|
|
1093
|
-
finish();
|
|
1094
|
-
};
|
|
1095
|
-
watcher.once('error', (error) => {
|
|
1096
|
-
finalOutcome = failure(1, 'WATCH_FAILED', null, error.message);
|
|
1097
|
-
onSignal();
|
|
1098
|
-
});
|
|
1099
|
-
process.once('SIGINT', onSignal);
|
|
1100
|
-
process.once('SIGTERM', onSignal);
|
|
1101
|
-
parentCheckTimer = setInterval(() => {
|
|
1102
|
-
if (process.ppid !== parentPid)
|
|
1103
|
-
onSignal();
|
|
1104
|
-
}, 1000);
|
|
1105
|
-
parentCheckTimer.unref();
|
|
1106
|
-
if (buildEnabled)
|
|
1107
|
-
void runCycle('initial', true);
|
|
1108
|
-
return result;
|
|
1109
|
-
}
|
|
1110
|
-
async function executeAppDev(args) {
|
|
1111
|
-
const parsed = parseSingleAppCommand(args, 'dev');
|
|
1112
|
-
if (!parsed)
|
|
1113
|
-
return usageFailure();
|
|
1114
|
-
const prepared = await prepareAppDevContext(parsed.appSelector);
|
|
1115
|
-
if (!prepared.ok)
|
|
1116
|
-
return prepared.outcome;
|
|
1117
|
-
const browserUrl = `${prepared.context.endpoint.origin}${prepared.context.devPath}`;
|
|
1118
|
-
process.stdout.write(`${browserUrl}\n`);
|
|
1119
|
-
if (!parsed.flag) {
|
|
1120
|
-
const pairingUrl = await getPairingDevUrl(prepared.context);
|
|
1121
|
-
if (typeof pairingUrl !== 'string')
|
|
1122
|
-
return pairingUrl;
|
|
1123
|
-
try {
|
|
1124
|
-
await openPlatformUrl(pairingUrl);
|
|
1125
|
-
}
|
|
1126
|
-
catch (error) {
|
|
1127
|
-
return failure(1, 'BROWSER_OPEN_FAILED', null, error instanceof Error ? error.message : 'Could not open the App dev tab.');
|
|
1128
|
-
}
|
|
1129
|
-
}
|
|
1130
|
-
return runAppDevWatcher(prepared.context, hasBuildScript(prepared.context.appDirectory));
|
|
1131
|
-
}
|
|
1132
|
-
async function executeAppCommand(args) {
|
|
1133
|
-
if (args[0] === 'dev')
|
|
1134
|
-
return executeAppDev(args);
|
|
1135
|
-
if (args[0] === 'check')
|
|
1136
|
-
return executeAppCheck(args);
|
|
1137
|
-
if (args[0] === 'run' && args[1] === '--dev')
|
|
1138
|
-
return executeAppDevAction(args);
|
|
1139
|
-
const parsed = parseCommand(args);
|
|
1140
|
-
if (!parsed)
|
|
1141
|
-
return usageFailure();
|
|
1142
|
-
const context = contextFromEnvironment();
|
|
1143
|
-
if (isAppCommandOutcome(context))
|
|
1144
|
-
return context;
|
|
1145
|
-
if (parsed.command === 'list') {
|
|
1146
|
-
const result = await loadLaunchers(context);
|
|
1147
|
-
if (!result.ok)
|
|
1148
|
-
return result.outcome;
|
|
1149
|
-
return success({ ok: true, apps: result.value });
|
|
1150
|
-
}
|
|
1151
|
-
if (parsed.command === 'receipt') {
|
|
1152
|
-
const receiptId = parsed.positional[0];
|
|
1153
|
-
const response = await performRequest(context, 'GET', pathFor(context, 'ui-action-receipts', receiptId));
|
|
1154
|
-
if (!response.ok)
|
|
1155
|
-
return response.outcome;
|
|
1156
|
-
const receipt = response.value['receipt'];
|
|
1157
|
-
if (!isRecord(receipt))
|
|
1158
|
-
return failure(1, 'ERROR', response.status);
|
|
1159
|
-
return success({ ok: true, receipt });
|
|
1160
|
-
}
|
|
1161
|
-
if (parsed.command === 'state') {
|
|
1162
|
-
const result = await readInstanceState(context, parsed.positional[0]);
|
|
1163
|
-
if (!result.ok)
|
|
1164
|
-
return result.outcome;
|
|
1165
|
-
return success({ ok: true, ...result.value });
|
|
1166
|
-
}
|
|
1167
|
-
if (parsed.command === 'open') {
|
|
1168
|
-
const appId = parsed.positional[0];
|
|
1169
|
-
const list = await loadLaunchers(context);
|
|
1170
|
-
if (!list.ok)
|
|
1171
|
-
return list.outcome;
|
|
1172
|
-
const launcher = findLauncher(list.value, appId);
|
|
1173
|
-
if (!launcher)
|
|
1174
|
-
return failure(6, 'MCP_APP_NOT_FOUND', null);
|
|
1175
|
-
const opened = await openLauncher(context, launcher);
|
|
1176
|
-
if (!opened.ok)
|
|
1177
|
-
return opened.outcome;
|
|
1178
|
-
return success({ ok: true, ...opened.value });
|
|
1179
|
-
}
|
|
1180
|
-
const [target, actionId] = parsed.positional;
|
|
1181
|
-
const requestId = parsed.requestId ?? `cxa_${(0, node_crypto_1.randomBytes)(16).toString('base64url')}`;
|
|
1182
|
-
let instanceId = target;
|
|
1183
|
-
if (!target.startsWith(APP_ID_PREFIX)) {
|
|
1184
|
-
const list = await loadLaunchers(context);
|
|
1185
|
-
if (!list.ok)
|
|
1186
|
-
return list.outcome;
|
|
1187
|
-
const launcher = findLauncher(list.value, target);
|
|
1188
|
-
if (!launcher)
|
|
1189
|
-
return failure(6, 'MCP_APP_NOT_FOUND', null);
|
|
1190
|
-
const opened = await openLauncher(context, launcher);
|
|
1191
|
-
if (!opened.ok)
|
|
1192
|
-
return opened.outcome;
|
|
1193
|
-
instanceId = opened.value.instanceId;
|
|
1194
|
-
}
|
|
1195
|
-
const action = await runAction(context, instanceId, actionId, requestId, parsed.input);
|
|
1196
|
-
if (!action.ok)
|
|
1197
|
-
return action.outcome;
|
|
1198
|
-
return success({ ok: true, ...action.value });
|
|
1199
|
-
}
|
|
1200
|
-
//# sourceMappingURL=engine.js.map
|
|
1
|
+
"use strict";var xe=exports&&exports.__createBinding||(Object.create?(function(e,t,n,r){if(r===void 0)r=n;var o=Object.getOwnPropertyDescriptor(t,n);if(!o||("get"in o?!t.__esModule:o.writable||o.configurable)){o={enumerable:true,get:function(){return t[n]}}}Object.defineProperty(e,r,o)}):(function(e,t,n,r){if(r===void 0)r=n;e[r]=t[n]}));var Ne=exports&&exports.__setModuleDefault||(Object.create?(function(e,t){Object.defineProperty(e,"default",{enumerable:true,value:t})}):function(e,t){e["default"]=t});var j=exports&&exports.__importStar||(function(){var e=function(t){e=Object.getOwnPropertyNames||function(n){var r=[];for(var o in n)if(Object.prototype.hasOwnProperty.call(n,o))r[r.length]=o;return r};return e(t)};return function(t){if(t&&t.__esModule)return t;var n={};if(t!=null){for(var r=e(t),o=0;o<r.length;o++)if(r[o]!=="default")xe(n,t,r[o])}Ne(n,t);return n}})();Object.defineProperty(exports,"__esModule",{value:true});exports.executeAppCommand=bt;const ie=require("node:crypto");const ae=require("node:child_process");const v=j(require("node:fs"));const je=j(require("node:http"));const $e=j(require("node:https"));const h=j(require("node:path"));const ue=require("node:url");const z=require("@crewx/shared/crewx-api-credential");const b=require("./receipt-wait.js");const C=require("./usage.js");const x=15e3;const Q=3e4;const Le=6e4;const ce=64*1024;const le="uic_";const Ue="/api/v1";const qe="/api/v1/app-dev/capabilities";const Me="/api/v1/app-dev/sessions";const Be=12;const Fe=13;const We=/^[A-Za-z0-9_-]{8,128}$/;const Ge=new Set(["127.0.0.1","[::1]","localhost"]);const Ve="http://localhost:8150";const Je="Do not work around this by clicking the screen or using another browser. Report that you could not use the App route.";const He="Do not retry. Report the failure.";const Xe="The server did not answer in time. Retry once; if it happens again, report it.";function l(e){return e!==null&&typeof e==="object"&&!Array.isArray(e)}function p(e,t,n,r){let o=r;if(!o&&(e===3||e===9||e===10||e===11)){o=Je}else if(!o&&e===4){o=He}return{exitCode:e,payload:{ok:false,exit:e,error:{code:t,status:n}},...o?{stderr:o}:{}}}function P(e){return{exitCode:0,payload:e}}function $(){return p(2,"USAGE",null)}function pe(e){const t=e[0];if(t!=="list"&&t!=="open"&&t!=="state"&&t!=="run"&&t!=="receipt"){return null}const n=[];const r=new Map;for(let a=1;a<e.length;a+=1){const u=e[a];if(!u.startsWith("--")){n.push(u);continue}const c=u.indexOf("=");const d=c>=0?u.slice(2,c):u.slice(2);const f=c>=0?u.slice(c+1):void 0;if(d!=="input"&&d!=="request-id"&&d!=="wait"||r.has(d))return null;const y=f??e[a+1];if(y===void 0)return null;if(f===void 0)a+=1;r.set(d,y)}if(t==="list"&&n.length!==0||t==="open"&&n.length!==1||t==="state"&&(n.length!==1||!n[0].startsWith(le))||t==="run"&&n.length!==2||t==="receipt"&&n.length!==1){return null}if(t!=="run"&&(r.has("input")||r.has("request-id")))return null;if(t!=="receipt"&&r.has("wait"))return null;let o={};if(r.has("input")){const a=r.get("input");try{const u=JSON.parse(a==="-"?v.readFileSync(0,"utf8"):a);if(!l(u))return null;o=u}catch{return null}}const i=r.get("request-id");if(i!==void 0&&!We.test(i))return null;const s=r.has("wait")?(0,b.parseWaitSeconds)(r.get("wait")):void 0;if(s===null)return null;return{command:t,positional:n,input:o,...i!==void 0?{requestId:i}:{},...s!==void 0?{waitSeconds:s}:{}}}function Y(e){if(!e)return null;let t;try{t=new ue.URL(e)}catch{return null}if(!Ge.has(t.hostname)||t.protocol!=="http:"&&t.protocol!=="https:"||t.username!==""||t.password!==""||t.search!==""||t.hash!==""||t.pathname!==""&&t.pathname!=="/"){return null}return t}function F(e){return encodeURIComponent(e)}function Ke(e,t){return["api","v1","ws",F(e),"threads",F(t)].join("/")}function L(e,...t){return`/${e.basePath}/${t.map(F).join("/")}`}function k(e,t){if(!l(e)||!l(e["error"]))return t;const n=e["error"]["code"];return typeof n==="string"&&n.length>0?n:t}function W(e){const{status:t,data:n}=e;if(t>=300&&t<400)return p(9,"ENDPOINT_REJECTED",t);if(t===401)return p(4,k(n,"UNAUTHORIZED"),t);if(t===403)return p(5,k(n,"FORBIDDEN"),t);if(t===404)return p(6,k(n,"NOT_FOUND"),t);if(t===409){const r=k(n,"CONFLICT");if(r==="APPROVAL_ALREADY_PENDING"){const o=l(n)&&l(n["error"])?n["error"]:void 0;const i=o&&l(o["details"])?o["details"]:void 0;const s=o?.["receiptId"]??i?.["receiptId"]??(l(n)?n["receiptId"]:void 0);return{exitCode:8,payload:{ok:false,exit:8,error:{code:r,status:t},...typeof s==="string"?{receiptId:s}:{}}}}return p(7,r,t)}return p(1,k(n,"ERROR"),t)}function U(e,t=true){const n=l(e)&&typeof e["code"]==="string"?e["code"]:"";const r=process.env["CODEX_SANDBOX_NETWORK_DISABLED"]==="1";const o=!r&&(n==="EPERM"||n==="EACCES");if(r||o)return p(11,"SANDBOX_BLOCKED",null);if(t&&n==="ETIMEDOUT"&&l(e)&&e["connectionEstablished"]===true){return p(14,"TIMED_OUT",null,Xe)}return p(10,"SERVER_UNREACHABLE",null)}function Z(e,t){if(e.exitCode!==14)return e;return{...e,payload:{...e.payload,requestId:t},stderr:["The request timed out and its outcome is unknown.",`Retry the same command once with --request-id ${t}; the server will return the same request's receipt without running the action twice.`,`If it times out again, report "server response delayed, outcome unknown" with requestId ${t}.`,"Do not report success or failure, click the screen, or use another route."].join("\n")}}function q(e,t,n,r,o,i=x){const s=e.protocol==="https:"?$e.request:je.request;const a=e.hostname==="[::1]"?"::1":e.hostname;const u=e.port?Number(e.port):void 0;const c=o===void 0?void 0:JSON.stringify(o);const d=s({protocol:e.protocol,hostname:a,...u!==void 0?{port:u}:{},method:n,path:r,headers:{Authorization:`Bearer ${t}`,Accept:"application/json",...c!==void 0?{"Content-Type":"application/json"}:{}}},m=>{const w=m.statusCode??0;if(w>=300&&w<400){m.destroy();clearTimeout(N);_({status:w,data:void 0,invalidJson:false,rawText:""});return}const A=[];m.setEncoding("utf8");m.on("data",O=>A.push(O));m.once("error",O=>I(O));m.once("end",()=>{clearTimeout(N);const O=A.join("");try{_({status:w,data:JSON.parse(O),invalidJson:false,rawText:O})}catch{_({status:w,data:void 0,invalidJson:true,rawText:O})}})});let f;let y=false;let _;let I;const S=new Promise((m,w)=>{_=m;I=w});const N=setTimeout(()=>{const m=new Error("Request timed out");m.code="ETIMEDOUT";m.connectionEstablished=y;d.destroy(m)},i);d.on("socket",m=>{const w=()=>{y=true};if(e.protocol==="https:"){const A=m;if(A.authorized)w();else A.once("secureConnect",w)}else{if(m.connecting)m.once("connect",w);else w()}m.once("error",A=>{f=A})});d.once("error",m=>{clearTimeout(N);I(f??m)});if(c!==void 0)d.write(c);d.end();return S}async function M(e,t,n,r,o=x){try{const i=await q(e.endpoint,e.ticket,t,n,r,o);if(i.status<200||i.status>=300){return{ok:false,outcome:W(i)}}if(i.invalidJson||!l(i.data)||i.data["success"]!==true){return{ok:false,outcome:p(1,k(i.data,"ERROR"),i.status)}}const s=i.data["data"];if(!l(s)){return{ok:false,outcome:p(1,"ERROR",i.status)}}return{ok:true,value:s,status:i.status}}catch(i){return{ok:false,outcome:U(i)}}}function de(e){return p(Be,"SERVER_TOO_OLD",e,"The running CrewX does not support App dev mode.")}function fe(e){if(e instanceof z.CrewxCredentialError){return p(e.exitCode,e.code,null,e.message)}return p(1,"ERROR",null)}function ze(){const e=process.env["CREWX_ENDPOINT"]?.trim()||Ve;return Y(e)}function G(e){try{return v.statSync(e).isDirectory()}catch{return false}}function ee(e){try{return v.statSync(e).isFile()}catch{return false}}function he(e=process.cwd()){let t=h.resolve(e);while(true){if(G(h.join(t,"apps"))||ee(h.join(t,"crewx.yaml"))){try{return v.realpathSync(t)}catch{return void 0}}const n=h.dirname(t);if(n===t)return void 0;t=n}}const ye="manifest.json";const me="ui-plugin.json";const Qe="dev.crewx";function we(e){try{const t=JSON.parse(v.readFileSync(e,"utf8"));return l(t)?t:void 0}catch{return void 0}}function te(e){const t=we(h.join(e,me));const n=h.join(e,ye);if(!ee(n))return t;const r=we(n);if(!r)return void 0;const o=r["_meta"];const i=l(o)?o[Qe]:void 0;const s={pluginId:r["name"],entry:l(i)?i["entry"]:void 0};if(t&&t["pluginId"]!==s["pluginId"])return void 0;return s}function Ee(e,t){const n=h.relative(e,t);return n!==""&&n!==".."&&!n.startsWith(`..${h.sep}`)&&!h.isAbsolute(n)}function Ye(e,t){if(!t||t==="."||t===".."||/[\\/]/.test(t)){return p(2,"USAGE",null,"App must be a folder name or pluginId.")}const n=h.join(e,"apps");const r=h.join(n,t);let o=G(r)?r:void 0;if(!o&&G(n)){try{for(const s of v.readdirSync(n,{withFileTypes:true})){if(!s.isDirectory())continue;const a=h.join(n,s.name);const u=te(a);if(u?.["pluginId"]===t){o=a;break}}}catch{}}let i=t;if(o){try{o=v.realpathSync(o)}catch{return p(6,"APP_INVALID",null)}if(!Ee(v.realpathSync(n),o)){return p(2,"USAGE",null,"App folder must be inside the workspace apps directory.")}const s=te(o);if(typeof s?.["pluginId"]==="string"&&s["pluginId"].length>0){i=s["pluginId"]}}else{o=r}return{appId:i,appDirectory:o}}function ve(e){if(e.status<200||e.status>=300){return{ok:false,outcome:W(e)}}if(e.invalidJson||!l(e.data)||e.data["success"]!==true||!l(e.data["data"])){return{ok:false,outcome:p(1,k(e.data,"ERROR"),e.status)}}return{ok:true,value:e.data,status:e.status,rawText:e.rawText}}async function Ze(e,t){try{const n=await q(e,t,"GET",qe);if(n.status===404||n.status>=300&&n.status<400||n.invalidJson){return{ok:false,outcome:de(n.status)}}if(n.status<200||n.status>=300){return{ok:false,outcome:W(n)}}if(!l(n.data)||n.data["success"]!==true||!l(n.data["data"])||n.data["data"]["appDev"]!==1){return{ok:false,outcome:de(n.status)}}return{ok:true,value:n.data,status:n.status,rawText:n.rawText}}catch(n){return{ok:false,outcome:U(n,false)}}}async function ne(e){const t=ze();if(!t)return{ok:false,outcome:p(9,"ENDPOINT_REJECTED",null)};let n;try{n=(0,z.resolveCrewxApiCredential)("app-dev").token}catch(c){return{ok:false,outcome:fe(c)}}const r=await Ze(t,n);if(!r.ok)return{ok:false,outcome:r.outcome};const o=he();if(!o){return{ok:false,outcome:p(2,"WORKSPACE_NOT_FOUND",null,"No workspace root with apps/ or crewx.yaml was found.")}}const i=Ye(o,e);if("exitCode"in i)return{ok:false,outcome:i};let s;try{s=await q(t,n,"POST",Me,{workspacePath:o,appId:i.appId})}catch(c){return{ok:false,outcome:U(c,false)}}const a=ve(s);if(!a.ok)return{ok:false,outcome:a.outcome};const u=a.value["data"];if(!l(u)||typeof u["workspaceSlug"]!=="string"||typeof u["appId"]!=="string"||typeof u["devPath"]!=="string"||!u["devPath"].startsWith("/ws/")){return{ok:false,outcome:p(1,"ERROR",s.status)}}if(!G(i.appDirectory)){return{ok:false,outcome:p(6,"APP_INVALID",s.status)}}return{ok:true,context:{endpoint:t,token:n,workspacePath:o,appId:u["appId"],appDirectory:i.appDirectory,devPath:u["devPath"]}}}function V(e,...t){return`${Ue}${e.devPath}${t.length>0?`/${t.map(F).join("/")}`:""}`}async function J(e,t,n,r,o=x){try{const i=await q(e.endpoint,e.token,t,n,r,o);return ve(i)}catch(i){return{ok:false,outcome:U(i,false)}}}function et(e){const t=e["appId"]??e["id"];if(typeof t==="string"&&t.length>0)return t;const n=e["definition"];if(l(n)&&typeof n["id"]==="string")return n["id"];return void 0}function tt(e){const t=e["definitionHash"];if(typeof t==="string"&&t.length>0)return t;const n=e["definition"];if(l(n)&&typeof n["hash"]==="string"){return n["hash"]}return void 0}function nt(e){if(!l(e))return void 0;const t=et(e);const n=tt(e);if(!t||!n)return void 0;const r=e["agentActions"];const o=ot(e["agentAccess"]);return{appId:t,name:typeof e["name"]==="string"?e["name"]:"",description:typeof e["description"]==="string"?e["description"]:"",definitionHash:n,agentActions:Array.isArray(r)?r:[],...o===void 0?{}:{agentAccess:o}}}const rt=new Set(["allowed","approval_required","denied"]);function ot(e){if(!l(e))return void 0;const t=new Map;for(const[n,r]of Object.entries(e)){if(rt.has(r))t.set(n,r)}return t.size>0?t:void 0}async function H(e,t=x){const n=await M(e,"GET",L(e,"ui-instances","apps"),void 0,t);if(!n.ok)return n;const r=n.value["apps"];if(!Array.isArray(r)){return{ok:false,outcome:p(1,"ERROR",n.status)}}const o=r.map(nt);if(o.some(i=>i===void 0)){return{ok:false,outcome:p(1,"ERROR",n.status)}}return{ok:true,value:o,status:n.status}}function re(e,t){return e.find(n=>n.appId===t)}async function ge(e,t,n=x){const r=await M(e,"POST",L(e,"ui-instances","open"),{appId:t.appId,expectedDefinitionHash:t.definitionHash},n);if(!r.ok)return r;const o=l(r.value["instance"])?r.value["instance"]:r.value;if(!l(o)){return{ok:false,outcome:p(1,"ERROR",r.status)}}const i=o["instanceId"]??o["id"];const s=o["revision"];if(typeof i!=="string"||typeof s!=="number"){return{ok:false,outcome:p(1,"ERROR",r.status)}}const a=l(r.value["ai"])?r.value["ai"]:l(o["ai"])?o["ai"]:void 0;return{ok:true,status:r.status,value:{appId:t.appId,definitionHash:t.definitionHash,instanceId:i,revision:s,state:a&&Object.prototype.hasOwnProperty.call(a,"state")?a["state"]:null}}}async function st(e,t){const n=await M(e,"GET",L(e,"ui-instances",t));if(!n.ok)return n;const r=n.value;const o=r["instanceId"];const i=r["revision"];if(typeof o!=="string"||typeof i!=="number"){return{ok:false,outcome:p(1,"ERROR",n.status)}}const s=l(r["ai"])?r["ai"]:void 0;const a=u=>{if(Object.prototype.hasOwnProperty.call(r,u))return{[u]:r[u]};if(s&&Object.prototype.hasOwnProperty.call(s,u))return{[u]:s[u]};return{}};return{ok:true,status:n.status,value:{instanceId:o,revision:i,state:s&&Object.prototype.hasOwnProperty.call(s,"state")?s["state"]:null,buttons:s&&Object.prototype.hasOwnProperty.call(s,"buttons")?s["buttons"]:null,...a("agentAccess"),...a("modelContext"),...a("pendingApproval")}}}async function it(e,t,n,r,o,i=x){const s=await M(e,"POST",L(e,"ui-instances",t,"actions"),{actionId:n,requestId:r,input:o},i);if(!s.ok)return s;const a=s.value["receipt"];if(!l(a)){return{ok:false,outcome:p(1,"ERROR",s.status)}}const u=a["receiptId"]??a["id"];const c=s.value["approval"];if(s.status===202&&l(c)){const _=c["approvalId"];const I=c["expiresAt"];const S=typeof a["requestId"]==="string"?a["requestId"]:typeof c["requestId"]==="string"?c["requestId"]:r;if(typeof u!=="string"||typeof _!=="string"||typeof I!=="string"&&typeof I!=="number"){return{ok:false,outcome:p(1,"ERROR",s.status)}}return{ok:false,outcome:{exitCode:8,payload:{ok:false,exit:8,error:{code:"APPROVAL_PENDING",status:202},receiptId:u,requestId:S,approvalId:_,expiresAt:I}}}}const d=a["status"]??(s.status===202?"requested":"settled");const f=s.value["resolve"];const y=l(f)&&l(f["ai"])?f["ai"]:void 0;return{ok:true,status:s.status,value:{receiptId:typeof u==="string"?u:"",requestId:typeof a["requestId"]==="string"?a["requestId"]:r,instanceId:t,actionId:typeof a["actionId"]==="string"?a["actionId"]:n,status:d,outcome:Object.prototype.hasOwnProperty.call(a,"outcome")?a["outcome"]:null,...typeof a["errorCode"]==="string"?{errorCode:a["errorCode"]}:{},state:y&&Object.prototype.hasOwnProperty.call(y,"state")?y["state"]:null,...Object.prototype.hasOwnProperty.call(s.value,"result")?{result:s.value["result"]}:{},...Object.prototype.hasOwnProperty.call(s.value,"resultExpired")?{resultExpired:s.value["resultExpired"]}:{}}}}function at(e){const t=e.payload["error"];return e.exitCode===14||l(t)&&t["status"]===429}async function Ae(e,t){const n=await M(e,"GET",L(e,"ui-action-receipts",t));if(!n.ok){return{state:"failed",retryable:at(n.outcome),value:n.outcome}}const r=n.value["receipt"];if(!l(r))return{state:"failed",retryable:false,value:p(1,"ERROR",n.status)};return{state:r["status"]==="requested"?"pending":"settled",value:P({ok:true,receipt:r,outcome:Object.prototype.hasOwnProperty.call(r,"outcome")?r["outcome"]:null,...typeof r["errorCode"]==="string"?{errorCode:r["errorCode"]}:{},...Object.prototype.hasOwnProperty.call(n.value,"result")?{result:n.value["result"]}:{},...Object.prototype.hasOwnProperty.call(n.value,"resultExpired")?{resultExpired:n.value["resultExpired"]}:{}})}}function ut(e,t){return{exitCode:124,payload:{ok:false,exit:124,error:{code:"WAIT_TIMEOUT",status:null},receiptId:e},suppressOutput:true,stderr:`\u2192 Receipt ${e} is still pending after ${t}s. Do not end your turn: run crewx app receipt ${e} --wait=${(0,b.nextWaitSeconds)(t)} again.`}}function Ie(){const e=process.env["CREWX_TASK_TICKET"]??"";const t=process.env["CREWX_WORKSPACE_SLUG"]??"";const n=process.env["CREWX_THREAD_ID"]??"";if(!e.startsWith("crewxtk_")||!t||!n){return p(3,"NO_TICKET",null)}const r=Y(process.env["CREWX_ENDPOINT"]);if(!r)return p(9,"ENDPOINT_REJECTED",null);return{ticket:e,workspaceSlug:t,threadId:n,endpoint:r,basePath:Ke(t,n)}}function _e(e){return"exitCode"in e}function Oe(e){return String(e).padStart(2,"0")}function ct(e){const t=typeof e==="number"?new Date(e):typeof e==="string"?new Date(/^\d+$/.test(e)?Number(e):e):void 0;if(!t||Number.isNaN(t.getTime()))return void 0;return`${Oe(t.getHours())}:${Oe(t.getMinutes())}`}function lt(e){if(e.exitCode!==8)return e;const t=ct(e.payload["expiresAt"]);const n=e.payload["receiptId"];const r=[`\u2192 Waiting for the user's approval in the App window${t?` (expires ${t})`:""}. Do not end your turn: ending it cancels the request.`,...typeof n==="string"&&n.length>0?[`\u2192 Wait: crewx app receipt ${n} --wait=${b.RECEIPT_WAIT_DEFAULT_SECONDS} (repeat on exit 124 until it settles)`]:[],"\u2192 Report success only if it settles as succeeded."];return{...e,stderr:[e.stderr,...r].filter(Boolean).join("\n")}}const pt=256*1024;function dt(e){try{const t=v.realpathSync(e);const n=v.realpathSync(h.join(t,"SKILL.md"));if(!Ee(t,n)||!ee(n))return void 0;const r=v.openSync(n,"r");try{const o=new Uint8Array(pt);const i=v.readSync(r,o,0,o.length,0);return(0,C.manualBody)(Buffer.from(o.buffer,0,i).toString("utf8"))}finally{v.closeSync(r)}}catch{return void 0}}async function ft(e){const t=he();if(!t)return void 0;try{const n=await Promise.resolve().then(()=>j(require("@crewx/sdk")));const r=await n.discoverWorkspaceApps(t);const o=r.apps.find(i=>i.appId===e)??r.apps.find(i=>i.pluginId===e);if(!o)return void 0;return{candidate:o,directory:h.join(t,o.relativePath),resolveAccess:n.resolveAgentAccess}}catch{return void 0}}async function ht(e){if(e.length!==2||e[1].startsWith("--"))return $();const t=e[1];const n=Ie();if(_e(n))return n;const r=await H(n);if(!r.ok)return r.outcome;const o=re(r.value,t);if(!o)return p(6,"MCP_APP_NOT_FOUND",null);const i=o.agentActions.filter(u=>typeof u==="string");const s=await ft(t);const a=s?(0,C.renderUsage)(dt(s.directory),(0,C.renderActionsSection)(t,s.candidate,(0,C.buildActionRows)(s.candidate,new Set(i),u=>s.resolveAccess(u),o.agentAccess)),s.candidate.manifest.csp?.connectDomains):(0,C.renderUsage)(void 0,(0,C.renderServerOnlyActions)(t,i));return{exitCode:0,payload:{ok:true},stdout:a}}function Re(e,t){if(e[0]!==t)return null;const n=[];let r=false;for(const o of e.slice(1)){if(o===(t==="dev"?"--no-open":"--json")){if(r)return null;r=true}else if(o.startsWith("--")){return null}else{n.push(o)}}if(n.length!==1)return null;return{appSelector:n[0],flag:r}}function Se(e){return`${JSON.stringify(e.payload)}
|
|
2
|
+
`}function yt(e){const t=e["detail"];if(e["rule"]!=="manifest"||!l(t))return"";const n=typeof t["path"]==="string"?t["path"]:"";const r=typeof t["rule"]==="string"?t["rule"]:"";if(!r)return"";return` ${n?`${n} `:""}(${r})`}async function mt(e){const t=Re(e,"check");if(!t)return $();const n=await ne(t.appSelector);if(!n.ok)return n.outcome;const r=await J(n.context,"GET",V(n.context,"check"));if(!r.ok)return r.outcome;const o=r.value["data"];if(!l(o))return p(1,"ERROR",r.status);const i=l(o["summary"])?o["summary"]:{};const s=Array.isArray(o["items"])?o["items"]:[];const a=typeof i["errors"]==="number"?i["errors"]:s.filter(f=>l(f)&&f["severity"]==="error").length;const u=typeof i["warnings"]==="number"?i["warnings"]:s.filter(f=>l(f)&&f["severity"]==="warning").length;const c=a===0?0:Fe;if(t.flag){return{exitCode:c,payload:r.value,stdout:`${r.rawText??Se({exitCode:c,payload:r.value})}`+(r.rawText?.endsWith("\n")?"":"\n"),suppressOutput:true}}const d=s.flatMap(f=>{if(!l(f))return[];const y=f["severity"];const _=y==="error"?"\u2715":y==="ok"?"\u2713":"!";const I=typeof f["where"]==="string"?f["where"]:"";const S=typeof f["code"]==="string"?f["code"]:"";return[`${_} ${I} ${S}${yt(f)}`]});d.push(`Summary: ${a} errors, ${u} warnings`);return{exitCode:c,payload:{},stdout:`${d.join("\n")}
|
|
3
|
+
`,suppressOutput:true}}async function wt(e){let t;try{t=(0,z.resolveCrewxApiCredential)("pairing").token}catch(r){return fe(r)}let n;try{n=await q(e.endpoint,t,"POST","/api/v1/auth/local/code")}catch(r){return U(r,false)}if(n.status<200||n.status>=300){return W(n)}if(n.invalidJson||!l(n.data)||typeof n.data["url"]!=="string"){return p(1,"PAIRING_CODE_INVALID",n.status)}try{const r=new ue.URL(n.data["url"]);const o=Y(r.origin);const i=r.hash.match(/^#pair=(.+)$/);if(!o||!i)return p(1,"PAIRING_CODE_INVALID",n.status);return`${o.origin}${e.devPath}#pair=${encodeURIComponent(i[1])}`}catch{return p(1,"PAIRING_CODE_INVALID",n.status)}}async function Et(e){const t=process.platform==="darwin"?"open":process.platform==="win32"?"explorer.exe":"xdg-open";const n=(0,ae.spawn)(t,[e],{detached:true,stdio:"ignore"});await new Promise((r,o)=>{n.once("error",o);n.once("spawn",()=>{n.unref();r()})})}async function vt(e){const t=pe(["run",...e.slice(2)]);if(!t||t.command!=="run")return $();const[n,r]=t.positional;const o=await ne(n);if(!o.ok)return o.outcome;const i=await J(o.context,"GET",V(o.context,"state"));if(!i.ok)return i.outcome;const s=i.value["data"];if(!l(s)||typeof s["revision"]!=="number"){return p(1,"APP_DEV_RESPONSE_INVALID",i.status)}const a=t.requestId??`cxa_${(0,ie.randomBytes)(16).toString("base64url")}`;const u=await J(o.context,"POST",V(o.context,"actions"),{actionId:r,requestId:a,input:t.input,expectedRevision:s["revision"]},Le);if(!u.ok)return u.outcome;const c=u.value["data"];const d=l(c)&&l(c["receipt"])?c["receipt"]:void 0;return{exitCode:d?.["outcome"]==="succeeded"?0:1,payload:u.value,stdout:`${u.rawText??Se({exitCode:0,payload:u.value})}`+(u.rawText?.endsWith("\n")?"":"\n"),suppressOutput:true}}function gt(e){try{const t=JSON.parse(v.readFileSync(h.join(e,"package.json"),"utf8"));if(!l(t)||!l(t["scripts"]))return false;const n=t["scripts"]["build"];return typeof n==="string"&&n.trim().length>0}catch{return false}}function De(e){const t=new Set([h.normalize(ye),h.normalize(me)]);const n=te(e);const r=n?.["entry"];if(typeof r==="string"&&r.length>0&&!h.isAbsolute(r)){const o=h.normalize(r);if(o!==".."&&!o.startsWith(`..${h.sep}`))t.add(o)}return t}function At(e,t){try{const n=v.statSync(h.join(e,t));return`${n.size}:${n.mtimeMs}:${n.ino}`}catch{return null}}function Te(e,t){return new Map([...t].map(n=>[n,At(e,n)]))}function It(e,t){const n=Date.now();let r="";let o=false;const i=s=>{const a=Buffer.from(r+(Buffer.isBuffer(s)?s.toString("utf8"):s),"utf8");r=a.length>ce?a.subarray(a.length-ce).toString("utf8"):a.toString("utf8")};return new Promise(s=>{const a=(0,ae.spawn)("npm",["run","build"],{cwd:e,env:process.env,stdio:["ignore","pipe","pipe"]});t(a);a.stdout?.on("data",i);a.stderr?.on("data",i);const u=c=>{if(o)return;o=true;t(void 0);s({ok:c,ms:Math.max(0,Date.now()-n),output:r})};a.once("error",c=>{i(`${c.message}
|
|
4
|
+
`);u(false)});a.once("close",c=>u(c===0))})}function _t(e){const t=e.payload["error"];return l(t)&&typeof t["code"]==="string"?t["code"]:"ERROR"}async function Ot(e,t){const n=await J(e,"POST",V(e,"reload"),t);if(!n.ok)return{errorCode:_t(n.outcome)};const r=n.value["data"];return l(r)&&typeof r["generation"]==="number"?{generation:r["generation"]}:{errorCode:"APP_DEV_RESPONSE_INVALID"}}async function Rt(e,t){const n=process.ppid;let r;try{r=v.watch(e.appDirectory,{recursive:true},(E,g)=>{O(g)})}catch(E){return p(1,"WATCH_FAILED",null,E instanceof Error?E.message:"Unable to watch the App folder.")}let o=false;let i=false;let s=false;let a=false;let u="";let c="";let d;let f;let y;let _={exitCode:0,payload:{},suppressOutput:true};const I=new Map;let S;const N=new Promise(E=>{S=E});const m=E=>{process.stdout.write(`${E}
|
|
5
|
+
`)};const w=()=>{if(o)return;o=true;if(d)clearTimeout(d);if(f)clearInterval(f);process.removeListener("SIGINT",A);process.removeListener("SIGTERM",A);S(_)};const A=()=>{if(i)return;i=true;r.close();if(d)clearTimeout(d);if(y&&y.exitCode===null)y.kill("SIGTERM");if(!s)w()};const O=E=>{if(i||o)return;const g=E===null?"":h.normalize(E.toString());const D=g.split(h.sep);if(D.some(T=>T==="node_modules"||T===".crewx"||T==="dist"))return;const K=t?De(e.appDirectory):new Set;if(y&&K.has(g))return;const R=I.get(g);if(R!==void 0){if(Date.now()<=R)return;I.delete(g)}if(s){a=true;u=g||"App files";return}c=g||"App files";if(d)clearTimeout(d);d=setTimeout(()=>{d=void 0;const T=c||"App files";c="";void X(T,false)},200)};const se=async E=>{const g=await Ot(e,E);return g.generation===void 0?`failed ${g.errorCode??"ERROR"}`:String(g.generation)};const X=async(E,g)=>{if(i||o)return;s=true;if(t){const D=De(e.appDirectory);const K=Te(e.appDirectory,D);const R=await It(e.appDirectory,B=>{y=B});const T=Te(e.appDirectory,D);for(const[B,Ce]of T){if(K.get(B)!==Ce){I.set(B,Date.now()+1e3)}}if(i){s=false;w();return}const ke={reason:"build",build:{ok:R.ok,ms:R.ms,...!R.ok?{output:R.output}:{}}};const be=await se(ke);m(`${g?"initial":E} -> build ${R.ok?"ok":"failed"} ${R.ms}ms -> reload ${be}`)}else if(!g){const D=await se({reason:"manual"});m(`${E} -> reload ${D}`)}s=false;if(i){w();return}if(a){const D=u||"App files";a=false;u="";void X(D,false);return}if(i)w()};r.once("error",E=>{_=p(1,"WATCH_FAILED",null,E.message);A()});process.once("SIGINT",A);process.once("SIGTERM",A);f=setInterval(()=>{if(process.ppid!==n)A()},1e3);f.unref();if(t)void X("initial",true);return N}async function St(e){const t=Re(e,"dev");if(!t)return $();const n=await ne(t.appSelector);if(!n.ok)return n.outcome;const r=`${n.context.endpoint.origin}${n.context.devPath}`;process.stdout.write(`${r}
|
|
6
|
+
`);if(!t.flag){const o=await wt(n.context);if(typeof o!=="string")return o;try{await Et(o)}catch(i){return p(1,"BROWSER_OPEN_FAILED",null,i instanceof Error?i.message:"Could not open the App dev tab.")}}return Rt(n.context,gt(n.context.appDirectory))}const Pe="usage: crewx app build [<appDir>] [--check | --watch]";const Dt=["crewx app <command>",""," list List Apps available to this task"," open <appId> Open an App in this conversation"," state <instanceId> Read AI-visible state, buttons, and agent access"," run <instanceId|appId> <actionId> [--input <JSON>|-] [--request-id <id>] Run one available action"," receipt <receiptId> [--wait=<seconds>] Read a safe action receipt"," build [<appDir>] [--check | --watch] Build a @Tool App: server.ts -> dist/ and the actions (manifest.json, or dist/tools.json)"," dev <app> [--no-open] Start a foreground App dev session"," check <app> [--json] Run the App dev checks"," run --dev <app> <actionId> [--input <JSON>|-] Run an App action through the dev checks"," usage [<appId>] Show the guide, or one App's manual and action table",""].join("\n");const Tt=[Pe,""," Builds <appDir> (default: the current directory) from its server.ts: dist/server.mjs, dist/entry.mjs,",' and the actions of manifest.json. With no "bin" and no crewx.app.command in package.json the App is'," server.ts only (tool mode): dist/server.mjs, dist/main.mjs and dist/tools.json, and manifest.json"," carries no actions. --check writes nothing and exits 1 when a generated file is stale."," --watch rebuilds on every change until Ctrl-C.",""].join("\n");function Pt(){const e=[h.join(__dirname,"..","..","build-app","load.cjs"),h.join(__dirname,"..","build-app","load.cjs")];const t=e.find(n=>v.existsSync(n));if(!t)return Promise.reject(new Error("the App build is not part of this install"));return require(t).loadAppBuilder()}function oe(e){return e instanceof Error?e.message:String(e)}async function kt(e){if(e.slice(1).some(s=>s==="--help"||s==="-h")){return{exitCode:0,payload:{ok:true},stdout:Tt}}let t;try{t=await Pt()}catch(s){return p(1,"BUILD_UNAVAILABLE",null,oe(s))}let n;try{n=t.parseArguments(e.slice(1))}catch(s){return p(2,"USAGE",null,`${oe(s)} \u2192 ${Pe}`)}const{appDirectory:r}=n;const o=h.relative(process.cwd(),r);const i=o===""?"crewx app build":`crewx app build ${o}`;if(n.watch){const s=new AbortController;const a=()=>s.abort();process.once("SIGINT",a);process.once("SIGTERM",a);try{await t.watchApp(r,u=>process.stdout.write(`${u}
|
|
7
|
+
`),{signal:s.signal})}finally{process.removeListener("SIGINT",a);process.removeListener("SIGTERM",a)}return{exitCode:0,payload:{},suppressOutput:true}}try{const s=await t.buildApp({appDirectory:r,check:n.check});if(n.check&&s.staleFiles.length>0){return{...p(1,"BUILD_STALE",null,`Stale generated files: ${s.staleFiles.join(", ")} \u2192 Fix: ${i}`),payload:{ok:false,exit:1,error:{code:"BUILD_STALE",status:null},appDir:r,stale:s.staleFiles}}}if(n.check){return P({ok:true,appDir:r,check:true,inSync:true,files:s.files,actions:s.actions})}const a=h.basename(r);const u=h.basename(h.dirname(r))==="apps";return{...P({ok:true,appDir:r,files:s.files,actions:s.actions}),stderr:u?`\u2192 Next: crewx app check ${a}`:`\u2192 Next: put ${a} in the workspace apps/ folder, then crewx app check ${a}`}}catch(s){return p(1,"BUILD_FAILED",null,oe(s))}}async function bt(e,{clock:t=b.realClock}={}){if(e.length===1&&(e[0]==="--help"||e[0]==="-h")){return{exitCode:0,payload:{ok:true},stdout:Dt}}if(e[0]==="build")return kt(e);if(e[0]==="dev")return St(e);if(e[0]==="check")return mt(e);if(e[0]==="run"&&e[1]==="--dev")return vt(e);if(e[0]==="usage")return ht(e);const n=pe(e);if(!n)return $();const r=Ie();if(_e(r))return r;if(n.command==="list"){const c=await H(r);if(!c.ok)return c.outcome;return P({ok:true,apps:c.value})}if(n.command==="receipt"){const c=n.positional[0];const d=(0,b.clampWaitSeconds)(n.waitSeconds??0);if(d===0)return(await Ae(r,c)).value;const f=await(0,b.pollUntilSettled)(()=>Ae(r,c),d,t);if(f.state!=="pending")return f.value;return ut(c,d)}if(n.command==="state"){const c=await st(r,n.positional[0]);if(!c.ok)return c.outcome;return P({ok:true,...c.value})}if(n.command==="open"){const c=n.positional[0];const d=await H(r);if(!d.ok)return d.outcome;const f=re(d.value,c);if(!f)return p(6,"MCP_APP_NOT_FOUND",null);const y=await ge(r,f);if(!y.ok)return y.outcome;return P({ok:true,...y.value})}const[o,i]=n.positional;const s=n.requestId??`cxa_${(0,ie.randomBytes)(16).toString("base64url")}`;let a=o;if(!o.startsWith(le)){const c=await H(r,Q);if(!c.ok)return Z(c.outcome,s);const d=re(c.value,o);if(!d)return p(6,"MCP_APP_NOT_FOUND",null);const f=await ge(r,d,Q);if(!f.ok)return Z(f.outcome,s);a=f.value.instanceId}const u=await it(r,a,i,s,n.input,Q);if(!u.ok)return lt(Z(u.outcome,s));return{...P({ok:true,...u.value}),stderr:`\u2192 Check: crewx app state ${u.value.instanceId}`}}
|