@auraindustry/aurajs 0.0.5 → 0.0.6

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/src/cli.mjs CHANGED
@@ -9,7 +9,13 @@ import {
9
9
  formatBundleError,
10
10
  isBundleError,
11
11
  } from './bundler.mjs';
12
- import { scaffold, scaffoldGame, listCreateTemplates, normalizeCreateTemplate } from './scaffold.mjs';
12
+ import {
13
+ scaffold,
14
+ scaffoldGame,
15
+ listCreateTemplates,
16
+ listCreateTemplateAliasHints,
17
+ normalizeCreateTemplate,
18
+ } from './scaffold.mjs';
13
19
  import { packageAssets } from './asset-pack.mjs';
14
20
  import { writeBuildManifest, writeWebBuildArtifacts } from './build-contract.mjs';
15
21
  import {
@@ -29,6 +35,16 @@ import {
29
35
  DEFAULT_STATE_MUTATION_GUARDRAILS,
30
36
  STATE_MUTATION_ALLOWLIST_PREFIXES,
31
37
  } from './game-state-runtime.mjs';
38
+ import {
39
+ StateArtifactError,
40
+ createStateArtifactEnvelope,
41
+ listStateArtifacts,
42
+ loadStateArtifact,
43
+ normalizeStateArtifactName,
44
+ resolveStateArtifactPath,
45
+ writeStateArtifactEnvelope,
46
+ } from './state-artifacts.mjs';
47
+ import { prepareDevStateRestoreEntry } from './state-dev-reload.mjs';
32
48
 
33
49
  // ---------------------------------------------------------------------------
34
50
  // Version
@@ -36,6 +52,9 @@ import {
36
52
 
37
53
  const PKG_PATH = new URL('../package.json', import.meta.url);
38
54
  const VERSION = JSON.parse(readFileSync(PKG_PATH, 'utf8')).version;
55
+ const CREATE_TEMPLATE_OPTION_LIST = listCreateTemplates().join('|');
56
+ const CREATE_TEMPLATE_ALIAS_HINT_LIST = listCreateTemplateAliasHints().join(', ');
57
+ const CREATE_USAGE = `aura create <name> [--template <${CREATE_TEMPLATE_OPTION_LIST}>] [--skip-install]`;
39
58
 
40
59
  // ---------------------------------------------------------------------------
41
60
  // Command registry
@@ -45,10 +64,10 @@ const COMMANDS = {
45
64
  init: { description: 'Scaffold a new AuraJS project', usage: 'aura init <name>' },
46
65
  create: {
47
66
  description: 'Scaffold a full npm-ready AuraJS game',
48
- usage: 'aura create <name> [--template <2d-shooter|3d-platformer|blank>] [--skip-install]',
67
+ usage: CREATE_USAGE,
49
68
  details: [
50
69
  ' Options:',
51
- ` --template <${listCreateTemplates().join('|')}> Choose starting game template (aliases: 2d, 3d, shooter, platformer)`,
70
+ ` --template <${CREATE_TEMPLATE_OPTION_LIST}> Choose starting game template (aliases: ${CREATE_TEMPLATE_ALIAS_HINT_LIST})`,
52
71
  ' --skip-install Skip npm install after scaffold',
53
72
  ],
54
73
  },
@@ -57,11 +76,14 @@ const COMMANDS = {
57
76
  usage: 'aura dev [--target web]',
58
77
  details: [
59
78
  ' Options:',
60
- ' --target web Run browser web-build dev loop (default: native host loop)',
79
+ ' --target web Run browser web-build rebuild loop for the current browser-backed subset',
80
+ ' --restore-slot <name> Reapply a saved slot after each native dev restart',
81
+ ' --restore-checkpoint <name> Reapply a saved checkpoint after each native dev restart',
61
82
  '',
62
83
  ' Notes:',
63
84
  ' - default `aura dev` preserves native host hot-reload semantics.',
64
- ' - `aura dev --target web` rebuilds web artifacts + emits refresh diagnostics.',
85
+ ' - restore flags are native-only and reapply a persisted state artifact after setup.',
86
+ ' - `aura dev --target web` rebuilds `build/web` and emits refresh diagnostics; serve those artifacts in a browser.',
65
87
  ],
66
88
  },
67
89
  build: {
@@ -73,24 +95,39 @@ const COMMANDS = {
73
95
  ' --asset-mode <embed|sibling> Package assets in pak or sibling assets/',
74
96
  '',
75
97
  ' Notes:',
76
- ' - `--target web` emits browser artifacts under `build/web`.',
98
+ ' - `--target web` emits browser artifacts under `build/web` for the current browser-backed subset.',
77
99
  ' - Native targets are current host only in v1.',
78
100
  ' - `--target all` keeps current host output; use CI matrix for all platforms.',
101
+ ' - Browser runtime truth today: current 2D/input/assets/storage path is supported; `aura.state` and `aura.net` remain reason-coded unsupported.',
102
+ ],
103
+ },
104
+ run: {
105
+ description: 'Build and run the game',
106
+ usage: 'aura run',
107
+ details: [
108
+ ' Notes:',
109
+ ' - `aura run` is native-only in v1.',
110
+ ' - For web, use `aura build --target web` and serve `build/web` in a browser.',
79
111
  ],
80
112
  },
81
- run: { description: 'Build and run the game', usage: 'aura run' },
82
113
  clean: { description: 'Delete build artifacts', usage: 'aura clean' },
83
114
  test: { description: 'Run game logic in headless mode', usage: 'aura test [file] [--width N] [--height N] [--frames N]' },
84
115
  conformance: { description: 'Run API conformance suites in headless mode', usage: 'aura conformance [--mode shim|native|both] [--json]' },
85
116
  state: {
86
117
  description: 'Export, diff, and patch canonical game-state snapshots',
87
- usage: 'aura state <export|diff|patch|apply> [options]',
118
+ usage: 'aura state <export|diff|patch|apply|slot|checkpoint> [options]',
88
119
  details: [
89
120
  ' Subcommands:',
90
121
  ' export Emit canonical game-state-v1 JSON snapshot',
91
122
  ' diff <before.json> <after.json> Emit deterministic patch payload',
92
123
  ' patch <state.json> <patch.json> Apply patch payload to a state snapshot',
93
124
  ' apply <state.json> Apply state payload via runtime hooks',
125
+ ' slot save <name> Persist named save slot under .aura/state/slots/',
126
+ ' slot restore <name> Reapply named save slot via runtime hooks',
127
+ ' slot list List named save slots with metadata',
128
+ ' checkpoint save <name> Persist named checkpoint under .aura/state/checkpoints/',
129
+ ' checkpoint restore <name> Reapply named checkpoint via runtime hooks',
130
+ ' checkpoint list List named checkpoints with metadata',
94
131
  '',
95
132
  ' Options:',
96
133
  ' export:',
@@ -127,6 +164,20 @@ const COMMANDS = {
127
164
  ' --max-bytes <N> Max payload bytes per input (default: 262144)',
128
165
  ' --max-runtime-ms <N> Max mutation runtime budget in ms (default: 200)',
129
166
  ' --audit <path|-> Audit artifact path (default: .aura/state/state-apply-audit.json)',
167
+ '',
168
+ ' slot/checkpoint save:',
169
+ ' --report <path|-> Write command report JSON to file (or "-" for stdout)',
170
+ ' --artifact <path> Override saved artifact path',
171
+ ' --note <text> Optional metadata note stored with the artifact',
172
+ '',
173
+ ' slot/checkpoint restore:',
174
+ ' --report <path|-> Write command report JSON to file (or "-" for stdout)',
175
+ ' --artifact <path> Override artifact path to restore from',
176
+ '',
177
+ ' slot/checkpoint list:',
178
+ ' --report <path|-> Write listing JSON to file (or "-" for stdout)',
179
+ ' --compact Compact JSON output (single line)',
180
+ ' --pretty Pretty JSON output (default)',
130
181
  ],
131
182
  },
132
183
  };
@@ -270,14 +321,14 @@ function parseCreateArgs(args) {
270
321
  throw new Error(`Unknown option for create: ${arg}`);
271
322
  }
272
323
  if (name !== null) {
273
- throw new Error(`Unexpected extra argument "${arg}". Usage: aura create <name> [--template <2d-shooter|3d-platformer|blank>] [--skip-install]`);
324
+ throw new Error(`Unexpected extra argument "${arg}". Usage: ${CREATE_USAGE}`);
274
325
  }
275
326
  name = arg;
276
327
  }
277
328
 
278
329
  const normalizedTemplate = normalizeCreateTemplate(template);
279
330
  if (!normalizedTemplate || !allowedTemplates.has(normalizedTemplate)) {
280
- throw new Error(`Invalid --template "${template}". Supported templates: ${[...allowedTemplates].join(', ')} (aliases: 2d, 3d, shooter, platformer).`);
331
+ throw new Error(`Invalid --template "${template}". Supported templates: ${[...allowedTemplates].join(', ')} (aliases: ${CREATE_TEMPLATE_ALIAS_HINT_LIST}).`);
281
332
  }
282
333
 
283
334
  return { name, template: normalizedTemplate, skipInstall };
@@ -902,121 +953,640 @@ function parseStateApplyArgs(args) {
902
953
  return parsed;
903
954
  }
904
955
 
905
- function normalizeStateCommandInputPath(value, flagName, reasonCode) {
906
- const normalized = String(value || '').trim();
907
- if (normalized.length === 0) {
908
- throw new StateExportError(reasonCode, `${flagName} requires a non-empty file path.`);
909
- }
910
- return resolve(process.cwd(), normalized);
911
- }
956
+ function parseDevArgs(args) {
957
+ const parsed = {
958
+ target: null,
959
+ restoreKind: null,
960
+ restoreName: null,
961
+ };
912
962
 
913
- function normalizeStateCommandOutput(value, reasonCode) {
914
- const normalized = String(value || '').trim();
915
- if (normalized.length === 0) {
916
- throw new StateExportError(reasonCode, '--output requires a non-empty path or "-".');
917
- }
918
- if (normalized === '-') {
919
- return null;
920
- }
921
- return resolve(process.cwd(), normalized);
922
- }
963
+ for (let i = 0; i < args.length; i += 1) {
964
+ const token = args[i];
923
965
 
924
- function normalizeStateAuditOutput(value, reasonCode) {
925
- const normalized = String(value || '').trim();
926
- if (normalized.length === 0) {
927
- throw new StateExportError(reasonCode, '--audit requires a non-empty path or "-".');
928
- }
929
- if (normalized === '-') {
930
- return null;
966
+ if (token === '--target') {
967
+ const value = readRequiredOptionValue(args, i, '--target');
968
+ parsed.target = normalizeDevTarget(value);
969
+ i += 1;
970
+ continue;
971
+ }
972
+ if (token.startsWith('--target=')) {
973
+ parsed.target = normalizeDevTarget(token.slice('--target='.length));
974
+ continue;
975
+ }
976
+
977
+ if (token === '--restore-slot') {
978
+ const value = readRequiredOptionValue(args, i, '--restore-slot');
979
+ if (parsed.restoreKind) {
980
+ throw new StateExportError(
981
+ 'invalid_dev_args',
982
+ 'Use only one of --restore-slot or --restore-checkpoint.',
983
+ );
984
+ }
985
+ parsed.restoreKind = 'slot';
986
+ parsed.restoreName = normalizeStateArtifactName(value, { kind: 'slot' });
987
+ i += 1;
988
+ continue;
989
+ }
990
+ if (token.startsWith('--restore-slot=')) {
991
+ if (parsed.restoreKind) {
992
+ throw new StateExportError(
993
+ 'invalid_dev_args',
994
+ 'Use only one of --restore-slot or --restore-checkpoint.',
995
+ );
996
+ }
997
+ parsed.restoreKind = 'slot';
998
+ parsed.restoreName = normalizeStateArtifactName(token.slice('--restore-slot='.length), { kind: 'slot' });
999
+ continue;
1000
+ }
1001
+
1002
+ if (token === '--restore-checkpoint') {
1003
+ const value = readRequiredOptionValue(args, i, '--restore-checkpoint');
1004
+ if (parsed.restoreKind) {
1005
+ throw new StateExportError(
1006
+ 'invalid_dev_args',
1007
+ 'Use only one of --restore-slot or --restore-checkpoint.',
1008
+ );
1009
+ }
1010
+ parsed.restoreKind = 'checkpoint';
1011
+ parsed.restoreName = normalizeStateArtifactName(value, { kind: 'checkpoint' });
1012
+ i += 1;
1013
+ continue;
1014
+ }
1015
+ if (token.startsWith('--restore-checkpoint=')) {
1016
+ if (parsed.restoreKind) {
1017
+ throw new StateExportError(
1018
+ 'invalid_dev_args',
1019
+ 'Use only one of --restore-slot or --restore-checkpoint.',
1020
+ );
1021
+ }
1022
+ parsed.restoreKind = 'checkpoint';
1023
+ parsed.restoreName = normalizeStateArtifactName(
1024
+ token.slice('--restore-checkpoint='.length),
1025
+ { kind: 'checkpoint' },
1026
+ );
1027
+ continue;
1028
+ }
1029
+
1030
+ if (token.startsWith('--')) {
1031
+ throw new StateExportError('invalid_dev_args', `Unknown dev option: ${token}`);
1032
+ }
931
1033
  }
932
- return resolve(process.cwd(), normalized);
933
- }
934
1034
 
935
- function defaultStatePatchAuditPath(projectRoot) {
936
- return resolve(projectRoot, '.aura/state/state-patch-audit.json');
1035
+ return parsed;
937
1036
  }
938
1037
 
939
- function defaultStateApplyAuditPath(projectRoot) {
940
- return resolve(projectRoot, '.aura/state/state-apply-audit.json');
1038
+ function normalizeDevTarget(value) {
1039
+ const normalized = String(value || '').trim().toLowerCase();
1040
+ if (normalized === 'macos' || normalized === 'darwin' || normalized === 'mac') return 'mac';
1041
+ if (normalized === 'win' || normalized === 'win32' || normalized === 'windows') return 'windows';
1042
+ if (normalized === 'browser' || normalized === 'web') return 'web';
1043
+ if (normalized === 'linux') return 'linux';
1044
+ throw new StateExportError(
1045
+ 'invalid_dev_args',
1046
+ `Unsupported dev target "${value}". Supported values: web.`,
1047
+ );
941
1048
  }
942
1049
 
943
- function readJsonInputFile(filePath, label, reasonCodePrefix) {
944
- let text;
945
- try {
946
- text = readFileSync(filePath, 'utf8');
947
- } catch (error) {
948
- throw new StateExportError(
949
- `${reasonCodePrefix}_read_failed`,
950
- `Failed to read ${label} "${filePath}": ${error.message}`,
951
- );
952
- }
953
- try {
954
- return JSON.parse(text);
955
- } catch (error) {
956
- throw new StateExportError(
957
- `${reasonCodePrefix}_invalid_json`,
958
- `${label} "${filePath}" is not valid JSON: ${error.message}`,
959
- );
960
- }
961
- }
1050
+ function parseStateArtifactSaveArgs(kind, args) {
1051
+ const normalizedKind = kind === 'slot' ? 'slot' : 'checkpoint';
1052
+ const reasonCode = `invalid_state_${normalizedKind}_save_args`;
1053
+ const parsed = {
1054
+ kind: normalizedKind,
1055
+ name: null,
1056
+ artifactPath: null,
1057
+ reportPath: null,
1058
+ note: null,
1059
+ file: null,
1060
+ mode: 'headless',
1061
+ frames: 1,
1062
+ schemaVersion: GAME_STATE_SCHEMA_VERSION,
1063
+ compact: false,
1064
+ };
962
1065
 
963
- function writeStateJsonOutput(filePath, jsonText, reasonCode) {
964
- if (!filePath) {
965
- process.stdout.write(jsonText);
966
- return;
1066
+ let formattingFlag = 'pretty';
1067
+
1068
+ for (let i = 0; i < args.length; i += 1) {
1069
+ const token = args[i];
1070
+
1071
+ if (token === '--artifact') {
1072
+ parsed.artifactPath = normalizeStateArtifactFilePath(
1073
+ readRequiredOptionValue(args, i, '--artifact'),
1074
+ '--artifact',
1075
+ reasonCode,
1076
+ );
1077
+ i += 1;
1078
+ continue;
1079
+ }
1080
+ if (token.startsWith('--artifact=')) {
1081
+ parsed.artifactPath = normalizeStateArtifactFilePath(
1082
+ token.slice('--artifact='.length),
1083
+ '--artifact',
1084
+ reasonCode,
1085
+ );
1086
+ continue;
1087
+ }
1088
+
1089
+ if (token === '--report') {
1090
+ parsed.reportPath = normalizeStateCommandOutput(
1091
+ readRequiredOptionValue(args, i, '--report'),
1092
+ reasonCode,
1093
+ );
1094
+ i += 1;
1095
+ continue;
1096
+ }
1097
+ if (token.startsWith('--report=')) {
1098
+ parsed.reportPath = normalizeStateCommandOutput(token.slice('--report='.length), reasonCode);
1099
+ continue;
1100
+ }
1101
+
1102
+ if (token === '--note') {
1103
+ parsed.note = normalizeOptionalNote(readRequiredOptionValue(args, i, '--note'));
1104
+ i += 1;
1105
+ continue;
1106
+ }
1107
+ if (token.startsWith('--note=')) {
1108
+ parsed.note = normalizeOptionalNote(token.slice('--note='.length));
1109
+ continue;
1110
+ }
1111
+
1112
+ if (token === '--mode') {
1113
+ parsed.mode = normalizeStateExportMode(readRequiredOptionValue(args, i, '--mode'));
1114
+ i += 1;
1115
+ continue;
1116
+ }
1117
+ if (token.startsWith('--mode=')) {
1118
+ parsed.mode = normalizeStateExportMode(token.slice('--mode='.length));
1119
+ continue;
1120
+ }
1121
+
1122
+ if (token === '--frames') {
1123
+ parsed.frames = parseNonNegativeIntegerArg(readRequiredOptionValue(args, i, '--frames'), '--frames', reasonCode);
1124
+ i += 1;
1125
+ continue;
1126
+ }
1127
+ if (token.startsWith('--frames=')) {
1128
+ parsed.frames = parseNonNegativeIntegerArg(token.slice('--frames='.length), '--frames', reasonCode);
1129
+ continue;
1130
+ }
1131
+
1132
+ if (token === '--schema' || token === '--schema-version') {
1133
+ parsed.schemaVersion = normalizeStateExportSchemaVersion(readRequiredOptionValue(args, i, token));
1134
+ i += 1;
1135
+ continue;
1136
+ }
1137
+ if (token.startsWith('--schema=')) {
1138
+ parsed.schemaVersion = normalizeStateExportSchemaVersion(token.slice('--schema='.length));
1139
+ continue;
1140
+ }
1141
+ if (token.startsWith('--schema-version=')) {
1142
+ parsed.schemaVersion = normalizeStateExportSchemaVersion(token.slice('--schema-version='.length));
1143
+ continue;
1144
+ }
1145
+
1146
+ if (token === '--compact') {
1147
+ if (formattingFlag === 'pretty') {
1148
+ parsed.compact = true;
1149
+ formattingFlag = 'compact';
1150
+ continue;
1151
+ }
1152
+ if (formattingFlag === 'compact') continue;
1153
+ throw new StateExportError(reasonCode, 'Cannot use --compact and --pretty together.');
1154
+ }
1155
+ if (token === '--pretty') {
1156
+ if (formattingFlag === 'compact') {
1157
+ throw new StateExportError(reasonCode, 'Cannot use --compact and --pretty together.');
1158
+ }
1159
+ parsed.compact = false;
1160
+ formattingFlag = 'pretty';
1161
+ continue;
1162
+ }
1163
+
1164
+ if (token === '--file') {
1165
+ parsed.file = normalizeStateExportFile(readRequiredOptionValue(args, i, '--file'));
1166
+ i += 1;
1167
+ continue;
1168
+ }
1169
+ if (token.startsWith('--file=')) {
1170
+ parsed.file = normalizeStateExportFile(token.slice('--file='.length));
1171
+ continue;
1172
+ }
1173
+
1174
+ if (token.startsWith('--')) {
1175
+ throw new StateExportError(reasonCode, `Unknown ${normalizedKind} save option: ${token}`);
1176
+ }
1177
+
1178
+ if (parsed.name !== null) {
1179
+ throw new StateExportError(
1180
+ reasonCode,
1181
+ `Unexpected extra argument "${token}". Usage: aura state ${normalizedKind} save <name> [options]`,
1182
+ );
1183
+ }
1184
+ parsed.name = normalizeStateArtifactName(token, { kind: normalizedKind });
967
1185
  }
968
1186
 
969
- try {
970
- mkdirSync(dirname(filePath), { recursive: true });
971
- writeFileSync(filePath, jsonText, 'utf8');
972
- } catch (error) {
1187
+ if (!parsed.name) {
973
1188
  throw new StateExportError(
974
1189
  reasonCode,
975
- `Failed to write state output "${filePath}": ${error.message}`,
1190
+ `Missing ${normalizedKind} name. Usage: aura state ${normalizedKind} save <name> [options]`,
976
1191
  );
977
1192
  }
978
- }
979
-
980
- function writeStateMutationAudit(auditPath, payload) {
981
- const text = `${JSON.stringify(payload, null, 2)}\n`;
982
- if (auditPath === null) {
983
- process.stdout.write(text);
984
- return;
985
- }
986
- try {
987
- mkdirSync(dirname(auditPath), { recursive: true });
988
- writeFileSync(auditPath, text, 'utf8');
989
- } catch (error) {
1193
+ if (parsed.schemaVersion !== GAME_STATE_SCHEMA_VERSION) {
990
1194
  throw new StateExportError(
991
- 'state_mutation_audit_write_failed',
992
- `Failed to write state mutation audit "${auditPath}": ${error.message}`,
1195
+ 'schema_version_mismatch',
1196
+ `Unsupported schema version "${parsed.schemaVersion}". Expected "${GAME_STATE_SCHEMA_VERSION}".`,
993
1197
  );
994
1198
  }
995
- }
996
1199
 
997
- function jsonByteSize(value) {
998
- let text = '';
999
- try {
1000
- text = JSON.stringify(value);
1001
- } catch {
1002
- text = '';
1003
- }
1004
- return Buffer.byteLength(text || '', 'utf8');
1200
+ return parsed;
1005
1201
  }
1006
1202
 
1007
- function mutationGuardrailsFromParsed(parsed) {
1008
- return {
1009
- maxMutations: parsed.maxOps,
1010
- maxPayloadBytes: parsed.maxBytes,
1011
- maxRuntimeMs: parsed.maxRuntimeMs,
1012
- allowlistPrefixes: STATE_MUTATION_ALLOWLIST_PREFIXES,
1203
+ function parseStateArtifactRestoreArgs(kind, args) {
1204
+ const normalizedKind = kind === 'slot' ? 'slot' : 'checkpoint';
1205
+ const reasonCode = `invalid_state_${normalizedKind}_restore_args`;
1206
+ const parsed = {
1207
+ kind: normalizedKind,
1208
+ name: null,
1209
+ artifactPath: null,
1210
+ reportPath: null,
1211
+ file: null,
1212
+ mode: 'headless',
1213
+ frames: 1,
1214
+ schemaVersion: GAME_STATE_SCHEMA_VERSION,
1215
+ compact: false,
1216
+ dryRun: false,
1217
+ verify: false,
1218
+ rollbackOnFail: false,
1219
+ maxOps: DEFAULT_STATE_MUTATION_GUARDRAILS.maxMutations,
1220
+ maxBytes: DEFAULT_STATE_MUTATION_GUARDRAILS.maxPayloadBytes,
1221
+ maxRuntimeMs: DEFAULT_STATE_MUTATION_GUARDRAILS.maxRuntimeMs,
1222
+ auditPath: defaultStateApplyAuditPath(process.cwd()),
1013
1223
  };
1014
- }
1015
1224
 
1016
- function readRequiredOptionValue(args, index, flagName) {
1017
- const value = args[index + 1];
1018
- if (!value) {
1019
- throw new StateExportError('invalid_state_export_args', `Missing value for ${flagName}.`);
1225
+ let formattingFlag = 'pretty';
1226
+
1227
+ for (let i = 0; i < args.length; i += 1) {
1228
+ const token = args[i];
1229
+
1230
+ if (token === '--artifact') {
1231
+ parsed.artifactPath = normalizeStateArtifactFilePath(
1232
+ readRequiredOptionValue(args, i, '--artifact'),
1233
+ '--artifact',
1234
+ reasonCode,
1235
+ );
1236
+ i += 1;
1237
+ continue;
1238
+ }
1239
+ if (token.startsWith('--artifact=')) {
1240
+ parsed.artifactPath = normalizeStateArtifactFilePath(
1241
+ token.slice('--artifact='.length),
1242
+ '--artifact',
1243
+ reasonCode,
1244
+ );
1245
+ continue;
1246
+ }
1247
+
1248
+ if (token === '--report') {
1249
+ parsed.reportPath = normalizeStateCommandOutput(
1250
+ readRequiredOptionValue(args, i, '--report'),
1251
+ reasonCode,
1252
+ );
1253
+ i += 1;
1254
+ continue;
1255
+ }
1256
+ if (token.startsWith('--report=')) {
1257
+ parsed.reportPath = normalizeStateCommandOutput(token.slice('--report='.length), reasonCode);
1258
+ continue;
1259
+ }
1260
+
1261
+ if (token === '--file') {
1262
+ parsed.file = normalizeStateExportFile(readRequiredOptionValue(args, i, '--file'));
1263
+ i += 1;
1264
+ continue;
1265
+ }
1266
+ if (token.startsWith('--file=')) {
1267
+ parsed.file = normalizeStateExportFile(token.slice('--file='.length));
1268
+ continue;
1269
+ }
1270
+
1271
+ if (token === '--mode') {
1272
+ parsed.mode = normalizeStateExportMode(readRequiredOptionValue(args, i, '--mode'));
1273
+ i += 1;
1274
+ continue;
1275
+ }
1276
+ if (token.startsWith('--mode=')) {
1277
+ parsed.mode = normalizeStateExportMode(token.slice('--mode='.length));
1278
+ continue;
1279
+ }
1280
+
1281
+ if (token === '--frames') {
1282
+ parsed.frames = parseNonNegativeIntegerArg(readRequiredOptionValue(args, i, '--frames'), '--frames', reasonCode);
1283
+ i += 1;
1284
+ continue;
1285
+ }
1286
+ if (token.startsWith('--frames=')) {
1287
+ parsed.frames = parseNonNegativeIntegerArg(token.slice('--frames='.length), '--frames', reasonCode);
1288
+ continue;
1289
+ }
1290
+
1291
+ if (token === '--schema' || token === '--schema-version') {
1292
+ parsed.schemaVersion = normalizeStateExportSchemaVersion(readRequiredOptionValue(args, i, token));
1293
+ i += 1;
1294
+ continue;
1295
+ }
1296
+ if (token.startsWith('--schema=')) {
1297
+ parsed.schemaVersion = normalizeStateExportSchemaVersion(token.slice('--schema='.length));
1298
+ continue;
1299
+ }
1300
+ if (token.startsWith('--schema-version=')) {
1301
+ parsed.schemaVersion = normalizeStateExportSchemaVersion(token.slice('--schema-version='.length));
1302
+ continue;
1303
+ }
1304
+
1305
+ if (token === '--dry-run') {
1306
+ parsed.dryRun = true;
1307
+ continue;
1308
+ }
1309
+ if (token === '--verify') {
1310
+ parsed.verify = true;
1311
+ continue;
1312
+ }
1313
+ if (token === '--rollback-on-fail') {
1314
+ parsed.rollbackOnFail = true;
1315
+ continue;
1316
+ }
1317
+
1318
+ if (token === '--max-ops') {
1319
+ parsed.maxOps = parseNonNegativeIntegerArg(readRequiredOptionValue(args, i, '--max-ops'), '--max-ops', reasonCode);
1320
+ i += 1;
1321
+ continue;
1322
+ }
1323
+ if (token.startsWith('--max-ops=')) {
1324
+ parsed.maxOps = parseNonNegativeIntegerArg(token.slice('--max-ops='.length), '--max-ops', reasonCode);
1325
+ continue;
1326
+ }
1327
+
1328
+ if (token === '--max-bytes') {
1329
+ parsed.maxBytes = parseNonNegativeIntegerArg(readRequiredOptionValue(args, i, '--max-bytes'), '--max-bytes', reasonCode);
1330
+ i += 1;
1331
+ continue;
1332
+ }
1333
+ if (token.startsWith('--max-bytes=')) {
1334
+ parsed.maxBytes = parseNonNegativeIntegerArg(token.slice('--max-bytes='.length), '--max-bytes', reasonCode);
1335
+ continue;
1336
+ }
1337
+
1338
+ if (token === '--max-runtime-ms') {
1339
+ parsed.maxRuntimeMs = parseNonNegativeIntegerArg(readRequiredOptionValue(args, i, '--max-runtime-ms'), '--max-runtime-ms', reasonCode);
1340
+ i += 1;
1341
+ continue;
1342
+ }
1343
+ if (token.startsWith('--max-runtime-ms=')) {
1344
+ parsed.maxRuntimeMs = parseNonNegativeIntegerArg(token.slice('--max-runtime-ms='.length), '--max-runtime-ms', reasonCode);
1345
+ continue;
1346
+ }
1347
+
1348
+ if (token === '--audit') {
1349
+ parsed.auditPath = normalizeStateAuditOutput(readRequiredOptionValue(args, i, '--audit'), reasonCode);
1350
+ i += 1;
1351
+ continue;
1352
+ }
1353
+ if (token.startsWith('--audit=')) {
1354
+ parsed.auditPath = normalizeStateAuditOutput(token.slice('--audit='.length), reasonCode);
1355
+ continue;
1356
+ }
1357
+
1358
+ if (token === '--compact') {
1359
+ if (formattingFlag === 'pretty') {
1360
+ parsed.compact = true;
1361
+ formattingFlag = 'compact';
1362
+ continue;
1363
+ }
1364
+ if (formattingFlag === 'compact') continue;
1365
+ throw new StateExportError(reasonCode, 'Cannot use --compact and --pretty together.');
1366
+ }
1367
+ if (token === '--pretty') {
1368
+ if (formattingFlag === 'compact') {
1369
+ throw new StateExportError(reasonCode, 'Cannot use --compact and --pretty together.');
1370
+ }
1371
+ parsed.compact = false;
1372
+ formattingFlag = 'pretty';
1373
+ continue;
1374
+ }
1375
+
1376
+ if (token.startsWith('--')) {
1377
+ throw new StateExportError(reasonCode, `Unknown ${normalizedKind} restore option: ${token}`);
1378
+ }
1379
+
1380
+ if (parsed.name !== null) {
1381
+ throw new StateExportError(
1382
+ reasonCode,
1383
+ `Unexpected extra argument "${token}". Usage: aura state ${normalizedKind} restore <name> [options]`,
1384
+ );
1385
+ }
1386
+ parsed.name = normalizeStateArtifactName(token, { kind: normalizedKind });
1387
+ }
1388
+
1389
+ if (!parsed.name && !parsed.artifactPath) {
1390
+ throw new StateExportError(
1391
+ reasonCode,
1392
+ `Missing ${normalizedKind} name. Usage: aura state ${normalizedKind} restore <name> [options]`,
1393
+ );
1394
+ }
1395
+ if (parsed.schemaVersion !== GAME_STATE_SCHEMA_VERSION) {
1396
+ throw new StateExportError(
1397
+ 'schema_version_mismatch',
1398
+ `Unsupported schema version "${parsed.schemaVersion}". Expected "${GAME_STATE_SCHEMA_VERSION}".`,
1399
+ );
1400
+ }
1401
+ if (parsed.dryRun && parsed.rollbackOnFail) {
1402
+ throw new StateExportError(reasonCode, '--rollback-on-fail cannot be combined with --dry-run.');
1403
+ }
1404
+ if (!parsed.reportPath && parsed.auditPath === null) {
1405
+ throw new StateExportError(
1406
+ reasonCode,
1407
+ '--audit - cannot be combined with report output to stdout; set --report <file> or --audit <file>.',
1408
+ );
1409
+ }
1410
+
1411
+ return parsed;
1412
+ }
1413
+
1414
+ function parseStateArtifactListArgs(kind, args) {
1415
+ const normalizedKind = kind === 'slot' ? 'slot' : 'checkpoint';
1416
+ const reasonCode = `invalid_state_${normalizedKind}_list_args`;
1417
+ const parsed = {
1418
+ kind: normalizedKind,
1419
+ reportPath: null,
1420
+ compact: false,
1421
+ };
1422
+ let formattingFlag = 'pretty';
1423
+
1424
+ for (let i = 0; i < args.length; i += 1) {
1425
+ const token = args[i];
1426
+
1427
+ if (token === '--report') {
1428
+ parsed.reportPath = normalizeStateCommandOutput(
1429
+ readRequiredOptionValue(args, i, '--report'),
1430
+ reasonCode,
1431
+ );
1432
+ i += 1;
1433
+ continue;
1434
+ }
1435
+ if (token.startsWith('--report=')) {
1436
+ parsed.reportPath = normalizeStateCommandOutput(token.slice('--report='.length), reasonCode);
1437
+ continue;
1438
+ }
1439
+ if (token === '--compact') {
1440
+ if (formattingFlag === 'pretty') {
1441
+ parsed.compact = true;
1442
+ formattingFlag = 'compact';
1443
+ continue;
1444
+ }
1445
+ if (formattingFlag === 'compact') continue;
1446
+ throw new StateExportError(reasonCode, 'Cannot use --compact and --pretty together.');
1447
+ }
1448
+ if (token === '--pretty') {
1449
+ if (formattingFlag === 'compact') {
1450
+ throw new StateExportError(reasonCode, 'Cannot use --compact and --pretty together.');
1451
+ }
1452
+ parsed.compact = false;
1453
+ formattingFlag = 'pretty';
1454
+ continue;
1455
+ }
1456
+ throw new StateExportError(reasonCode, `Unknown ${normalizedKind} list option: ${token}`);
1457
+ }
1458
+
1459
+ return parsed;
1460
+ }
1461
+
1462
+ function normalizeStateCommandInputPath(value, flagName, reasonCode) {
1463
+ const normalized = String(value || '').trim();
1464
+ if (normalized.length === 0) {
1465
+ throw new StateExportError(reasonCode, `${flagName} requires a non-empty file path.`);
1466
+ }
1467
+ return resolve(process.cwd(), normalized);
1468
+ }
1469
+
1470
+ function normalizeStateCommandOutput(value, reasonCode) {
1471
+ const normalized = String(value || '').trim();
1472
+ if (normalized.length === 0) {
1473
+ throw new StateExportError(reasonCode, '--output requires a non-empty path or "-".');
1474
+ }
1475
+ if (normalized === '-') {
1476
+ return null;
1477
+ }
1478
+ return resolve(process.cwd(), normalized);
1479
+ }
1480
+
1481
+ function normalizeStateAuditOutput(value, reasonCode) {
1482
+ const normalized = String(value || '').trim();
1483
+ if (normalized.length === 0) {
1484
+ throw new StateExportError(reasonCode, '--audit requires a non-empty path or "-".');
1485
+ }
1486
+ if (normalized === '-') {
1487
+ return null;
1488
+ }
1489
+ return resolve(process.cwd(), normalized);
1490
+ }
1491
+
1492
+ function normalizeStateArtifactFilePath(value, flagName, reasonCode) {
1493
+ const normalized = String(value || '').trim();
1494
+ if (normalized.length === 0 || normalized === '-') {
1495
+ throw new StateExportError(reasonCode, `${flagName} requires a writable file path.`);
1496
+ }
1497
+ return resolve(process.cwd(), normalized);
1498
+ }
1499
+
1500
+ function normalizeOptionalNote(value) {
1501
+ const normalized = String(value || '').trim();
1502
+ return normalized.length > 0 ? normalized : null;
1503
+ }
1504
+
1505
+ function defaultStatePatchAuditPath(projectRoot) {
1506
+ return resolve(projectRoot, '.aura/state/state-patch-audit.json');
1507
+ }
1508
+
1509
+ function defaultStateApplyAuditPath(projectRoot) {
1510
+ return resolve(projectRoot, '.aura/state/state-apply-audit.json');
1511
+ }
1512
+
1513
+ function readJsonInputFile(filePath, label, reasonCodePrefix) {
1514
+ let text;
1515
+ try {
1516
+ text = readFileSync(filePath, 'utf8');
1517
+ } catch (error) {
1518
+ throw new StateExportError(
1519
+ `${reasonCodePrefix}_read_failed`,
1520
+ `Failed to read ${label} "${filePath}": ${error.message}`,
1521
+ );
1522
+ }
1523
+ try {
1524
+ return JSON.parse(text);
1525
+ } catch (error) {
1526
+ throw new StateExportError(
1527
+ `${reasonCodePrefix}_invalid_json`,
1528
+ `${label} "${filePath}" is not valid JSON: ${error.message}`,
1529
+ );
1530
+ }
1531
+ }
1532
+
1533
+ function writeStateJsonOutput(filePath, jsonText, reasonCode) {
1534
+ if (!filePath) {
1535
+ process.stdout.write(jsonText);
1536
+ return;
1537
+ }
1538
+
1539
+ try {
1540
+ mkdirSync(dirname(filePath), { recursive: true });
1541
+ writeFileSync(filePath, jsonText, 'utf8');
1542
+ } catch (error) {
1543
+ throw new StateExportError(
1544
+ reasonCode,
1545
+ `Failed to write state output "${filePath}": ${error.message}`,
1546
+ );
1547
+ }
1548
+ }
1549
+
1550
+ function writeStateMutationAudit(auditPath, payload) {
1551
+ const text = `${JSON.stringify(payload, null, 2)}\n`;
1552
+ if (auditPath === null) {
1553
+ process.stdout.write(text);
1554
+ return;
1555
+ }
1556
+ try {
1557
+ mkdirSync(dirname(auditPath), { recursive: true });
1558
+ writeFileSync(auditPath, text, 'utf8');
1559
+ } catch (error) {
1560
+ throw new StateExportError(
1561
+ 'state_mutation_audit_write_failed',
1562
+ `Failed to write state mutation audit "${auditPath}": ${error.message}`,
1563
+ );
1564
+ }
1565
+ }
1566
+
1567
+ function jsonByteSize(value) {
1568
+ let text = '';
1569
+ try {
1570
+ text = JSON.stringify(value);
1571
+ } catch {
1572
+ text = '';
1573
+ }
1574
+ return Buffer.byteLength(text || '', 'utf8');
1575
+ }
1576
+
1577
+ function mutationGuardrailsFromParsed(parsed) {
1578
+ return {
1579
+ maxMutations: parsed.maxOps,
1580
+ maxPayloadBytes: parsed.maxBytes,
1581
+ maxRuntimeMs: parsed.maxRuntimeMs,
1582
+ allowlistPrefixes: STATE_MUTATION_ALLOWLIST_PREFIXES,
1583
+ };
1584
+ }
1585
+
1586
+ function readRequiredOptionValue(args, index, flagName) {
1587
+ const value = args[index + 1];
1588
+ if (!value) {
1589
+ throw new StateExportError('invalid_state_export_args', `Missing value for ${flagName}.`);
1020
1590
  }
1021
1591
  return value;
1022
1592
  }
@@ -1199,7 +1769,7 @@ async function cmdInit(args) {
1199
1769
  async function cmdCreate(args) {
1200
1770
  const parsed = parseCreateArgs(args);
1201
1771
  if (!parsed.name) {
1202
- error('Missing project name. Usage: aura create <name> [--template <2d-shooter|3d-platformer|blank>] [--skip-install]');
1772
+ error(`Missing project name. Usage: ${CREATE_USAGE}`);
1203
1773
  }
1204
1774
 
1205
1775
  const name = parsed.name;
@@ -1372,14 +1942,22 @@ async function cmdDevWeb(_args) {
1372
1942
  });
1373
1943
  }
1374
1944
 
1375
- async function cmdDevNative() {
1945
+ async function cmdDevNative(devArgs = {}) {
1376
1946
  const projectRoot = process.cwd();
1377
1947
  const config = await loadConfig({ projectRoot, mode: 'dev' });
1378
1948
  const identity = resolveBuildIdentity(config);
1379
1949
  const hostBinary = resolveAndCacheHostBinary();
1950
+ const devEntry = devArgs.restoreKind
1951
+ ? prepareDevStateRestoreEntry({
1952
+ projectRoot,
1953
+ entryFile: config.build.entry,
1954
+ artifactKind: devArgs.restoreKind,
1955
+ artifactName: devArgs.restoreName,
1956
+ })
1957
+ : null;
1380
1958
  const incremental = createIncrementalBundler({
1381
1959
  projectRoot,
1382
- entryFile: config.build.entry,
1960
+ entryFile: devEntry ? devEntry.entryFile : config.build.entry,
1383
1961
  });
1384
1962
 
1385
1963
  let shuttingDown = false;
@@ -1508,6 +2086,11 @@ async function cmdDevNative() {
1508
2086
  for (const diagnostic of hostBinary.diagnostics || []) {
1509
2087
  console.log(` Host diagnostic: ${diagnostic}`);
1510
2088
  }
2089
+ if (devEntry) {
2090
+ console.log(` Dev restore: ${devEntry.artifact.kind} "${devEntry.artifact.name}"`);
2091
+ console.log(` Dev restore artifact: ${devEntry.artifactPath}`);
2092
+ console.log(` Dev restore wrapper: ${devEntry.wrapperPath}`);
2093
+ }
1511
2094
  startHost(initial.outFile, 'initial');
1512
2095
  console.log('\n Watching src/ for changes... (Ctrl+C to stop)');
1513
2096
 
@@ -1553,12 +2136,19 @@ async function cmdDevNative() {
1553
2136
  }
1554
2137
 
1555
2138
  async function cmdDev(args) {
1556
- const target = parseTarget(args);
2139
+ const parsed = parseDevArgs(args);
2140
+ const target = parsed.target || parseTarget(args);
1557
2141
  if (target === 'web') {
2142
+ if (parsed.restoreKind) {
2143
+ throw new StateExportError(
2144
+ 'invalid_dev_args',
2145
+ '--restore-slot and --restore-checkpoint are only supported for native `aura dev`.',
2146
+ );
2147
+ }
1558
2148
  await cmdDevWeb(args);
1559
2149
  return;
1560
2150
  }
1561
- await cmdDevNative(args);
2151
+ await cmdDevNative(parsed);
1562
2152
  }
1563
2153
 
1564
2154
  async function cmdBuild(args) {
@@ -1805,8 +2395,7 @@ async function cmdTest(args) {
1805
2395
  console.log('');
1806
2396
  }
1807
2397
 
1808
- async function cmdStateExport(args) {
1809
- const parsed = parseStateExportArgs(args);
2398
+ async function runStateExportPayload(parsed) {
1810
2399
  const projectRoot = process.cwd();
1811
2400
  const config = await loadConfig({ projectRoot, mode: 'build' });
1812
2401
  const file = parsed.file || config.build.entry;
@@ -1819,210 +2408,87 @@ async function cmdStateExport(args) {
1819
2408
  frames: parsed.frames,
1820
2409
  mode: parsed.mode,
1821
2410
  frameIndex: parsed.frames,
1822
- });
1823
- } catch (error) {
1824
- if (error instanceof HeadlessTestError) {
1825
- throw new StateExportError(
1826
- 'state_export_runtime_failed',
1827
- error.message,
1828
- );
1829
- }
1830
- throw error;
1831
- }
1832
-
1833
- const exportResult = runResult.exportResult;
1834
- if (!exportResult || typeof exportResult !== 'object') {
1835
- throw new StateExportError(
1836
- 'state_export_runtime_failed',
1837
- 'State export runtime returned an invalid export result.',
1838
- );
1839
- }
1840
- if (exportResult.ok !== true) {
1841
- throw new StateExportError(
1842
- typeof exportResult.reasonCode === 'string' ? exportResult.reasonCode : 'state_export_failed',
1843
- 'State export hook returned a failure result.',
1844
- { exportResult },
1845
- );
1846
- }
1847
- if (!exportResult.payload || typeof exportResult.payload !== 'object') {
1848
- throw new StateExportError(
1849
- 'state_export_invalid_payload',
1850
- 'State export hook returned an invalid payload.',
1851
- { exportResult },
1852
- );
1853
- }
1854
- if (exportResult.payload.schemaVersion !== parsed.schemaVersion) {
1855
- throw new StateExportError(
1856
- 'schema_version_mismatch',
1857
- `Exported schema "${exportResult.payload.schemaVersion}" does not match requested schema "${parsed.schemaVersion}".`,
1858
- { exported: exportResult.payload.schemaVersion, requested: parsed.schemaVersion },
1859
- );
1860
- }
1861
-
1862
- const jsonText = parsed.compact
1863
- ? `${JSON.stringify(exportResult.payload)}\n`
1864
- : `${JSON.stringify(exportResult.payload, null, 2)}\n`;
1865
-
1866
- writeStateJsonOutput(parsed.outputPath, jsonText, 'state_export_output_write_failed');
1867
- }
1868
-
1869
- async function cmdStateDiff(args) {
1870
- const parsed = parseStateDiffArgs(args);
1871
- const beforePayload = readJsonInputFile(parsed.beforePath, 'state diff before payload', 'state_diff_before');
1872
- const afterPayload = readJsonInputFile(parsed.afterPath, 'state diff after payload', 'state_diff_after');
1873
-
1874
- const diffResult = diffCanonicalGameState(beforePayload, afterPayload);
1875
- if (!diffResult.ok) {
1876
- throw new StateExportError(
1877
- typeof diffResult.reasonCode === 'string' ? diffResult.reasonCode : 'state_diff_failed',
1878
- 'State diff operation failed.',
1879
- { diffResult },
1880
- );
1881
- }
1882
- if (!diffResult.patch || typeof diffResult.patch !== 'object') {
1883
- throw new StateExportError('state_diff_failed', 'State diff returned an invalid patch payload.');
1884
- }
1885
- if (diffResult.patch.schemaVersion !== parsed.schemaVersion) {
1886
- throw new StateExportError(
1887
- 'schema_version_mismatch',
1888
- `Diff schema "${diffResult.patch.schemaVersion}" does not match requested schema "${parsed.schemaVersion}".`,
1889
- );
1890
- }
1891
-
1892
- const jsonText = parsed.compact
1893
- ? `${JSON.stringify(diffResult.patch)}\n`
1894
- : `${JSON.stringify(diffResult.patch, null, 2)}\n`;
1895
- writeStateJsonOutput(parsed.outputPath, jsonText, 'state_diff_output_write_failed');
1896
- }
1897
-
1898
- async function cmdStatePatch(args) {
1899
- const projectRoot = process.cwd();
1900
- let parsed = null;
1901
- let auditPath = defaultStatePatchAuditPath(projectRoot);
1902
- let pendingError = null;
1903
- const audit = {
1904
- schemaVersion: STATE_MUTATION_AUDIT_SCHEMA_VERSION,
1905
- command: 'state.patch',
1906
- ok: false,
1907
- reasonCode: 'state_patch_failed',
1908
- limits: {
1909
- maxOps: DEFAULT_STATE_MUTATION_GUARDRAILS.maxMutations,
1910
- maxBytes: DEFAULT_STATE_MUTATION_GUARDRAILS.maxPayloadBytes,
1911
- maxRuntimeMs: DEFAULT_STATE_MUTATION_GUARDRAILS.maxRuntimeMs,
1912
- },
1913
- allowlistPrefixes: [...STATE_MUTATION_ALLOWLIST_PREFIXES],
1914
- io: {
1915
- statePath: null,
1916
- patchPath: null,
1917
- outputPath: '-',
1918
- auditPath: auditPath,
1919
- },
1920
- metrics: {
1921
- statePayloadBytes: 0,
1922
- patchPayloadBytes: 0,
1923
- outputPayloadBytes: 0,
1924
- mutationCount: 0,
1925
- appliedMutations: 0,
1926
- },
1927
- detail: null,
1928
- };
1929
-
1930
- try {
1931
- parsed = parseStatePatchArgs(args);
1932
- auditPath = parsed.auditPath;
1933
- audit.io.statePath = parsed.statePath;
1934
- audit.io.patchPath = parsed.patchPath;
1935
- audit.io.outputPath = parsed.outputPath || '-';
1936
- audit.io.auditPath = parsed.auditPath || '-';
1937
- audit.limits = {
1938
- maxOps: parsed.maxOps,
1939
- maxBytes: parsed.maxBytes,
1940
- maxRuntimeMs: parsed.maxRuntimeMs,
1941
- };
1942
-
1943
- const statePayload = readJsonInputFile(parsed.statePath, 'state payload', 'state_patch_state');
1944
- const patchPayload = readJsonInputFile(parsed.patchPath, 'state patch payload', 'state_patch_patch');
1945
- audit.metrics.statePayloadBytes = jsonByteSize(statePayload);
1946
- audit.metrics.patchPayloadBytes = jsonByteSize(patchPayload);
1947
- audit.metrics.mutationCount = Array.isArray(patchPayload?.mutations) ? patchPayload.mutations.length : 0;
1948
-
1949
- const patchResult = applyCanonicalStatePatch(
1950
- statePayload,
1951
- patchPayload,
1952
- mutationGuardrailsFromParsed(parsed),
1953
- );
1954
- if (!patchResult.ok) {
1955
- throw new StateExportError(
1956
- typeof patchResult.reasonCode === 'string' ? patchResult.reasonCode : 'state_patch_failed',
1957
- 'State patch operation failed.',
1958
- { patchResult },
1959
- );
1960
- }
1961
- if (!patchResult.payload || typeof patchResult.payload !== 'object') {
1962
- throw new StateExportError('state_patch_failed', 'State patch returned an invalid payload.');
1963
- }
1964
- if (patchResult.payload.schemaVersion !== parsed.schemaVersion) {
1965
- throw new StateExportError(
1966
- 'schema_version_mismatch',
1967
- `Patched schema "${patchResult.payload.schemaVersion}" does not match requested schema "${parsed.schemaVersion}".`,
1968
- );
1969
- }
1970
-
1971
- const jsonText = parsed.compact
1972
- ? `${JSON.stringify(patchResult.payload)}\n`
1973
- : `${JSON.stringify(patchResult.payload, null, 2)}\n`;
1974
- writeStateJsonOutput(parsed.outputPath, jsonText, 'state_patch_output_write_failed');
1975
-
1976
- audit.ok = true;
1977
- audit.reasonCode = 'state_patch_ok';
1978
- audit.metrics.outputPayloadBytes = jsonByteSize(patchResult.payload);
1979
- audit.metrics.appliedMutations = Number.isInteger(patchResult.appliedMutations) ? patchResult.appliedMutations : 0;
2411
+ });
1980
2412
  } catch (error) {
1981
- pendingError = error;
1982
- audit.ok = false;
1983
- audit.reasonCode = error instanceof StateExportError
1984
- ? error.reasonCode
1985
- : 'state_patch_failed';
1986
- audit.detail = error instanceof Error ? error.message : String(error);
2413
+ if (error instanceof HeadlessTestError) {
2414
+ throw new StateExportError('state_export_runtime_failed', error.message);
2415
+ }
2416
+ throw error;
1987
2417
  }
1988
2418
 
1989
- writeStateMutationAudit(auditPath, audit);
1990
- if (pendingError) {
1991
- throw pendingError;
2419
+ const exportResult = runResult.exportResult;
2420
+ if (!exportResult || typeof exportResult !== 'object') {
2421
+ throw new StateExportError(
2422
+ 'state_export_runtime_failed',
2423
+ 'State export runtime returned an invalid export result.',
2424
+ );
2425
+ }
2426
+ if (exportResult.ok !== true) {
2427
+ throw new StateExportError(
2428
+ typeof exportResult.reasonCode === 'string' ? exportResult.reasonCode : 'state_export_failed',
2429
+ 'State export hook returned a failure result.',
2430
+ { exportResult },
2431
+ );
2432
+ }
2433
+ if (!exportResult.payload || typeof exportResult.payload !== 'object') {
2434
+ throw new StateExportError(
2435
+ 'state_export_invalid_payload',
2436
+ 'State export hook returned an invalid payload.',
2437
+ { exportResult },
2438
+ );
2439
+ }
2440
+ if (exportResult.payload.schemaVersion !== parsed.schemaVersion) {
2441
+ throw new StateExportError(
2442
+ 'schema_version_mismatch',
2443
+ `Exported schema "${exportResult.payload.schemaVersion}" does not match requested schema "${parsed.schemaVersion}".`,
2444
+ { exported: exportResult.payload.schemaVersion, requested: parsed.schemaVersion },
2445
+ );
1992
2446
  }
2447
+
2448
+ return {
2449
+ projectRoot,
2450
+ file,
2451
+ payload: exportResult.payload,
2452
+ };
1993
2453
  }
1994
2454
 
1995
- async function cmdStateApply(args) {
1996
- const projectRoot = process.cwd();
1997
- let parsed = null;
1998
- let auditPath = defaultStateApplyAuditPath(projectRoot);
1999
- let pendingError = null;
2455
+ async function executeStateApplyOperation({
2456
+ parsed,
2457
+ payload,
2458
+ projectRoot = process.cwd(),
2459
+ file,
2460
+ outputPath = null,
2461
+ auditPath = defaultStateApplyAuditPath(projectRoot),
2462
+ inputPathLabel = null,
2463
+ commandName = 'state.apply',
2464
+ artifactMetadata = null,
2465
+ }) {
2000
2466
  const audit = {
2001
2467
  schemaVersion: STATE_MUTATION_AUDIT_SCHEMA_VERSION,
2002
- command: 'state.apply',
2468
+ command: commandName,
2003
2469
  ok: false,
2004
2470
  reasonCode: 'state_apply_failed',
2005
2471
  limits: {
2006
- maxOps: DEFAULT_STATE_MUTATION_GUARDRAILS.maxMutations,
2007
- maxBytes: DEFAULT_STATE_MUTATION_GUARDRAILS.maxPayloadBytes,
2008
- maxRuntimeMs: DEFAULT_STATE_MUTATION_GUARDRAILS.maxRuntimeMs,
2472
+ maxOps: parsed.maxOps,
2473
+ maxBytes: parsed.maxBytes,
2474
+ maxRuntimeMs: parsed.maxRuntimeMs,
2009
2475
  },
2010
2476
  allowlistPrefixes: [...STATE_MUTATION_ALLOWLIST_PREFIXES],
2011
2477
  io: {
2012
- statePath: null,
2013
- file: null,
2014
- outputPath: '-',
2015
- auditPath: auditPath,
2478
+ statePath: inputPathLabel || '-',
2479
+ file,
2480
+ outputPath: outputPath || '-',
2481
+ auditPath: auditPath || '-',
2016
2482
  },
2017
2483
  metrics: {
2018
- payloadBytes: 0,
2484
+ payloadBytes: jsonByteSize(payload),
2019
2485
  outputPayloadBytes: 0,
2020
2486
  mutationCount: 0,
2021
2487
  },
2022
2488
  result: {
2023
- dryRun: false,
2024
- verify: false,
2025
- rollbackOnFail: false,
2489
+ dryRun: parsed.dryRun === true,
2490
+ verify: parsed.verify === true,
2491
+ rollbackOnFail: parsed.rollbackOnFail === true,
2026
2492
  applied: false,
2027
2493
  verified: false,
2028
2494
  rolledBack: false,
@@ -2033,27 +2499,16 @@ async function cmdStateApply(args) {
2033
2499
  detail: null,
2034
2500
  };
2035
2501
 
2036
- try {
2037
- parsed = parseStateApplyArgs(args);
2038
- auditPath = parsed.auditPath;
2039
- audit.io.statePath = parsed.statePath;
2040
- audit.io.outputPath = parsed.outputPath || '-';
2041
- audit.io.auditPath = parsed.auditPath || '-';
2042
- audit.limits = {
2043
- maxOps: parsed.maxOps,
2044
- maxBytes: parsed.maxBytes,
2045
- maxRuntimeMs: parsed.maxRuntimeMs,
2502
+ if (artifactMetadata) {
2503
+ audit.artifact = {
2504
+ kind: artifactMetadata.kind,
2505
+ name: artifactMetadata.name,
2506
+ path: artifactMetadata.path,
2507
+ payloadFingerprint: artifactMetadata.payloadFingerprint || null,
2046
2508
  };
2047
- audit.result.dryRun = parsed.dryRun === true;
2048
- audit.result.verify = parsed.verify === true;
2049
- audit.result.rollbackOnFail = parsed.rollbackOnFail === true;
2050
-
2051
- const config = await loadConfig({ projectRoot, mode: 'build' });
2052
- const file = parsed.file || config.build.entry;
2053
- audit.io.file = file;
2054
- const payload = readJsonInputFile(parsed.statePath, 'state apply payload', 'state_apply_input');
2055
- audit.metrics.payloadBytes = jsonByteSize(payload);
2509
+ }
2056
2510
 
2511
+ try {
2057
2512
  if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
2058
2513
  throw new StateExportError('invalid_schema_payload', 'State apply payload must be a JSON object.');
2059
2514
  }
@@ -2088,6 +2543,7 @@ async function cmdStateApply(args) {
2088
2543
  if (!runResult || typeof runResult !== 'object') {
2089
2544
  throw new StateExportError('state_apply_runtime_failed', 'State apply runtime returned an invalid result.');
2090
2545
  }
2546
+
2091
2547
  if (runResult.ok !== true) {
2092
2548
  const failureReport = {
2093
2549
  ok: false,
@@ -2103,12 +2559,16 @@ async function cmdStateApply(args) {
2103
2559
  baselineFingerprint: runResult.baselineFingerprint || null,
2104
2560
  finalFingerprint: runResult.finalFingerprint || null,
2105
2561
  };
2106
- if (parsed.outputPath) {
2562
+ if (artifactMetadata) {
2563
+ failureReport.artifact = artifactMetadata;
2564
+ }
2565
+ if (outputPath) {
2107
2566
  const failureText = parsed.compact
2108
2567
  ? `${JSON.stringify(failureReport)}\n`
2109
2568
  : `${JSON.stringify(failureReport, null, 2)}\n`;
2110
- writeStateJsonOutput(parsed.outputPath, failureText, 'state_apply_output_write_failed');
2569
+ writeStateJsonOutput(outputPath, failureText, 'state_apply_output_write_failed');
2111
2570
  }
2571
+
2112
2572
  audit.ok = false;
2113
2573
  audit.reasonCode = failureReport.reasonCode;
2114
2574
  audit.metrics.mutationCount = failureReport.mutationCount;
@@ -2123,6 +2583,7 @@ async function cmdStateApply(args) {
2123
2583
  baselineFingerprint: failureReport.baselineFingerprint,
2124
2584
  finalFingerprint: failureReport.finalFingerprint,
2125
2585
  };
2586
+
2126
2587
  throw new StateExportError(
2127
2588
  failureReport.reasonCode,
2128
2589
  'State apply operation failed.',
@@ -2143,15 +2604,17 @@ async function cmdStateApply(args) {
2143
2604
  baselineFingerprint: runResult.baselineFingerprint || null,
2144
2605
  finalFingerprint: runResult.finalFingerprint || null,
2145
2606
  };
2146
-
2147
2607
  if (runResult.previewPatch && typeof runResult.previewPatch === 'object') {
2148
2608
  report.previewPatch = runResult.previewPatch;
2149
2609
  }
2610
+ if (artifactMetadata) {
2611
+ report.artifact = artifactMetadata;
2612
+ }
2150
2613
 
2151
2614
  const jsonText = parsed.compact
2152
2615
  ? `${JSON.stringify(report)}\n`
2153
2616
  : `${JSON.stringify(report, null, 2)}\n`;
2154
- writeStateJsonOutput(parsed.outputPath, jsonText, 'state_apply_output_write_failed');
2617
+ writeStateJsonOutput(outputPath, jsonText, 'state_apply_output_write_failed');
2155
2618
 
2156
2619
  audit.ok = true;
2157
2620
  audit.reasonCode = report.reasonCode;
@@ -2168,12 +2631,144 @@ async function cmdStateApply(args) {
2168
2631
  baselineFingerprint: report.baselineFingerprint,
2169
2632
  finalFingerprint: report.finalFingerprint,
2170
2633
  };
2634
+
2635
+ return report;
2171
2636
  } catch (error) {
2172
- pendingError = error;
2173
- if (error instanceof StateExportError) {
2174
- audit.reasonCode = error.reasonCode;
2637
+ audit.reasonCode = error instanceof StateExportError ? error.reasonCode : 'state_apply_failed';
2638
+ audit.detail = error instanceof Error ? error.message : String(error);
2639
+ throw error;
2640
+ } finally {
2641
+ writeStateMutationAudit(auditPath, audit);
2642
+ }
2643
+ }
2644
+
2645
+ async function cmdStateExport(args) {
2646
+ const parsed = parseStateExportArgs(args);
2647
+ const { payload } = await runStateExportPayload(parsed);
2648
+ const jsonText = parsed.compact
2649
+ ? `${JSON.stringify(payload)}\n`
2650
+ : `${JSON.stringify(payload, null, 2)}\n`;
2651
+
2652
+ writeStateJsonOutput(parsed.outputPath, jsonText, 'state_export_output_write_failed');
2653
+ }
2654
+
2655
+ async function cmdStateDiff(args) {
2656
+ const parsed = parseStateDiffArgs(args);
2657
+ const beforePayload = readJsonInputFile(parsed.beforePath, 'state diff before payload', 'state_diff_before');
2658
+ const afterPayload = readJsonInputFile(parsed.afterPath, 'state diff after payload', 'state_diff_after');
2659
+
2660
+ const diffResult = diffCanonicalGameState(beforePayload, afterPayload);
2661
+ if (!diffResult.ok) {
2662
+ throw new StateExportError(
2663
+ typeof diffResult.reasonCode === 'string' ? diffResult.reasonCode : 'state_diff_failed',
2664
+ 'State diff operation failed.',
2665
+ { diffResult },
2666
+ );
2667
+ }
2668
+ if (!diffResult.patch || typeof diffResult.patch !== 'object') {
2669
+ throw new StateExportError('state_diff_failed', 'State diff returned an invalid patch payload.');
2670
+ }
2671
+ if (diffResult.patch.schemaVersion !== parsed.schemaVersion) {
2672
+ throw new StateExportError(
2673
+ 'schema_version_mismatch',
2674
+ `Diff schema "${diffResult.patch.schemaVersion}" does not match requested schema "${parsed.schemaVersion}".`,
2675
+ );
2676
+ }
2677
+
2678
+ const jsonText = parsed.compact
2679
+ ? `${JSON.stringify(diffResult.patch)}\n`
2680
+ : `${JSON.stringify(diffResult.patch, null, 2)}\n`;
2681
+ writeStateJsonOutput(parsed.outputPath, jsonText, 'state_diff_output_write_failed');
2682
+ }
2683
+
2684
+ async function cmdStatePatch(args) {
2685
+ const projectRoot = process.cwd();
2686
+ let parsed = null;
2687
+ let auditPath = defaultStatePatchAuditPath(projectRoot);
2688
+ let pendingError = null;
2689
+ const audit = {
2690
+ schemaVersion: STATE_MUTATION_AUDIT_SCHEMA_VERSION,
2691
+ command: 'state.patch',
2692
+ ok: false,
2693
+ reasonCode: 'state_patch_failed',
2694
+ limits: {
2695
+ maxOps: DEFAULT_STATE_MUTATION_GUARDRAILS.maxMutations,
2696
+ maxBytes: DEFAULT_STATE_MUTATION_GUARDRAILS.maxPayloadBytes,
2697
+ maxRuntimeMs: DEFAULT_STATE_MUTATION_GUARDRAILS.maxRuntimeMs,
2698
+ },
2699
+ allowlistPrefixes: [...STATE_MUTATION_ALLOWLIST_PREFIXES],
2700
+ io: {
2701
+ statePath: null,
2702
+ patchPath: null,
2703
+ outputPath: '-',
2704
+ auditPath: auditPath,
2705
+ },
2706
+ metrics: {
2707
+ statePayloadBytes: 0,
2708
+ patchPayloadBytes: 0,
2709
+ outputPayloadBytes: 0,
2710
+ mutationCount: 0,
2711
+ appliedMutations: 0,
2712
+ },
2713
+ detail: null,
2714
+ };
2715
+
2716
+ try {
2717
+ parsed = parseStatePatchArgs(args);
2718
+ auditPath = parsed.auditPath;
2719
+ audit.io.statePath = parsed.statePath;
2720
+ audit.io.patchPath = parsed.patchPath;
2721
+ audit.io.outputPath = parsed.outputPath || '-';
2722
+ audit.io.auditPath = parsed.auditPath || '-';
2723
+ audit.limits = {
2724
+ maxOps: parsed.maxOps,
2725
+ maxBytes: parsed.maxBytes,
2726
+ maxRuntimeMs: parsed.maxRuntimeMs,
2727
+ };
2728
+
2729
+ const statePayload = readJsonInputFile(parsed.statePath, 'state payload', 'state_patch_state');
2730
+ const patchPayload = readJsonInputFile(parsed.patchPath, 'state patch payload', 'state_patch_patch');
2731
+ audit.metrics.statePayloadBytes = jsonByteSize(statePayload);
2732
+ audit.metrics.patchPayloadBytes = jsonByteSize(patchPayload);
2733
+ audit.metrics.mutationCount = Array.isArray(patchPayload?.mutations) ? patchPayload.mutations.length : 0;
2734
+
2735
+ const patchResult = applyCanonicalStatePatch(
2736
+ statePayload,
2737
+ patchPayload,
2738
+ mutationGuardrailsFromParsed(parsed),
2739
+ );
2740
+ if (!patchResult.ok) {
2741
+ throw new StateExportError(
2742
+ typeof patchResult.reasonCode === 'string' ? patchResult.reasonCode : 'state_patch_failed',
2743
+ 'State patch operation failed.',
2744
+ { patchResult },
2745
+ );
2746
+ }
2747
+ if (!patchResult.payload || typeof patchResult.payload !== 'object') {
2748
+ throw new StateExportError('state_patch_failed', 'State patch returned an invalid payload.');
2749
+ }
2750
+ if (patchResult.payload.schemaVersion !== parsed.schemaVersion) {
2751
+ throw new StateExportError(
2752
+ 'schema_version_mismatch',
2753
+ `Patched schema "${patchResult.payload.schemaVersion}" does not match requested schema "${parsed.schemaVersion}".`,
2754
+ );
2175
2755
  }
2756
+
2757
+ const jsonText = parsed.compact
2758
+ ? `${JSON.stringify(patchResult.payload)}\n`
2759
+ : `${JSON.stringify(patchResult.payload, null, 2)}\n`;
2760
+ writeStateJsonOutput(parsed.outputPath, jsonText, 'state_patch_output_write_failed');
2761
+
2762
+ audit.ok = true;
2763
+ audit.reasonCode = 'state_patch_ok';
2764
+ audit.metrics.outputPayloadBytes = jsonByteSize(patchResult.payload);
2765
+ audit.metrics.appliedMutations = Number.isInteger(patchResult.appliedMutations) ? patchResult.appliedMutations : 0;
2766
+ } catch (error) {
2767
+ pendingError = error;
2176
2768
  audit.ok = false;
2769
+ audit.reasonCode = error instanceof StateExportError
2770
+ ? error.reasonCode
2771
+ : 'state_patch_failed';
2177
2772
  audit.detail = error instanceof Error ? error.message : String(error);
2178
2773
  }
2179
2774
 
@@ -2183,12 +2778,115 @@ async function cmdStateApply(args) {
2183
2778
  }
2184
2779
  }
2185
2780
 
2781
+ async function cmdStateApply(args) {
2782
+ const projectRoot = process.cwd();
2783
+ const parsed = parseStateApplyArgs(args);
2784
+ const config = await loadConfig({ projectRoot, mode: 'build' });
2785
+ const file = parsed.file || config.build.entry;
2786
+ const payload = readJsonInputFile(parsed.statePath, 'state apply payload', 'state_apply_input');
2787
+
2788
+ await executeStateApplyOperation({
2789
+ parsed,
2790
+ payload,
2791
+ projectRoot,
2792
+ file,
2793
+ outputPath: parsed.outputPath,
2794
+ auditPath: parsed.auditPath,
2795
+ inputPathLabel: parsed.statePath,
2796
+ commandName: 'state.apply',
2797
+ });
2798
+ }
2799
+
2800
+ async function cmdStateArtifactSave(kind, args) {
2801
+ const parsed = parseStateArtifactSaveArgs(kind, args);
2802
+ const { projectRoot, file, payload } = await runStateExportPayload(parsed);
2803
+ const artifactPath = parsed.artifactPath || resolveStateArtifactPath(projectRoot, parsed.kind, parsed.name);
2804
+ const envelope = createStateArtifactEnvelope({
2805
+ kind: parsed.kind,
2806
+ name: parsed.name,
2807
+ payload,
2808
+ note: parsed.note,
2809
+ source: {
2810
+ mode: parsed.mode,
2811
+ frames: parsed.frames,
2812
+ file,
2813
+ reportPath: parsed.reportPath || '-',
2814
+ },
2815
+ });
2816
+ writeStateArtifactEnvelope(artifactPath, envelope);
2817
+
2818
+ const report = {
2819
+ ok: true,
2820
+ reasonCode: parsed.kind === 'slot' ? 'state_slot_saved' : 'state_checkpoint_saved',
2821
+ artifact: {
2822
+ kind: parsed.kind,
2823
+ name: parsed.name,
2824
+ path: artifactPath,
2825
+ createdAt: envelope.createdAt,
2826
+ note: envelope.note || null,
2827
+ stateSchemaVersion: envelope.stateSchemaVersion,
2828
+ payloadFingerprint: envelope.payloadFingerprint,
2829
+ mode: payload?.export?.mode || null,
2830
+ frameIndex: payload?.export?.frameIndex ?? null,
2831
+ elapsedSeconds: payload?.export?.elapsedSeconds ?? null,
2832
+ },
2833
+ };
2834
+
2835
+ const jsonText = parsed.compact
2836
+ ? `${JSON.stringify(report)}\n`
2837
+ : `${JSON.stringify(report, null, 2)}\n`;
2838
+ writeStateJsonOutput(parsed.reportPath, jsonText, 'state_artifact_report_write_failed');
2839
+ }
2840
+
2841
+ async function cmdStateArtifactRestore(kind, args) {
2842
+ const projectRoot = process.cwd();
2843
+ const parsed = parseStateArtifactRestoreArgs(kind, args);
2844
+ const artifact = loadStateArtifact({
2845
+ projectRoot,
2846
+ kind: parsed.kind,
2847
+ name: parsed.name,
2848
+ artifactPath: parsed.artifactPath,
2849
+ });
2850
+ const config = await loadConfig({ projectRoot, mode: 'build' });
2851
+ const file = parsed.file || config.build.entry;
2852
+
2853
+ await executeStateApplyOperation({
2854
+ parsed,
2855
+ payload: artifact.envelope.payload,
2856
+ projectRoot,
2857
+ file,
2858
+ outputPath: parsed.reportPath,
2859
+ auditPath: parsed.auditPath,
2860
+ inputPathLabel: artifact.filePath,
2861
+ commandName: parsed.kind === 'slot' ? 'state.slot.restore' : 'state.checkpoint.restore',
2862
+ artifactMetadata: artifact.metadata,
2863
+ });
2864
+ }
2865
+
2866
+ async function cmdStateArtifactList(kind, args) {
2867
+ const projectRoot = process.cwd();
2868
+ const parsed = parseStateArtifactListArgs(kind, args);
2869
+ const listing = listStateArtifacts(projectRoot, parsed.kind);
2870
+ const report = {
2871
+ ok: true,
2872
+ reasonCode: parsed.kind === 'slot' ? 'state_slot_list_ok' : 'state_checkpoint_list_ok',
2873
+ kind: parsed.kind,
2874
+ directory: listing.directory,
2875
+ artifacts: listing.artifacts,
2876
+ invalidArtifacts: listing.invalidArtifacts,
2877
+ };
2878
+ const jsonText = parsed.compact
2879
+ ? `${JSON.stringify(report)}\n`
2880
+ : `${JSON.stringify(report, null, 2)}\n`;
2881
+ writeStateJsonOutput(parsed.reportPath, jsonText, 'state_artifact_report_write_failed');
2882
+ }
2883
+
2186
2884
  async function cmdState(args) {
2187
2885
  const subcommand = args[0];
2188
2886
  if (!subcommand) {
2189
2887
  throw new StateExportError(
2190
2888
  'state_subcommand_required',
2191
- 'Missing state subcommand. Usage: aura state <export|diff|patch> [options]',
2889
+ 'Missing state subcommand. Usage: aura state <export|diff|patch|apply|slot|checkpoint> [options]',
2192
2890
  );
2193
2891
  }
2194
2892
 
@@ -2212,9 +2910,35 @@ async function cmdState(args) {
2212
2910
  return;
2213
2911
  }
2214
2912
 
2913
+ if (subcommand === 'slot' || subcommand === 'checkpoint') {
2914
+ const artifactAction = args[1];
2915
+ if (!artifactAction) {
2916
+ throw new StateExportError(
2917
+ 'state_subcommand_unsupported',
2918
+ `Missing ${subcommand} action. Supported actions: save, restore, list.`,
2919
+ );
2920
+ }
2921
+ if (artifactAction === 'save') {
2922
+ await cmdStateArtifactSave(subcommand, args.slice(2));
2923
+ return;
2924
+ }
2925
+ if (artifactAction === 'restore') {
2926
+ await cmdStateArtifactRestore(subcommand, args.slice(2));
2927
+ return;
2928
+ }
2929
+ if (artifactAction === 'list') {
2930
+ await cmdStateArtifactList(subcommand, args.slice(2));
2931
+ return;
2932
+ }
2933
+ throw new StateExportError(
2934
+ 'state_subcommand_unsupported',
2935
+ `Unknown ${subcommand} action "${artifactAction}". Supported actions: save, restore, list.`,
2936
+ );
2937
+ }
2938
+
2215
2939
  throw new StateExportError(
2216
2940
  'state_subcommand_unsupported',
2217
- `Unknown state subcommand "${subcommand}". Supported subcommands: export, diff, patch, apply.`,
2941
+ `Unknown state subcommand "${subcommand}". Supported subcommands: export, diff, patch, apply, slot, checkpoint.`,
2218
2942
  );
2219
2943
  }
2220
2944
 
@@ -2280,6 +3004,9 @@ async function main() {
2280
3004
  if (err instanceof StateExportError) {
2281
3005
  error(`${err.message} (reasonCode=${err.reasonCode})`, 6);
2282
3006
  }
3007
+ if (err instanceof StateArtifactError) {
3008
+ error(`${err.message} (reasonCode=${err.reasonCode})`, 6);
3009
+ }
2283
3010
  if (err instanceof HeadlessTestError) {
2284
3011
  if (Array.isArray(err.details?.failures) && err.details.failures.length > 0) {
2285
3012
  console.error('\n aura test failures:');