nexarch 0.9.3 → 0.9.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/init-agent.js +31 -18
- package/dist/commands/init-project.js +132 -23
- package/dist/index.js +2 -0
- package/package.json +1 -1
|
@@ -59,12 +59,12 @@ async function promptForValue(promptText, required = false) {
|
|
|
59
59
|
rl.close();
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
|
-
async function confirmInstructionWrite() {
|
|
62
|
+
async function confirmInstructionWrite(promptText = "Allow nexarch init-agent to write/update AGENTS.md/CLAUDE.md registration instructions? [y/N]: ") {
|
|
63
63
|
if (!process.stdin.isTTY || !process.stdout.isTTY)
|
|
64
64
|
return false;
|
|
65
65
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
66
66
|
try {
|
|
67
|
-
const answer = (await rl.question(
|
|
67
|
+
const answer = (await rl.question(promptText)).trim().toLowerCase();
|
|
68
68
|
return answer === "y" || answer === "yes";
|
|
69
69
|
}
|
|
70
70
|
finally {
|
|
@@ -1084,20 +1084,25 @@ export async function initAgent(args) {
|
|
|
1084
1084
|
catch {
|
|
1085
1085
|
// non-fatal
|
|
1086
1086
|
}
|
|
1087
|
+
let existingInstructionTargets = injectAgentConfigs(registry);
|
|
1088
|
+
if (existingInstructionTargets.length === 0) {
|
|
1089
|
+
existingInstructionTargets = injectGenericAgentConfig(registry);
|
|
1090
|
+
}
|
|
1091
|
+
const alreadyConfigured = existingInstructionTargets.length > 0 && existingInstructionTargets.every((r) => r.status === "already_present");
|
|
1087
1092
|
if (denyInstructionWriteFlag) {
|
|
1088
1093
|
instructionsWriteAllowed = false;
|
|
1089
1094
|
}
|
|
1090
1095
|
else if (allowInstructionWriteFlag) {
|
|
1091
1096
|
instructionsWriteAllowed = true;
|
|
1092
1097
|
}
|
|
1098
|
+
else if (alreadyConfigured) {
|
|
1099
|
+
instructionsWriteAllowed = false;
|
|
1100
|
+
}
|
|
1093
1101
|
else if (!asJson) {
|
|
1094
1102
|
instructionsWriteAllowed = await confirmInstructionWrite();
|
|
1095
1103
|
}
|
|
1096
1104
|
if (instructionsWriteAllowed) {
|
|
1097
|
-
agentConfigResults =
|
|
1098
|
-
if (agentConfigResults.length === 0) {
|
|
1099
|
-
agentConfigResults = injectGenericAgentConfig(registry);
|
|
1100
|
-
}
|
|
1105
|
+
agentConfigResults = existingInstructionTargets;
|
|
1101
1106
|
if (agentConfigResults.length > 0) {
|
|
1102
1107
|
trustAttestationAttempted = true;
|
|
1103
1108
|
trustAttestation = await requestTrustAttestation(agentId);
|
|
@@ -1116,6 +1121,9 @@ export async function initAgent(args) {
|
|
|
1116
1121
|
}
|
|
1117
1122
|
}
|
|
1118
1123
|
}
|
|
1124
|
+
else if (alreadyConfigured) {
|
|
1125
|
+
agentConfigResults = existingInstructionTargets;
|
|
1126
|
+
}
|
|
1119
1127
|
}
|
|
1120
1128
|
checks.push({
|
|
1121
1129
|
name: "agent.registration",
|
|
@@ -1132,29 +1140,34 @@ export async function initAgent(args) {
|
|
|
1132
1140
|
? `awaiting identity input (${identityCapture.missingRequired.join(", ")})`
|
|
1133
1141
|
: identityCapture.detail,
|
|
1134
1142
|
});
|
|
1143
|
+
const instructionsAlreadyConfigured = agentConfigResults.length > 0 && agentConfigResults.every((r) => r.status === "already_present");
|
|
1135
1144
|
checks.push({
|
|
1136
1145
|
name: "agent.instructions.injection",
|
|
1137
1146
|
ok: !registration.ok || !instructionsWriteAllowed || agentConfigResults.length > 0,
|
|
1138
1147
|
detail: !registration.ok
|
|
1139
1148
|
? "skipped (registration failed)"
|
|
1140
|
-
:
|
|
1141
|
-
? "
|
|
1142
|
-
:
|
|
1143
|
-
?
|
|
1144
|
-
:
|
|
1149
|
+
: instructionsAlreadyConfigured
|
|
1150
|
+
? "already configured (no write needed)"
|
|
1151
|
+
: !instructionsWriteAllowed
|
|
1152
|
+
? "skipped (consent not granted)"
|
|
1153
|
+
: agentConfigResults.length > 0
|
|
1154
|
+
? `updated ${agentConfigResults.length} instruction target file(s)`
|
|
1155
|
+
: "no runtime instruction target matched this repository (non-fatal; create AGENTS.md/CLAUDE.md or configure a generic target)",
|
|
1145
1156
|
});
|
|
1146
1157
|
checks.push({
|
|
1147
1158
|
name: "agent.trust.attestation",
|
|
1148
1159
|
ok: !registration.ok || !instructionsWriteAllowed || !trustAttestationAttempted || Boolean(trustAttestation?.ok),
|
|
1149
1160
|
detail: !registration.ok
|
|
1150
1161
|
? "skipped (registration failed)"
|
|
1151
|
-
: !instructionsWriteAllowed
|
|
1152
|
-
? "skipped (
|
|
1153
|
-
: !
|
|
1154
|
-
? "skipped (
|
|
1155
|
-
:
|
|
1156
|
-
? "
|
|
1157
|
-
:
|
|
1162
|
+
: instructionsAlreadyConfigured && !instructionsWriteAllowed
|
|
1163
|
+
? "skipped (already configured)"
|
|
1164
|
+
: !instructionsWriteAllowed
|
|
1165
|
+
? "skipped (consent not granted)"
|
|
1166
|
+
: !trustAttestationAttempted
|
|
1167
|
+
? "skipped (no instruction target written)"
|
|
1168
|
+
: trustAttestation?.ok
|
|
1169
|
+
? "minted and injected into instruction file(s)"
|
|
1170
|
+
: `unavailable (${trustAttestation?.reason ?? "unknown"})`,
|
|
1158
1171
|
});
|
|
1159
1172
|
checks.push({
|
|
1160
1173
|
name: "technology.components",
|
|
@@ -28,6 +28,14 @@ function formatMs(ms) {
|
|
|
28
28
|
return `${ms}ms`;
|
|
29
29
|
return `${(ms / 1000).toFixed(2)}s`;
|
|
30
30
|
}
|
|
31
|
+
function chunkArray(items, size) {
|
|
32
|
+
if (size <= 0)
|
|
33
|
+
return [items];
|
|
34
|
+
const chunks = [];
|
|
35
|
+
for (let i = 0; i < items.length; i += size)
|
|
36
|
+
chunks.push(items.slice(i, i + size));
|
|
37
|
+
return chunks;
|
|
38
|
+
}
|
|
31
39
|
function slugify(name) {
|
|
32
40
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
33
41
|
}
|
|
@@ -956,12 +964,34 @@ async function promptApplicationChoice(matches, allApps, suggested) {
|
|
|
956
964
|
export async function initProject(args) {
|
|
957
965
|
const asJson = parseFlag(args, "--json");
|
|
958
966
|
const dryRun = parseFlag(args, "--dry-run");
|
|
967
|
+
const profileEnabled = parseFlag(args, "--profile");
|
|
959
968
|
const commandStartedAt = Date.now();
|
|
969
|
+
const profile = {
|
|
970
|
+
enabled: profileEnabled,
|
|
971
|
+
startedAt: new Date(commandStartedAt).toISOString(),
|
|
972
|
+
events: [],
|
|
973
|
+
mcpCalls: [],
|
|
974
|
+
upsertBatches: {
|
|
975
|
+
entities: [],
|
|
976
|
+
relationships: [],
|
|
977
|
+
},
|
|
978
|
+
};
|
|
960
979
|
const logProgress = (phase, details) => {
|
|
961
|
-
const
|
|
980
|
+
const atMs = Date.now() - commandStartedAt;
|
|
981
|
+
if (profileEnabled)
|
|
982
|
+
profile.events.push({ phase, atMs, details });
|
|
983
|
+
const elapsed = formatMs(atMs);
|
|
962
984
|
const line = details ? `[init-project +${elapsed}] ${phase} — ${details}` : `[init-project +${elapsed}] ${phase}`;
|
|
963
985
|
process.stderr.write(`${line}\n`);
|
|
964
986
|
};
|
|
987
|
+
const callMcpProfiled = async (toolName, input, meta) => {
|
|
988
|
+
const startedAt = Date.now();
|
|
989
|
+
const result = await callMcpTool(toolName, input, mcpOpts);
|
|
990
|
+
if (profileEnabled) {
|
|
991
|
+
profile.mcpCalls.push({ tool: toolName, durationMs: Date.now() - startedAt, meta });
|
|
992
|
+
}
|
|
993
|
+
return result;
|
|
994
|
+
};
|
|
965
995
|
const dirArg = parseOptionValue(args, "--dir") ?? process.cwd();
|
|
966
996
|
const dir = resolvePath(dirArg);
|
|
967
997
|
const nameOverride = parseOptionValue(args, "--name");
|
|
@@ -973,6 +1003,7 @@ export async function initProject(args) {
|
|
|
973
1003
|
const forceCreateApplication = parseFlag(args, "--create-application");
|
|
974
1004
|
const autoMapApplication = parseFlag(args, "--auto-map-application");
|
|
975
1005
|
const nonInteractive = parseFlag(args, "--non-interactive");
|
|
1006
|
+
const upsertBatchSize = Number(parseOptionValue(args, "--batch-size") ?? "10") || 10;
|
|
976
1007
|
const creds = requireCredentials();
|
|
977
1008
|
const mcpOpts = { companyId: creds.companyId };
|
|
978
1009
|
if (!asJson)
|
|
@@ -1021,7 +1052,7 @@ export async function initProject(args) {
|
|
|
1021
1052
|
for (let i = 0; i < detectedNames.length; i += BATCH_SIZE) {
|
|
1022
1053
|
const batch = detectedNames.slice(i, i + BATCH_SIZE);
|
|
1023
1054
|
logProgress("resolve.batch", `${Math.floor(i / BATCH_SIZE) + 1}/${Math.ceil(detectedNames.length / BATCH_SIZE)} size=${batch.length}`);
|
|
1024
|
-
const raw = await
|
|
1055
|
+
const raw = await callMcpProfiled("nexarch_resolve_reference", { names: batch, companyId: creds.companyId }, { batchSize: batch.length });
|
|
1025
1056
|
const data = parseToolText(raw);
|
|
1026
1057
|
allResolveResults.push(...data.results);
|
|
1027
1058
|
}
|
|
@@ -1061,7 +1092,7 @@ export async function initProject(args) {
|
|
|
1061
1092
|
return;
|
|
1062
1093
|
}
|
|
1063
1094
|
logProgress("preflight.onboarding.check");
|
|
1064
|
-
const onboardingRaw = await
|
|
1095
|
+
const onboardingRaw = await callMcpProfiled("nexarch_get_company_onboarding", {});
|
|
1065
1096
|
const onboarding = parseToolText(onboardingRaw);
|
|
1066
1097
|
if (onboarding.isComplete !== true) {
|
|
1067
1098
|
const message = "Company onboarding is incomplete. Complete onboarding before running init-project.";
|
|
@@ -1074,7 +1105,7 @@ export async function initProject(args) {
|
|
|
1074
1105
|
}
|
|
1075
1106
|
// Policy bootstrap
|
|
1076
1107
|
logProgress("preflight.policies.check");
|
|
1077
|
-
const policiesRaw = await
|
|
1108
|
+
const policiesRaw = await callMcpProfiled("nexarch_get_applied_policies", {});
|
|
1078
1109
|
const policies = parseToolText(policiesRaw);
|
|
1079
1110
|
const policyBundleHash = policies.policyBundleHash ?? null;
|
|
1080
1111
|
if (!policyBundleHash) {
|
|
@@ -1099,7 +1130,7 @@ export async function initProject(args) {
|
|
|
1099
1130
|
console.log(`\nUsing --application-ref target: ${projectExternalKey}`);
|
|
1100
1131
|
}
|
|
1101
1132
|
else {
|
|
1102
|
-
const appsRaw = await
|
|
1133
|
+
const appsRaw = await callMcpProfiled("nexarch_list_entities", { entityTypeCode: "application", status: "active", limit: 500, companyId: creds.companyId }, { entityTypeCode: "application", limit: 500 });
|
|
1103
1134
|
const appsData = parseToolText(appsRaw);
|
|
1104
1135
|
const apps = (appsData.entities ?? []).filter((e) => (e.entityRef ?? e.externalKey));
|
|
1105
1136
|
if (apps.length > 0) {
|
|
@@ -1116,18 +1147,40 @@ export async function initProject(args) {
|
|
|
1116
1147
|
console.log(`\nAuto-mapped to existing application: ${suggested.name} (${projectExternalKey})`);
|
|
1117
1148
|
}
|
|
1118
1149
|
else if (!interactiveAllowed) {
|
|
1119
|
-
|
|
1120
|
-
|
|
1150
|
+
const message = "Application mapping requires explicit choice in non-interactive mode. Pass --application-ref <entityRef> or --create-application (or run interactively).";
|
|
1151
|
+
const existingApplications = apps.slice(0, 25).map((a) => ({
|
|
1152
|
+
entityRef: a.entityRef ?? a.externalKey,
|
|
1153
|
+
name: a.name,
|
|
1154
|
+
entityTypeCode: a.entityTypeCode,
|
|
1155
|
+
}));
|
|
1156
|
+
if (asJson) {
|
|
1157
|
+
process.stdout.write(`${JSON.stringify({
|
|
1158
|
+
ok: true,
|
|
1159
|
+
status: "input_required",
|
|
1160
|
+
code: "APPLICATION_MAPPING_REQUIRED",
|
|
1161
|
+
message,
|
|
1162
|
+
suggested: suggested ?? null,
|
|
1163
|
+
candidates: matches.slice(0, 10),
|
|
1164
|
+
existingApplications,
|
|
1165
|
+
requiredInput: {
|
|
1166
|
+
applicationRefOption: "--application-ref <entityRef>",
|
|
1167
|
+
createOption: "--create-application",
|
|
1168
|
+
},
|
|
1169
|
+
}, null, 2)}\n`);
|
|
1170
|
+
return;
|
|
1171
|
+
}
|
|
1172
|
+
console.log("\nInput required before continuing:");
|
|
1173
|
+
console.log(` ${message}`);
|
|
1174
|
+
if (suggested) {
|
|
1175
|
+
console.log(` Suggested: ${suggested.name} (${suggested.entityRef}) score=${suggested.score.toFixed(2)}`);
|
|
1121
1176
|
}
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
process.exitCode = 1;
|
|
1127
|
-
return;
|
|
1177
|
+
if (existingApplications.length > 0) {
|
|
1178
|
+
console.log(" Existing applications:");
|
|
1179
|
+
for (const app of existingApplications) {
|
|
1180
|
+
console.log(` - ${app.name} (${app.entityRef})`);
|
|
1128
1181
|
}
|
|
1129
|
-
throw new Error(message);
|
|
1130
1182
|
}
|
|
1183
|
+
return;
|
|
1131
1184
|
}
|
|
1132
1185
|
else {
|
|
1133
1186
|
const chosen = await promptApplicationChoice(matches, apps, highConfidence ? suggested : null);
|
|
@@ -1290,7 +1343,7 @@ export async function initProject(args) {
|
|
|
1290
1343
|
// Company org is accountable_for the top-level project entity
|
|
1291
1344
|
let orgExternalKey = `organisation:${normalizeToken(creds.companyId)}`;
|
|
1292
1345
|
try {
|
|
1293
|
-
const orgRaw = await
|
|
1346
|
+
const orgRaw = await callMcpProfiled("nexarch_list_entities", { entityTypeCode: "organisation", status: "active", limit: 1, companyId: creds.companyId }, { entityTypeCode: "organisation", limit: 1 });
|
|
1294
1347
|
const orgData = parseToolText(orgRaw);
|
|
1295
1348
|
const org = (orgData.entities ?? [])[0];
|
|
1296
1349
|
if (org?.entityRef || org?.externalKey)
|
|
@@ -1312,17 +1365,64 @@ export async function initProject(args) {
|
|
|
1312
1365
|
}
|
|
1313
1366
|
if (!asJson)
|
|
1314
1367
|
console.log(`\nWriting to graph…`);
|
|
1315
|
-
// Upsert entities
|
|
1316
|
-
|
|
1317
|
-
const
|
|
1318
|
-
|
|
1368
|
+
// Upsert entities (chunked for progressive feedback)
|
|
1369
|
+
const entityChunks = chunkArray(entities, Math.max(1, upsertBatchSize));
|
|
1370
|
+
const entitiesResult = {
|
|
1371
|
+
summary: { requested: 0, succeeded: 0, failed: 0 },
|
|
1372
|
+
errors: [],
|
|
1373
|
+
};
|
|
1374
|
+
logProgress("upsert.entities.start", `count=${entities.length}, batches=${entityChunks.length}, batchSize=${Math.max(1, upsertBatchSize)}`);
|
|
1375
|
+
for (let i = 0; i < entityChunks.length; i += 1) {
|
|
1376
|
+
const chunk = entityChunks[i];
|
|
1377
|
+
logProgress("upsert.entities.batch.start", `${i + 1}/${entityChunks.length} size=${chunk.length}`);
|
|
1378
|
+
const entityBatchStartedAt = Date.now();
|
|
1379
|
+
const entitiesRaw = await callMcpProfiled("nexarch_upsert_entities", { entities: chunk, agentContext, policyContext }, { batchIndex: i + 1, totalBatches: entityChunks.length, batchSize: chunk.length });
|
|
1380
|
+
const chunkResult = parseToolText(entitiesRaw);
|
|
1381
|
+
if (profileEnabled) {
|
|
1382
|
+
profile.upsertBatches.entities.push({
|
|
1383
|
+
index: i + 1,
|
|
1384
|
+
size: chunk.length,
|
|
1385
|
+
durationMs: Date.now() - entityBatchStartedAt,
|
|
1386
|
+
succeeded: Number(chunkResult.summary?.succeeded ?? 0),
|
|
1387
|
+
failed: Number(chunkResult.summary?.failed ?? 0),
|
|
1388
|
+
});
|
|
1389
|
+
}
|
|
1390
|
+
entitiesResult.summary.requested = Number(entitiesResult.summary.requested ?? 0) + Number(chunkResult.summary?.requested ?? chunk.length);
|
|
1391
|
+
entitiesResult.summary.succeeded = Number(entitiesResult.summary.succeeded ?? 0) + Number(chunkResult.summary?.succeeded ?? 0);
|
|
1392
|
+
entitiesResult.summary.failed = Number(entitiesResult.summary.failed ?? 0) + Number(chunkResult.summary?.failed ?? 0);
|
|
1393
|
+
if (chunkResult.errors?.length)
|
|
1394
|
+
entitiesResult.errors.push(...chunkResult.errors);
|
|
1395
|
+
logProgress("upsert.entities.batch.done", `${i + 1}/${entityChunks.length} succeeded=${chunkResult.summary?.succeeded ?? 0}, failed=${chunkResult.summary?.failed ?? 0}`);
|
|
1396
|
+
}
|
|
1319
1397
|
logProgress("upsert.entities.done", `succeeded=${entitiesResult.summary?.succeeded ?? 0}, failed=${entitiesResult.summary?.failed ?? 0}`);
|
|
1320
|
-
// Upsert relationships
|
|
1398
|
+
// Upsert relationships (chunked)
|
|
1321
1399
|
let relsResult = null;
|
|
1322
1400
|
if (relationships.length > 0) {
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1401
|
+
const relationshipChunks = chunkArray(relationships, Math.max(1, upsertBatchSize));
|
|
1402
|
+
relsResult = { summary: { requested: 0, succeeded: 0, failed: 0 }, errors: [] };
|
|
1403
|
+
logProgress("upsert.relationships.start", `count=${relationships.length}, batches=${relationshipChunks.length}, batchSize=${Math.max(1, upsertBatchSize)}`);
|
|
1404
|
+
for (let i = 0; i < relationshipChunks.length; i += 1) {
|
|
1405
|
+
const chunk = relationshipChunks[i];
|
|
1406
|
+
logProgress("upsert.relationships.batch.start", `${i + 1}/${relationshipChunks.length} size=${chunk.length}`);
|
|
1407
|
+
const relBatchStartedAt = Date.now();
|
|
1408
|
+
const relsRaw = await callMcpProfiled("nexarch_upsert_relationships", { relationships: chunk, agentContext, policyContext }, { batchIndex: i + 1, totalBatches: relationshipChunks.length, batchSize: chunk.length });
|
|
1409
|
+
const chunkResult = parseToolText(relsRaw);
|
|
1410
|
+
if (profileEnabled) {
|
|
1411
|
+
profile.upsertBatches.relationships.push({
|
|
1412
|
+
index: i + 1,
|
|
1413
|
+
size: chunk.length,
|
|
1414
|
+
durationMs: Date.now() - relBatchStartedAt,
|
|
1415
|
+
succeeded: Number(chunkResult.summary?.succeeded ?? 0),
|
|
1416
|
+
failed: Number(chunkResult.summary?.failed ?? 0),
|
|
1417
|
+
});
|
|
1418
|
+
}
|
|
1419
|
+
relsResult.summary.requested = Number(relsResult.summary.requested ?? 0) + Number(chunkResult.summary?.requested ?? chunk.length);
|
|
1420
|
+
relsResult.summary.succeeded = Number(relsResult.summary.succeeded ?? 0) + Number(chunkResult.summary?.succeeded ?? 0);
|
|
1421
|
+
relsResult.summary.failed = Number(relsResult.summary.failed ?? 0) + Number(chunkResult.summary?.failed ?? 0);
|
|
1422
|
+
if (chunkResult.errors?.length)
|
|
1423
|
+
relsResult.errors.push(...chunkResult.errors);
|
|
1424
|
+
logProgress("upsert.relationships.batch.done", `${i + 1}/${relationshipChunks.length} succeeded=${chunkResult.summary?.succeeded ?? 0}, failed=${chunkResult.summary?.failed ?? 0}`);
|
|
1425
|
+
}
|
|
1326
1426
|
logProgress("upsert.relationships.done", `succeeded=${relsResult.summary?.succeeded ?? 0}, failed=${relsResult.summary?.failed ?? 0}`);
|
|
1327
1427
|
}
|
|
1328
1428
|
// Build structured enrichment task (included in JSON output and printed in human mode)
|
|
@@ -1573,6 +1673,15 @@ ${subPkgSection}${adrSection}${gapCheckSection}`;
|
|
|
1573
1673
|
entityErrors: entitiesResult.errors ?? [],
|
|
1574
1674
|
relationshipErrors: relsResult?.errors ?? [],
|
|
1575
1675
|
enrichmentTask,
|
|
1676
|
+
profile: profileEnabled
|
|
1677
|
+
? {
|
|
1678
|
+
startedAt: profile.startedAt,
|
|
1679
|
+
totalDurationMs: Date.now() - commandStartedAt,
|
|
1680
|
+
events: profile.events,
|
|
1681
|
+
mcpCalls: profile.mcpCalls,
|
|
1682
|
+
upsertBatches: profile.upsertBatches,
|
|
1683
|
+
}
|
|
1684
|
+
: undefined,
|
|
1576
1685
|
};
|
|
1577
1686
|
if (asJson) {
|
|
1578
1687
|
logProgress("complete", `ok=${output.ok}`);
|
package/dist/index.js
CHANGED
|
@@ -94,6 +94,8 @@ Usage:
|
|
|
94
94
|
--create-application force new application entity
|
|
95
95
|
--auto-map-application auto-map only when high confidence
|
|
96
96
|
--non-interactive fail on ambiguous mapping
|
|
97
|
+
--batch-size <n> upsert batch size (default: 10)
|
|
98
|
+
--profile include timing/profile data in JSON output
|
|
97
99
|
--dry-run preview without writing
|
|
98
100
|
--json
|
|
99
101
|
nexarch update-entity
|