@crewx/app 0.1.0-rc.17 → 0.1.0-rc.19
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 +17 -4
- package/build-app/index.d.mts +54 -0
- package/build-app/index.mjs +448 -0
- package/build-app/load.cjs +4 -0
- package/build-app/manifest.mjs +63 -0
- package/dist/src/engine.d.ts +4 -1
- package/dist/src/engine.d.ts.map +1 -1
- package/dist/src/engine.js +306 -24
- package/dist/src/engine.js.map +1 -1
- package/dist/src/receipt-wait.d.ts +24 -0
- package/dist/src/receipt-wait.d.ts.map +1 -0
- package/dist/src/receipt-wait.js +41 -0
- package/dist/src/receipt-wait.js.map +1 -0
- package/dist/src/usage.d.ts +25 -0
- package/dist/src/usage.d.ts.map +1 -0
- package/dist/src/usage.js +182 -0
- package/dist/src/usage.js.map +1 -0
- package/package.json +6 -1
package/dist/src/engine.js
CHANGED
|
@@ -42,6 +42,8 @@ const https = __importStar(require("node:https"));
|
|
|
42
42
|
const path = __importStar(require("node:path"));
|
|
43
43
|
const node_url_1 = require("node:url");
|
|
44
44
|
const crewx_api_credential_1 = require("@crewx/shared/crewx-api-credential");
|
|
45
|
+
const receipt_wait_js_1 = require("./receipt-wait.js");
|
|
46
|
+
const usage_js_1 = require("./usage.js");
|
|
45
47
|
const REQUEST_TIMEOUT_MS = 15000;
|
|
46
48
|
const RUN_REQUEST_TIMEOUT_MS = 30000;
|
|
47
49
|
const DEV_ACTION_TIMEOUT_MS = 60000;
|
|
@@ -97,7 +99,7 @@ function parseCommand(args) {
|
|
|
97
99
|
const separator = arg.indexOf('=');
|
|
98
100
|
const key = separator >= 0 ? arg.slice(2, separator) : arg.slice(2);
|
|
99
101
|
const inlineValue = separator >= 0 ? arg.slice(separator + 1) : undefined;
|
|
100
|
-
if ((key !== 'input' && key !== 'request-id') || options.has(key))
|
|
102
|
+
if ((key !== 'input' && key !== 'request-id' && key !== 'wait') || options.has(key))
|
|
101
103
|
return null;
|
|
102
104
|
const value = inlineValue ?? args[index + 1];
|
|
103
105
|
if (value === undefined)
|
|
@@ -115,6 +117,8 @@ function parseCommand(args) {
|
|
|
115
117
|
}
|
|
116
118
|
if (command !== 'run' && (options.has('input') || options.has('request-id')))
|
|
117
119
|
return null;
|
|
120
|
+
if (command !== 'receipt' && options.has('wait'))
|
|
121
|
+
return null;
|
|
118
122
|
let input = {};
|
|
119
123
|
if (options.has('input')) {
|
|
120
124
|
const raw = options.get('input');
|
|
@@ -131,11 +135,15 @@ function parseCommand(args) {
|
|
|
131
135
|
const requestId = options.get('request-id');
|
|
132
136
|
if (requestId !== undefined && !REQUEST_ID_PATTERN.test(requestId))
|
|
133
137
|
return null;
|
|
138
|
+
const waitSeconds = options.has('wait') ? (0, receipt_wait_js_1.parseWaitSeconds)(options.get('wait')) : undefined;
|
|
139
|
+
if (waitSeconds === null)
|
|
140
|
+
return null;
|
|
134
141
|
return {
|
|
135
142
|
command,
|
|
136
143
|
positional,
|
|
137
144
|
input,
|
|
138
145
|
...(requestId !== undefined ? { requestId } : {}),
|
|
146
|
+
...(waitSeconds !== undefined ? { waitSeconds } : {}),
|
|
139
147
|
};
|
|
140
148
|
}
|
|
141
149
|
function getEndpoint(raw) {
|
|
@@ -388,15 +396,36 @@ function resolveWorkspaceRoot(startDirectory = process.cwd()) {
|
|
|
388
396
|
current = parent;
|
|
389
397
|
}
|
|
390
398
|
}
|
|
391
|
-
|
|
399
|
+
const APP_MANIFEST_FILE = 'manifest.json';
|
|
400
|
+
const LEGACY_APP_MANIFEST_FILE = 'ui-plugin.json';
|
|
401
|
+
const CREWX_META_NAMESPACE = 'dev.crewx';
|
|
402
|
+
function readJsonObject(filePath) {
|
|
392
403
|
try {
|
|
393
|
-
const parsed = JSON.parse(fs.readFileSync(
|
|
404
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
394
405
|
return isRecord(parsed) ? parsed : undefined;
|
|
395
406
|
}
|
|
396
407
|
catch {
|
|
397
408
|
return undefined;
|
|
398
409
|
}
|
|
399
410
|
}
|
|
411
|
+
function readAppManifest(directory) {
|
|
412
|
+
const legacy = readJsonObject(path.join(directory, LEGACY_APP_MANIFEST_FILE));
|
|
413
|
+
const modernPath = path.join(directory, APP_MANIFEST_FILE);
|
|
414
|
+
if (!isFile(modernPath))
|
|
415
|
+
return legacy;
|
|
416
|
+
const modern = readJsonObject(modernPath);
|
|
417
|
+
if (!modern)
|
|
418
|
+
return undefined;
|
|
419
|
+
const meta = modern['_meta'];
|
|
420
|
+
const crewx = isRecord(meta) ? meta[CREWX_META_NAMESPACE] : undefined;
|
|
421
|
+
const view = {
|
|
422
|
+
pluginId: modern['name'],
|
|
423
|
+
entry: isRecord(crewx) ? crewx['entry'] : undefined,
|
|
424
|
+
};
|
|
425
|
+
if (legacy && legacy['pluginId'] !== view['pluginId'])
|
|
426
|
+
return undefined;
|
|
427
|
+
return view;
|
|
428
|
+
}
|
|
400
429
|
function isPathInside(parentDirectory, candidate) {
|
|
401
430
|
const relative = path.relative(parentDirectory, candidate);
|
|
402
431
|
return relative !== '' && relative !== '..' && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
|
|
@@ -584,14 +613,27 @@ function projectLauncher(value) {
|
|
|
584
613
|
if (!appId || !definitionHash)
|
|
585
614
|
return undefined;
|
|
586
615
|
const actions = value['agentActions'];
|
|
616
|
+
const agentAccess = projectAgentAccess(value['agentAccess']);
|
|
587
617
|
return {
|
|
588
618
|
appId,
|
|
589
619
|
name: typeof value['name'] === 'string' ? value['name'] : '',
|
|
590
620
|
description: typeof value['description'] === 'string' ? value['description'] : '',
|
|
591
621
|
definitionHash,
|
|
592
622
|
agentActions: Array.isArray(actions) ? actions : [],
|
|
623
|
+
...(agentAccess === undefined ? {} : { agentAccess }),
|
|
593
624
|
};
|
|
594
625
|
}
|
|
626
|
+
const AGENT_ACCESS_LEVELS = new Set(['allowed', 'approval_required', 'denied']);
|
|
627
|
+
function projectAgentAccess(value) {
|
|
628
|
+
if (!isRecord(value))
|
|
629
|
+
return undefined;
|
|
630
|
+
const levels = new Map();
|
|
631
|
+
for (const [actionId, level] of Object.entries(value)) {
|
|
632
|
+
if (AGENT_ACCESS_LEVELS.has(level))
|
|
633
|
+
levels.set(actionId, level);
|
|
634
|
+
}
|
|
635
|
+
return levels.size > 0 ? levels : undefined;
|
|
636
|
+
}
|
|
595
637
|
async function loadLaunchers(context, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
596
638
|
const response = await performRequest(context, 'GET', pathFor(context, 'ui-instances', 'apps'), undefined, timeoutMs);
|
|
597
639
|
if (!response.ok)
|
|
@@ -732,6 +774,41 @@ async function runAction(context, instanceId, actionId, requestId, input, timeou
|
|
|
732
774
|
},
|
|
733
775
|
};
|
|
734
776
|
}
|
|
777
|
+
function isRetryablePollFailure(outcome) {
|
|
778
|
+
const error = outcome.payload['error'];
|
|
779
|
+
return outcome.exitCode === 14
|
|
780
|
+
|| (isRecord(error) && error['status'] === 429);
|
|
781
|
+
}
|
|
782
|
+
async function readReceipt(context, receiptId) {
|
|
783
|
+
const response = await performRequest(context, 'GET', pathFor(context, 'ui-action-receipts', receiptId));
|
|
784
|
+
if (!response.ok) {
|
|
785
|
+
return { state: 'failed', retryable: isRetryablePollFailure(response.outcome), value: response.outcome };
|
|
786
|
+
}
|
|
787
|
+
const receipt = response.value['receipt'];
|
|
788
|
+
if (!isRecord(receipt))
|
|
789
|
+
return { state: 'failed', retryable: false, value: failure(1, 'ERROR', response.status) };
|
|
790
|
+
return {
|
|
791
|
+
state: receipt['status'] === 'requested' ? 'pending' : 'settled',
|
|
792
|
+
value: success({
|
|
793
|
+
ok: true,
|
|
794
|
+
receipt,
|
|
795
|
+
outcome: Object.prototype.hasOwnProperty.call(receipt, 'outcome') ? receipt['outcome'] : null,
|
|
796
|
+
...(typeof receipt['errorCode'] === 'string' ? { errorCode: receipt['errorCode'] } : {}),
|
|
797
|
+
...(Object.prototype.hasOwnProperty.call(response.value, 'result') ? { result: response.value['result'] } : {}),
|
|
798
|
+
...(Object.prototype.hasOwnProperty.call(response.value, 'resultExpired')
|
|
799
|
+
? { resultExpired: response.value['resultExpired'] }
|
|
800
|
+
: {}),
|
|
801
|
+
}),
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
function receiptStillPending(receiptId, waitedSeconds) {
|
|
805
|
+
return {
|
|
806
|
+
exitCode: 124,
|
|
807
|
+
payload: { ok: false, exit: 124, error: { code: 'WAIT_TIMEOUT', status: null }, receiptId },
|
|
808
|
+
suppressOutput: true,
|
|
809
|
+
stderr: `→ Receipt ${receiptId} is still pending after ${waitedSeconds}s. Do not end your turn: run crewx app receipt ${receiptId} --wait=${(0, receipt_wait_js_1.nextWaitSeconds)(waitedSeconds)} again.`,
|
|
810
|
+
};
|
|
811
|
+
}
|
|
735
812
|
function contextFromEnvironment() {
|
|
736
813
|
const ticket = process.env['CREWX_TASK_TICKET'] ?? '';
|
|
737
814
|
const workspaceSlug = process.env['CREWX_WORKSPACE_SLUG'] ?? '';
|
|
@@ -753,6 +830,95 @@ function contextFromEnvironment() {
|
|
|
753
830
|
function isAppCommandOutcome(value) {
|
|
754
831
|
return 'exitCode' in value;
|
|
755
832
|
}
|
|
833
|
+
function padTime(value) {
|
|
834
|
+
return String(value).padStart(2, '0');
|
|
835
|
+
}
|
|
836
|
+
function localExpiryTime(expiresAt) {
|
|
837
|
+
const date = typeof expiresAt === 'number'
|
|
838
|
+
? new Date(expiresAt)
|
|
839
|
+
: typeof expiresAt === 'string'
|
|
840
|
+
? new Date(/^\d+$/.test(expiresAt) ? Number(expiresAt) : expiresAt)
|
|
841
|
+
: undefined;
|
|
842
|
+
if (!date || Number.isNaN(date.getTime()))
|
|
843
|
+
return undefined;
|
|
844
|
+
return `${padTime(date.getHours())}:${padTime(date.getMinutes())}`;
|
|
845
|
+
}
|
|
846
|
+
function withApprovalHint(outcome) {
|
|
847
|
+
if (outcome.exitCode !== 8)
|
|
848
|
+
return outcome;
|
|
849
|
+
const time = localExpiryTime(outcome.payload['expiresAt']);
|
|
850
|
+
const receiptId = outcome.payload['receiptId'];
|
|
851
|
+
const lines = [
|
|
852
|
+
`→ Waiting for the user's approval in the App window${time ? ` (expires ${time})` : ''}. Do not end your turn: ending it cancels the request.`,
|
|
853
|
+
...(typeof receiptId === 'string' && receiptId.length > 0
|
|
854
|
+
? [`→ Wait: crewx app receipt ${receiptId} --wait=${receipt_wait_js_1.RECEIPT_WAIT_DEFAULT_SECONDS} (repeat on exit 124 until it settles)`]
|
|
855
|
+
: []),
|
|
856
|
+
'→ Report success only if it settles as succeeded.',
|
|
857
|
+
];
|
|
858
|
+
return { ...outcome, stderr: [outcome.stderr, ...lines].filter(Boolean).join('\n') };
|
|
859
|
+
}
|
|
860
|
+
const MANUAL_READ_LIMIT_BYTES = 256 * 1024;
|
|
861
|
+
function readAppManual(appDirectory) {
|
|
862
|
+
try {
|
|
863
|
+
const realDirectory = fs.realpathSync(appDirectory);
|
|
864
|
+
const realFile = fs.realpathSync(path.join(realDirectory, 'SKILL.md'));
|
|
865
|
+
if (!isPathInside(realDirectory, realFile) || !isFile(realFile))
|
|
866
|
+
return undefined;
|
|
867
|
+
const descriptor = fs.openSync(realFile, 'r');
|
|
868
|
+
try {
|
|
869
|
+
const bytes = new Uint8Array(MANUAL_READ_LIMIT_BYTES);
|
|
870
|
+
const bytesRead = fs.readSync(descriptor, bytes, 0, bytes.length, 0);
|
|
871
|
+
return (0, usage_js_1.manualBody)(Buffer.from(bytes.buffer, 0, bytesRead).toString('utf8'));
|
|
872
|
+
}
|
|
873
|
+
finally {
|
|
874
|
+
fs.closeSync(descriptor);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
catch {
|
|
878
|
+
return undefined;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
async function findLocalApp(appId) {
|
|
882
|
+
const root = resolveWorkspaceRoot();
|
|
883
|
+
if (!root)
|
|
884
|
+
return undefined;
|
|
885
|
+
try {
|
|
886
|
+
const sdk = await Promise.resolve().then(() => __importStar(require('@crewx/sdk')));
|
|
887
|
+
const discovered = await sdk.discoverWorkspaceApps(root);
|
|
888
|
+
const candidate = discovered.apps.find((app) => app.appId === appId)
|
|
889
|
+
?? discovered.apps.find((app) => app.pluginId === appId);
|
|
890
|
+
if (!candidate)
|
|
891
|
+
return undefined;
|
|
892
|
+
return {
|
|
893
|
+
candidate,
|
|
894
|
+
directory: path.join(root, candidate.relativePath),
|
|
895
|
+
resolveAccess: sdk.resolveAgentAccess,
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
catch {
|
|
899
|
+
return undefined;
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
async function executeAppUsage(args) {
|
|
903
|
+
if (args.length !== 2 || args[1].startsWith('--'))
|
|
904
|
+
return usageFailure();
|
|
905
|
+
const appId = args[1];
|
|
906
|
+
const context = contextFromEnvironment();
|
|
907
|
+
if (isAppCommandOutcome(context))
|
|
908
|
+
return context;
|
|
909
|
+
const list = await loadLaunchers(context);
|
|
910
|
+
if (!list.ok)
|
|
911
|
+
return list.outcome;
|
|
912
|
+
const launcher = findLauncher(list.value, appId);
|
|
913
|
+
if (!launcher)
|
|
914
|
+
return failure(6, 'MCP_APP_NOT_FOUND', null);
|
|
915
|
+
const serverActions = launcher.agentActions.filter((item) => typeof item === 'string');
|
|
916
|
+
const local = await findLocalApp(appId);
|
|
917
|
+
const text = local
|
|
918
|
+
? (0, usage_js_1.renderUsage)(readAppManual(local.directory), (0, usage_js_1.renderActionsSection)(appId, local.candidate, (0, usage_js_1.buildActionRows)(local.candidate, new Set(serverActions), (action) => local.resolveAccess(action), launcher.agentAccess)), local.candidate.manifest.csp?.connectDomains)
|
|
919
|
+
: (0, usage_js_1.renderUsage)(undefined, (0, usage_js_1.renderServerOnlyActions)(appId, serverActions));
|
|
920
|
+
return { exitCode: 0, payload: { ok: true }, stdout: text };
|
|
921
|
+
}
|
|
756
922
|
function parseSingleAppCommand(args, command) {
|
|
757
923
|
if (args[0] !== command)
|
|
758
924
|
return null;
|
|
@@ -778,6 +944,16 @@ function parseSingleAppCommand(args, command) {
|
|
|
778
944
|
function standardJsonOutput(outcome) {
|
|
779
945
|
return `${JSON.stringify(outcome.payload)}\n`;
|
|
780
946
|
}
|
|
947
|
+
function manifestReasonSuffix(item) {
|
|
948
|
+
const detail = item['detail'];
|
|
949
|
+
if (item['rule'] !== 'manifest' || !isRecord(detail))
|
|
950
|
+
return '';
|
|
951
|
+
const path = typeof detail['path'] === 'string' ? detail['path'] : '';
|
|
952
|
+
const rule = typeof detail['rule'] === 'string' ? detail['rule'] : '';
|
|
953
|
+
if (!rule)
|
|
954
|
+
return '';
|
|
955
|
+
return ` ${path ? `${path} ` : ''}(${rule})`;
|
|
956
|
+
}
|
|
781
957
|
async function executeAppCheck(args) {
|
|
782
958
|
const parsed = parseSingleAppCommand(args, 'check');
|
|
783
959
|
if (!parsed)
|
|
@@ -816,7 +992,7 @@ async function executeAppCheck(args) {
|
|
|
816
992
|
const marker = severity === 'error' ? '✕' : severity === 'ok' ? '✓' : '!';
|
|
817
993
|
const where = typeof item['where'] === 'string' ? item['where'] : '';
|
|
818
994
|
const code = typeof item['code'] === 'string' ? item['code'] : '';
|
|
819
|
-
return [`${marker} ${where} ${code}`];
|
|
995
|
+
return [`${marker} ${where} ${code}${manifestReasonSuffix(item)}`];
|
|
820
996
|
});
|
|
821
997
|
lines.push(`Summary: ${errors} errors, ${warnings} warnings`);
|
|
822
998
|
return {
|
|
@@ -921,7 +1097,10 @@ function hasBuildScript(appDirectory) {
|
|
|
921
1097
|
}
|
|
922
1098
|
}
|
|
923
1099
|
function buildOutputPaths(appDirectory) {
|
|
924
|
-
const result = new Set([
|
|
1100
|
+
const result = new Set([
|
|
1101
|
+
path.normalize(APP_MANIFEST_FILE),
|
|
1102
|
+
path.normalize(LEGACY_APP_MANIFEST_FILE),
|
|
1103
|
+
]);
|
|
925
1104
|
const manifest = readAppManifest(appDirectory);
|
|
926
1105
|
const entry = manifest?.['entry'];
|
|
927
1106
|
if (typeof entry === 'string' && entry.length > 0 && !path.isAbsolute(entry)) {
|
|
@@ -1172,13 +1351,122 @@ async function executeAppDev(args) {
|
|
|
1172
1351
|
}
|
|
1173
1352
|
return runAppDevWatcher(prepared.context, hasBuildScript(prepared.context.appDirectory));
|
|
1174
1353
|
}
|
|
1175
|
-
|
|
1354
|
+
const BUILD_USAGE = 'usage: crewx app build [<appDir>] [--check | --watch]';
|
|
1355
|
+
const APP_HELP = [
|
|
1356
|
+
'crewx app <command>',
|
|
1357
|
+
'',
|
|
1358
|
+
' list List Apps available to this task',
|
|
1359
|
+
' open <appId> Open an App in this conversation',
|
|
1360
|
+
' state <instanceId> Read AI-visible state, buttons, and agent access',
|
|
1361
|
+
' run <instanceId|appId> <actionId> [--input <JSON>|-] [--request-id <id>] Run one available action',
|
|
1362
|
+
' receipt <receiptId> [--wait=<seconds>] Read a safe action receipt',
|
|
1363
|
+
' build [<appDir>] [--check | --watch] Build a @Tool App: server.ts -> dist/ and the manifest.json actions',
|
|
1364
|
+
' dev <app> [--no-open] Start a foreground App dev session',
|
|
1365
|
+
' check <app> [--json] Run the App dev checks',
|
|
1366
|
+
' run --dev <app> <actionId> [--input <JSON>|-] Run an App action through the dev checks',
|
|
1367
|
+
' usage [<appId>] Show the guide, or one App\'s manual and action table',
|
|
1368
|
+
'',
|
|
1369
|
+
].join('\n');
|
|
1370
|
+
const BUILD_HELP = [
|
|
1371
|
+
BUILD_USAGE,
|
|
1372
|
+
'',
|
|
1373
|
+
' Builds <appDir> (default: the current directory) from its server.ts: dist/server.mjs, dist/entry.mjs,',
|
|
1374
|
+
' and the actions of manifest.json. --check writes nothing and exits 1 when a generated file is stale.',
|
|
1375
|
+
' --watch rebuilds on every change until Ctrl-C.',
|
|
1376
|
+
'',
|
|
1377
|
+
].join('\n');
|
|
1378
|
+
function loadAppBuilder() {
|
|
1379
|
+
const candidates = [
|
|
1380
|
+
path.join(__dirname, '..', '..', 'build-app', 'load.cjs'),
|
|
1381
|
+
path.join(__dirname, '..', 'build-app', 'load.cjs'),
|
|
1382
|
+
];
|
|
1383
|
+
const shim = candidates.find((candidate) => fs.existsSync(candidate));
|
|
1384
|
+
if (!shim)
|
|
1385
|
+
return Promise.reject(new Error('the App build is not part of this install'));
|
|
1386
|
+
return require(shim).loadAppBuilder();
|
|
1387
|
+
}
|
|
1388
|
+
function describeError(error) {
|
|
1389
|
+
return error instanceof Error ? error.message : String(error);
|
|
1390
|
+
}
|
|
1391
|
+
async function executeAppBuild(args) {
|
|
1392
|
+
if (args.slice(1).some((arg) => arg === '--help' || arg === '-h')) {
|
|
1393
|
+
return { exitCode: 0, payload: { ok: true }, stdout: BUILD_HELP };
|
|
1394
|
+
}
|
|
1395
|
+
let builder;
|
|
1396
|
+
try {
|
|
1397
|
+
builder = await loadAppBuilder();
|
|
1398
|
+
}
|
|
1399
|
+
catch (error) {
|
|
1400
|
+
return failure(1, 'BUILD_UNAVAILABLE', null, describeError(error));
|
|
1401
|
+
}
|
|
1402
|
+
let options;
|
|
1403
|
+
try {
|
|
1404
|
+
options = builder.parseArguments(args.slice(1));
|
|
1405
|
+
}
|
|
1406
|
+
catch (error) {
|
|
1407
|
+
return failure(2, 'USAGE', null, `${describeError(error)} → ${BUILD_USAGE}`);
|
|
1408
|
+
}
|
|
1409
|
+
const { appDirectory } = options;
|
|
1410
|
+
const shownDirectory = path.relative(process.cwd(), appDirectory);
|
|
1411
|
+
const rebuildCommand = shownDirectory === '' ? 'crewx app build' : `crewx app build ${shownDirectory}`;
|
|
1412
|
+
if (options.watch) {
|
|
1413
|
+
const controller = new AbortController();
|
|
1414
|
+
const stop = () => controller.abort();
|
|
1415
|
+
process.once('SIGINT', stop);
|
|
1416
|
+
process.once('SIGTERM', stop);
|
|
1417
|
+
try {
|
|
1418
|
+
await builder.watchApp(appDirectory, (line) => process.stdout.write(`${line}\n`), { signal: controller.signal });
|
|
1419
|
+
}
|
|
1420
|
+
finally {
|
|
1421
|
+
process.removeListener('SIGINT', stop);
|
|
1422
|
+
process.removeListener('SIGTERM', stop);
|
|
1423
|
+
}
|
|
1424
|
+
return { exitCode: 0, payload: {}, suppressOutput: true };
|
|
1425
|
+
}
|
|
1426
|
+
try {
|
|
1427
|
+
const result = await builder.buildApp({ appDirectory, check: options.check });
|
|
1428
|
+
if (options.check && result.staleFiles.length > 0) {
|
|
1429
|
+
return {
|
|
1430
|
+
...failure(1, 'BUILD_STALE', null, `Stale generated files: ${result.staleFiles.join(', ')} → Fix: ${rebuildCommand}`),
|
|
1431
|
+
payload: {
|
|
1432
|
+
ok: false,
|
|
1433
|
+
exit: 1,
|
|
1434
|
+
error: { code: 'BUILD_STALE', status: null },
|
|
1435
|
+
appDir: appDirectory,
|
|
1436
|
+
stale: result.staleFiles,
|
|
1437
|
+
},
|
|
1438
|
+
};
|
|
1439
|
+
}
|
|
1440
|
+
if (options.check) {
|
|
1441
|
+
return success({ ok: true, appDir: appDirectory, check: true, inSync: true, files: result.files, actions: result.actions });
|
|
1442
|
+
}
|
|
1443
|
+
const name = path.basename(appDirectory);
|
|
1444
|
+
const inWorkspaceApps = path.basename(path.dirname(appDirectory)) === 'apps';
|
|
1445
|
+
return {
|
|
1446
|
+
...success({ ok: true, appDir: appDirectory, files: result.files, actions: result.actions }),
|
|
1447
|
+
stderr: inWorkspaceApps
|
|
1448
|
+
? `→ Next: crewx app check ${name}`
|
|
1449
|
+
: `→ Next: put ${name} in the workspace apps/ folder, then crewx app check ${name}`,
|
|
1450
|
+
};
|
|
1451
|
+
}
|
|
1452
|
+
catch (error) {
|
|
1453
|
+
return failure(1, 'BUILD_FAILED', null, describeError(error));
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
async function executeAppCommand(args, { clock = receipt_wait_js_1.realClock } = {}) {
|
|
1457
|
+
if (args.length === 1 && (args[0] === '--help' || args[0] === '-h')) {
|
|
1458
|
+
return { exitCode: 0, payload: { ok: true }, stdout: APP_HELP };
|
|
1459
|
+
}
|
|
1460
|
+
if (args[0] === 'build')
|
|
1461
|
+
return executeAppBuild(args);
|
|
1176
1462
|
if (args[0] === 'dev')
|
|
1177
1463
|
return executeAppDev(args);
|
|
1178
1464
|
if (args[0] === 'check')
|
|
1179
1465
|
return executeAppCheck(args);
|
|
1180
1466
|
if (args[0] === 'run' && args[1] === '--dev')
|
|
1181
1467
|
return executeAppDevAction(args);
|
|
1468
|
+
if (args[0] === 'usage')
|
|
1469
|
+
return executeAppUsage(args);
|
|
1182
1470
|
const parsed = parseCommand(args);
|
|
1183
1471
|
if (!parsed)
|
|
1184
1472
|
return usageFailure();
|
|
@@ -1193,22 +1481,13 @@ async function executeAppCommand(args) {
|
|
|
1193
1481
|
}
|
|
1194
1482
|
if (parsed.command === 'receipt') {
|
|
1195
1483
|
const receiptId = parsed.positional[0];
|
|
1196
|
-
const
|
|
1197
|
-
if (
|
|
1198
|
-
return
|
|
1199
|
-
const
|
|
1200
|
-
if (
|
|
1201
|
-
return
|
|
1202
|
-
return
|
|
1203
|
-
ok: true,
|
|
1204
|
-
receipt,
|
|
1205
|
-
outcome: Object.prototype.hasOwnProperty.call(receipt, 'outcome') ? receipt['outcome'] : null,
|
|
1206
|
-
...(typeof receipt['errorCode'] === 'string' ? { errorCode: receipt['errorCode'] } : {}),
|
|
1207
|
-
...(Object.prototype.hasOwnProperty.call(response.value, 'result') ? { result: response.value['result'] } : {}),
|
|
1208
|
-
...(Object.prototype.hasOwnProperty.call(response.value, 'resultExpired')
|
|
1209
|
-
? { resultExpired: response.value['resultExpired'] }
|
|
1210
|
-
: {}),
|
|
1211
|
-
});
|
|
1484
|
+
const waitSeconds = (0, receipt_wait_js_1.clampWaitSeconds)(parsed.waitSeconds ?? 0);
|
|
1485
|
+
if (waitSeconds === 0)
|
|
1486
|
+
return (await readReceipt(context, receiptId)).value;
|
|
1487
|
+
const last = await (0, receipt_wait_js_1.pollUntilSettled)(() => readReceipt(context, receiptId), waitSeconds, clock);
|
|
1488
|
+
if (last.state !== 'pending')
|
|
1489
|
+
return last.value;
|
|
1490
|
+
return receiptStillPending(receiptId, waitSeconds);
|
|
1212
1491
|
}
|
|
1213
1492
|
if (parsed.command === 'state') {
|
|
1214
1493
|
const result = await readInstanceState(context, parsed.positional[0]);
|
|
@@ -1246,7 +1525,10 @@ async function executeAppCommand(args) {
|
|
|
1246
1525
|
}
|
|
1247
1526
|
const action = await runAction(context, instanceId, actionId, requestId, parsed.input, RUN_REQUEST_TIMEOUT_MS);
|
|
1248
1527
|
if (!action.ok)
|
|
1249
|
-
return withRunTimeoutGuidance(action.outcome, requestId);
|
|
1250
|
-
return
|
|
1528
|
+
return withApprovalHint(withRunTimeoutGuidance(action.outcome, requestId));
|
|
1529
|
+
return {
|
|
1530
|
+
...success({ ok: true, ...action.value }),
|
|
1531
|
+
stderr: `→ Check: crewx app state ${action.value.instanceId}`,
|
|
1532
|
+
};
|
|
1251
1533
|
}
|
|
1252
1534
|
//# sourceMappingURL=engine.js.map
|