nexarch 0.11.4 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/check-in.js +38 -4
- package/dist/commands/command-claim.js +14 -0
- package/dist/commands/init-project.js +4 -4
- package/dist/commands/proposals-start.js +38 -3
- package/dist/commands/register-runtime.js +77 -0
- package/dist/commands/setup.js +2 -2
- package/dist/commands/status.js +15 -0
- package/dist/index.js +11 -0
- package/dist/lib/application-context.js +51 -0
- package/dist/lib/check-in-lease.js +55 -0
- package/package.json +1 -1
|
@@ -2,8 +2,10 @@ import process from "process";
|
|
|
2
2
|
import { existsSync, readFileSync } from "fs";
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
import { homedir } from "os";
|
|
5
|
+
import { saveCheckInLease } from "../lib/check-in-lease.js";
|
|
5
6
|
import { requireCredentials } from "../lib/credentials.js";
|
|
6
7
|
import { callMcpTool } from "../lib/mcp.js";
|
|
8
|
+
import { inferAccessibleApplicationRefs } from "../lib/application-context.js";
|
|
7
9
|
function parseFlag(args, flag) {
|
|
8
10
|
return args.includes(flag);
|
|
9
11
|
}
|
|
@@ -35,6 +37,7 @@ function loadIdentity() {
|
|
|
35
37
|
export async function checkIn(args) {
|
|
36
38
|
const asJson = parseFlag(args, "--json");
|
|
37
39
|
const agentKeyArg = parseOptionValue(args, "--agent-ref") ?? parseOptionValue(args, "--agent-key");
|
|
40
|
+
const applicationRefArg = parseOptionValue(args, "--application-ref");
|
|
38
41
|
const creds = requireCredentials();
|
|
39
42
|
const identity = loadIdentity();
|
|
40
43
|
const agentKey = agentKeyArg ?? identity.agentKey;
|
|
@@ -47,19 +50,44 @@ export async function checkIn(args) {
|
|
|
47
50
|
}
|
|
48
51
|
return;
|
|
49
52
|
}
|
|
53
|
+
const applicationContext = await inferAccessibleApplicationRefs({
|
|
54
|
+
companyId: creds.companyId,
|
|
55
|
+
explicitApplicationRefs: applicationRefArg ? [applicationRefArg] : undefined,
|
|
56
|
+
});
|
|
50
57
|
const raw = await callMcpTool("nexarch_check_in", {
|
|
51
58
|
agentRef: agentKey,
|
|
52
59
|
agentKey,
|
|
60
|
+
applicationContext: applicationContext.accessibleApplicationRefs.length > 0
|
|
61
|
+
? { accessibleApplicationRefs: applicationContext.accessibleApplicationRefs }
|
|
62
|
+
: undefined,
|
|
53
63
|
companyId: creds.companyId,
|
|
54
64
|
}, { companyId: creds.companyId });
|
|
55
65
|
const result = parseToolText(raw);
|
|
56
66
|
const draftApplications = result.draftApplications ?? [];
|
|
57
67
|
const proposedApplications = result.proposedApplications ?? [];
|
|
68
|
+
if (result.leaseToken && result.leaseIssuedAt && result.leaseExpiresAt) {
|
|
69
|
+
saveCheckInLease({
|
|
70
|
+
leaseToken: result.leaseToken,
|
|
71
|
+
leaseIssuedAt: result.leaseIssuedAt,
|
|
72
|
+
leaseExpiresAt: result.leaseExpiresAt,
|
|
73
|
+
policyBundleHash: result.policyBundleHash ?? null,
|
|
74
|
+
companyId: creds.companyId,
|
|
75
|
+
agentRef: agentKey,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
58
78
|
if (asJson) {
|
|
59
79
|
process.stdout.write(JSON.stringify(result) + "\n");
|
|
60
80
|
return;
|
|
61
81
|
}
|
|
62
82
|
const commands = result.commands ?? [];
|
|
83
|
+
if (result.leaseExpiresAt) {
|
|
84
|
+
console.log(`Check-in lease active until ${new Date(result.leaseExpiresAt).toLocaleString()}.`);
|
|
85
|
+
}
|
|
86
|
+
console.log(`Application context: ${applicationContext.detail}`);
|
|
87
|
+
for (const warning of result.warnings ?? []) {
|
|
88
|
+
if (warning.message)
|
|
89
|
+
console.log(`Warning: ${warning.message}`);
|
|
90
|
+
}
|
|
63
91
|
if (commands.length === 0 && draftApplications.length === 0 && proposedApplications.length === 0) {
|
|
64
92
|
console.log("No pending application-target commands found.");
|
|
65
93
|
console.log("No draft or proposed applications need attention.");
|
|
@@ -73,7 +101,7 @@ export async function checkIn(args) {
|
|
|
73
101
|
}
|
|
74
102
|
for (const cmd of commands) {
|
|
75
103
|
const claimable = cmd.claimable ? "yes" : "no";
|
|
76
|
-
const reason = cmd.claimReason ?? (cmd.claimable ? "application_access_confirmed" : "
|
|
104
|
+
const reason = cmd.claimReason ?? (cmd.claimable ? "application_access_confirmed" : "application_context_required_for_claim");
|
|
77
105
|
console.log(`\n- ID : ${cmd.id}`);
|
|
78
106
|
console.log(` Type : ${cmd.command_type}`);
|
|
79
107
|
console.log(` Target : ${cmd.target_entity_key ?? "(none)"}`);
|
|
@@ -83,12 +111,14 @@ export async function checkIn(args) {
|
|
|
83
111
|
console.log(` Type ver : ${cmd.command_type_version}`);
|
|
84
112
|
if (cmd.resolved_runtime_handler)
|
|
85
113
|
console.log(` Runtime : ${cmd.resolved_runtime_handler}`);
|
|
86
|
-
if (cmd.
|
|
87
|
-
console.log(`
|
|
88
|
-
}
|
|
114
|
+
if (cmd.claimGuidance)
|
|
115
|
+
console.log(` Guidance : ${cmd.claimGuidance}`);
|
|
116
|
+
console.log(` Claim cmd : npx nexarch command-claim --id "${cmd.id}"${cmd.target_entity_key ? ` --application-ref "${cmd.target_entity_key}"` : ""}`);
|
|
89
117
|
}
|
|
90
118
|
console.log("\nTo validate an application target is accessible in Nexarch before claiming:");
|
|
91
119
|
console.log(" npx nexarch policy-controls --entity <application:key> --json");
|
|
120
|
+
console.log("If no application context was inferred, re-run with:");
|
|
121
|
+
console.log(" npx nexarch check-in --application-ref <application:key>");
|
|
92
122
|
}
|
|
93
123
|
else {
|
|
94
124
|
console.log("No pending application-target commands found.");
|
|
@@ -124,8 +154,12 @@ export async function checkIn(args) {
|
|
|
124
154
|
console.log(` Desc : ${app.description}`);
|
|
125
155
|
if (app.recommendationBasis)
|
|
126
156
|
console.log(` Basis : ${app.recommendationBasis}`);
|
|
157
|
+
if (app.recommendationStatus)
|
|
158
|
+
console.log(` Status : ${app.recommendationStatus}`);
|
|
127
159
|
if (typeof app.recommendedTechnologyCount === "number")
|
|
128
160
|
console.log(` Tech : ${app.recommendedTechnologyCount} recommended`);
|
|
161
|
+
if (typeof app.capabilityGapCount === "number")
|
|
162
|
+
console.log(` Gaps : ${app.capabilityGapCount} unresolved`);
|
|
129
163
|
if (typeof app.confirmedPolicyCount === "number")
|
|
130
164
|
console.log(` Policy : ${app.confirmedPolicyCount} confirmed`);
|
|
131
165
|
if (app.requiresPolicyReview)
|
|
@@ -2,6 +2,7 @@ import process from "process";
|
|
|
2
2
|
import { existsSync, readFileSync } from "fs";
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
import { homedir } from "os";
|
|
5
|
+
import { selectCheckInLease } from "../lib/check-in-lease.js";
|
|
5
6
|
import { requireCredentials } from "../lib/credentials.js";
|
|
6
7
|
import { callMcpTool } from "../lib/mcp.js";
|
|
7
8
|
function parseFlag(args, flag) {
|
|
@@ -36,6 +37,7 @@ export async function commandClaim(args) {
|
|
|
36
37
|
const asJson = parseFlag(args, "--json");
|
|
37
38
|
const id = parseOptionValue(args, "--id");
|
|
38
39
|
const agentKeyArg = parseOptionValue(args, "--agent-ref") ?? parseOptionValue(args, "--agent-key");
|
|
40
|
+
const applicationEntityRef = parseOptionValue(args, "--application-ref");
|
|
39
41
|
if (!id) {
|
|
40
42
|
console.error("error: --id <commandId> is required");
|
|
41
43
|
process.exit(1);
|
|
@@ -47,10 +49,13 @@ export async function commandClaim(args) {
|
|
|
47
49
|
console.error("error: no agent key found. Pass --agent-key or run nexarch init-agent first.");
|
|
48
50
|
process.exit(1);
|
|
49
51
|
}
|
|
52
|
+
const lease = selectCheckInLease(creds.companyId, agentKey);
|
|
50
53
|
const raw = await callMcpTool("nexarch_claim_command_by_id", {
|
|
51
54
|
commandId: id,
|
|
52
55
|
agentRef: agentKey,
|
|
53
56
|
agentKey,
|
|
57
|
+
leaseToken: lease?.leaseToken,
|
|
58
|
+
applicationEntityRef: applicationEntityRef ?? undefined,
|
|
54
59
|
companyId: creds.companyId,
|
|
55
60
|
}, { companyId: creds.companyId });
|
|
56
61
|
const result = parseToolText(raw);
|
|
@@ -58,10 +63,19 @@ export async function commandClaim(args) {
|
|
|
58
63
|
process.stdout.write(JSON.stringify(result) + "\n");
|
|
59
64
|
return;
|
|
60
65
|
}
|
|
66
|
+
if ((result.warnings ?? []).length > 0) {
|
|
67
|
+
for (const warning of result.warnings ?? []) {
|
|
68
|
+
if (warning.message)
|
|
69
|
+
console.log(`Warning: ${warning.message}`);
|
|
70
|
+
}
|
|
71
|
+
console.log("Tip: run `npx nexarch check-in` to refresh your lease before delivery actions.");
|
|
72
|
+
}
|
|
61
73
|
if (!result.command) {
|
|
62
74
|
console.log(`Command ${id} was not claimed.`);
|
|
63
75
|
if (result.reason)
|
|
64
76
|
console.log(`Reason: ${result.reason}`);
|
|
77
|
+
if (!applicationEntityRef)
|
|
78
|
+
console.log("Tip: re-run with --application-ref <application:key>.");
|
|
65
79
|
return;
|
|
66
80
|
}
|
|
67
81
|
const cmd = result.command;
|
|
@@ -221,7 +221,7 @@ function choosePreferredRepositoryRef(candidates) {
|
|
|
221
221
|
});
|
|
222
222
|
return scored[0]?.rawRef ?? null;
|
|
223
223
|
}
|
|
224
|
-
function detectSourceRepository(dir) {
|
|
224
|
+
export function detectSourceRepository(dir) {
|
|
225
225
|
let rawRef = null;
|
|
226
226
|
let vcsType = "unknown";
|
|
227
227
|
// Prefer git when present, and prefer hosted remotes over local-only URLs.
|
|
@@ -869,7 +869,7 @@ function structuralRelForSubPackage(sp, projectExternalKey) {
|
|
|
869
869
|
return { type: "part_of", from: sp.externalKey, to: projectExternalKey };
|
|
870
870
|
return null;
|
|
871
871
|
}
|
|
872
|
-
function scanProject(dir) {
|
|
872
|
+
export function scanProject(dir) {
|
|
873
873
|
const names = new Set();
|
|
874
874
|
let projectName = basename(dir);
|
|
875
875
|
const subPackages = [];
|
|
@@ -995,7 +995,7 @@ function pickRelationshipType(toEntityTypeCode, toEntitySubtypeCode, _fromEntity
|
|
|
995
995
|
return "depends_on";
|
|
996
996
|
}
|
|
997
997
|
}
|
|
998
|
-
function normalizeToken(value) {
|
|
998
|
+
export function normalizeToken(value) {
|
|
999
999
|
return value.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
1000
1000
|
}
|
|
1001
1001
|
function extractHost(input) {
|
|
@@ -1008,7 +1008,7 @@ function extractHost(input) {
|
|
|
1008
1008
|
return null;
|
|
1009
1009
|
}
|
|
1010
1010
|
}
|
|
1011
|
-
function scoreApplicationCandidate(app, projectName, repoUrl) {
|
|
1011
|
+
export function scoreApplicationCandidate(app, projectName, repoUrl) {
|
|
1012
1012
|
const entityRef = app.entityRef ?? app.externalKey ?? null;
|
|
1013
1013
|
if (!entityRef)
|
|
1014
1014
|
return null;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import process from "process";
|
|
2
2
|
import * as readline from "node:readline/promises";
|
|
3
|
-
import { existsSync, mkdirSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { homedir } from "node:os";
|
|
4
5
|
import { dirname, join, resolve as resolvePath } from "node:path";
|
|
6
|
+
import { selectCheckInLease } from "../lib/check-in-lease.js";
|
|
5
7
|
import { requireCredentials } from "../lib/credentials.js";
|
|
6
8
|
import { callMcpTool } from "../lib/mcp.js";
|
|
7
9
|
function parseFlag(args, flag) {
|
|
@@ -20,6 +22,18 @@ function parseToolText(result) {
|
|
|
20
22
|
const text = result.content?.[0]?.text ?? "{}";
|
|
21
23
|
return JSON.parse(text);
|
|
22
24
|
}
|
|
25
|
+
function loadIdentity() {
|
|
26
|
+
const identityPath = join(homedir(), ".nexarch", "identity.json");
|
|
27
|
+
if (!existsSync(identityPath))
|
|
28
|
+
return { agentKey: null };
|
|
29
|
+
try {
|
|
30
|
+
const data = JSON.parse(readFileSync(identityPath, "utf8"));
|
|
31
|
+
return { agentKey: data.agentKey ?? null };
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return { agentKey: null };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
23
37
|
function slugify(value) {
|
|
24
38
|
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "application";
|
|
25
39
|
}
|
|
@@ -199,6 +213,8 @@ function inferStackTemplate(proposal) {
|
|
|
199
213
|
function buildReadme(proposal, template) {
|
|
200
214
|
const technologies = proposal.technologyChoices.map((item) => item.name);
|
|
201
215
|
const policies = proposal.policyControls.map((item) => item.name);
|
|
216
|
+
const capabilityGaps = (proposal.proposal.recommendationCapabilityGaps ?? [])
|
|
217
|
+
.map((item) => `${item.capability ?? "Unspecified capability"}${item.layer ? ` (${item.layer})` : ""}${item.severity ? ` — ${item.severity}` : ""}${item.reason ? `: ${item.reason}` : ""}`);
|
|
202
218
|
return `# ${proposal.application.name}
|
|
203
219
|
|
|
204
220
|
${proposal.application.description ?? "Application scaffold generated from a NexArch proposal."}
|
|
@@ -216,13 +232,14 @@ ${proposal.proposal.requiresPolicyReview ? `> Policy review is required before a
|
|
|
216
232
|
- Workflow state: ${proposal.application.workflowState}
|
|
217
233
|
- Application subtype: ${proposal.application.entitySubtypeCode ?? "Not specified"}
|
|
218
234
|
- Recommendation basis: ${proposal.application.recommendationBasis ?? "Not specified"}
|
|
235
|
+
- Recommendation status: ${proposal.application.recommendationStatus ?? proposal.proposal.recommendationStatus ?? "Not specified"}
|
|
219
236
|
- NexArch reference: ${proposal.application.entityRef ?? proposal.application.id}
|
|
220
237
|
|
|
221
238
|
${proposal.application.recommendationSummary ? `## Recommendation notes\n\n${proposal.application.recommendationSummary}\n\n` : ""}## Suggested technology choices
|
|
222
239
|
|
|
223
240
|
${formatBulletList(technologies)}
|
|
224
241
|
|
|
225
|
-
## Policy controls to satisfy
|
|
242
|
+
${capabilityGaps.length > 0 ? `## Capability gaps\n\n${formatBulletList(capabilityGaps)}\n\n` : ""}## Policy controls to satisfy
|
|
226
243
|
|
|
227
244
|
${formatBulletList(policies)}
|
|
228
245
|
|
|
@@ -233,11 +250,20 @@ ${template.nextSteps.map((step, index) => `${index + 1}. ${step}`).join("\n")}
|
|
|
233
250
|
}
|
|
234
251
|
function buildProposalMarkdown(proposal) {
|
|
235
252
|
const techLines = proposal.technologyChoices.map((tech) => `${tech.name}${tech.entitySubtypeCode ? ` (${tech.entitySubtypeCode})` : ""}${tech.description ? ` — ${tech.description}` : ""}`);
|
|
253
|
+
const recommendationDetailLines = (proposal.proposal.recommendationTechnologyDetails ?? [])
|
|
254
|
+
.map((tech) => `${tech.entityName ?? tech.entityId ?? "Unknown technology"}${tech.architectureLayer ? ` [${tech.architectureLayer}]` : ""}${tech.source ? ` • ${tech.source}` : ""}${tech.reason ? ` — ${tech.reason}` : ""}`);
|
|
255
|
+
const blueprintLines = (proposal.proposal.recommendationBlueprint ?? [])
|
|
256
|
+
.map((item) => `${item.label ?? item.layer ?? "Unlabelled layer"}${item.coverage ? ` — ${item.coverage}` : ""}${item.recommendationEntityIds.length > 0 ? ` (${item.recommendationEntityIds.length} mapped recommendation${item.recommendationEntityIds.length === 1 ? "" : "s"})` : ""}`);
|
|
257
|
+
const capabilityGapLines = (proposal.proposal.recommendationCapabilityGaps ?? [])
|
|
258
|
+
.map((item) => `${item.capability ?? "Unspecified capability"}${item.layer ? ` [${item.layer}]` : ""}${item.severity ? ` • ${item.severity}` : ""}${item.reason ? ` — ${item.reason}` : ""}`);
|
|
259
|
+
const warningLines = proposal.proposal.recommendationWarnings ?? [];
|
|
260
|
+
const riskLines = proposal.proposal.recommendationRisks ?? [];
|
|
236
261
|
const policyLines = proposal.policyControls.map((control) => `${control.name}${control.description ? ` — ${control.description}` : ""}`);
|
|
237
262
|
const gateLines = proposal.proposal.requiresPolicyReview
|
|
238
263
|
? `- Activation gate: policy review required\n- Required policy controls: ${proposal.proposal.requiredPolicyControlIds?.length ?? 0}\n`
|
|
239
264
|
: "";
|
|
240
|
-
|
|
265
|
+
const trace = proposal.proposal.recommendationTrace;
|
|
266
|
+
return `# NexArch proposal context\n\n## Application\n\n- Name: ${proposal.application.name}\n- ID: ${proposal.application.id}\n- Reference: ${proposal.application.entityRef ?? "n/a"}\n- Subtype: ${proposal.application.entitySubtypeCode ?? "n/a"}\n- Description: ${proposal.application.description ?? "n/a"}\n- Recommendation basis: ${proposal.application.recommendationBasis ?? "n/a"}\n- Recommendation status: ${proposal.application.recommendationStatus ?? proposal.proposal.recommendationStatus ?? "n/a"}\n- Recommendation job: ${proposal.application.recommendationJobId ?? proposal.proposal.recommendationJobId ?? "n/a"}\n- Recommendation generated: ${proposal.application.recommendationGeneratedAt ?? proposal.proposal.recommendationGeneratedAt ?? "n/a"}\n- Proposal source: ${proposal.proposal.proposalSource ?? "n/a"}\n${gateLines}\n${proposal.application.recommendationSummary ? `## Recommendation summary\n\n${proposal.application.recommendationSummary}\n\n` : ""}${blueprintLines.length > 0 ? `## Layer blueprint\n\n${formatBulletList(blueprintLines)}\n\n` : ""}${recommendationDetailLines.length > 0 ? `## Recommendation provenance\n\n${formatBulletList(recommendationDetailLines)}\n\n` : ""}${capabilityGapLines.length > 0 ? `## Capability gaps\n\n${formatBulletList(capabilityGapLines)}\n\n` : ""}${warningLines.length > 0 ? `## Warnings\n\n${formatBulletList(warningLines)}\n\n` : ""}${riskLines.length > 0 ? `## Risks\n\n${formatBulletList(riskLines)}\n\n` : ""}${trace ? `## Recommendation trace\n\n- Candidate count: ${trace.candidateCount ?? 0}\n- Reference matches: ${trace.referenceMatchCount ?? 0}\n- Similar applications: ${trace.similarApplicationCount ?? 0}\n\n` : ""}${proposal.proposal.preScaffoldInstructions ? `## Pre-scaffold instruction\n\n${proposal.proposal.preScaffoldInstructions}\n\n` : ""}## Technology choices\n\n${formatBulletList(techLines)}\n\n## Policy controls\n\n${formatBulletList(policyLines)}\n`;
|
|
241
267
|
}
|
|
242
268
|
function buildPolicyMarkdown(proposal) {
|
|
243
269
|
if (proposal.policyControls.length === 0) {
|
|
@@ -498,6 +524,9 @@ export async function proposalsStart(args) {
|
|
|
498
524
|
const reason = parseOptionValue(args, "--reason") ?? "Application scaffold started from approved proposal workflow";
|
|
499
525
|
const repoUrl = parseOptionValue(args, "--repo");
|
|
500
526
|
const creds = requireCredentials();
|
|
527
|
+
const identity = loadIdentity();
|
|
528
|
+
const agentRef = identity.agentKey;
|
|
529
|
+
const lease = agentRef ? selectCheckInLease(creds.companyId, agentRef) : null;
|
|
501
530
|
const listRaw = await callMcpTool("nexarch_list_proposed_applications", { companyId: creds.companyId }, { companyId: creds.companyId });
|
|
502
531
|
const list = parseToolText(listRaw);
|
|
503
532
|
const proposals = list.proposals ?? [];
|
|
@@ -567,8 +596,10 @@ export async function proposalsStart(args) {
|
|
|
567
596
|
initiatedBy: "nexarch-cli",
|
|
568
597
|
command: "proposals start",
|
|
569
598
|
operatorEmail: creds.email,
|
|
599
|
+
agentRef,
|
|
570
600
|
},
|
|
571
601
|
reviewedPolicyControlIds: proposal.proposal.requiredPolicyControlIds ?? proposal.proposal.confirmedPolicyControlIds,
|
|
602
|
+
leaseToken: lease?.leaseToken,
|
|
572
603
|
}, { companyId: creds.companyId });
|
|
573
604
|
activated = parseToolText(activateRaw);
|
|
574
605
|
}
|
|
@@ -591,6 +622,10 @@ export async function proposalsStart(args) {
|
|
|
591
622
|
console.log(`- Runtime : ${scaffold.template.runtime}`);
|
|
592
623
|
console.log(`- Files : ${scaffold.createdFiles.length} created`);
|
|
593
624
|
console.log(`- State : ${activated ? `${activated.status}${activated.alreadyActive ? " (already active)" : ""}` : "left in proposed state"}`);
|
|
625
|
+
for (const warning of activated?.warnings ?? []) {
|
|
626
|
+
if (warning.message)
|
|
627
|
+
console.log(`- Warning : ${warning.message}`);
|
|
628
|
+
}
|
|
594
629
|
console.log("\nNext steps:");
|
|
595
630
|
scaffold.template.nextSteps.forEach((step, index) => console.log(`${index + 1}. ${step}`));
|
|
596
631
|
console.log(`${scaffold.template.nextSteps.length + 1}. Use docs/architecture/nexarch-proposal.md and policy-controls.md while building.`);
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import process from "process";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { arch, hostname, platform, release, type as osType, homedir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { requireCredentials } from "../lib/credentials.js";
|
|
6
|
+
import { callMcpTool } from "../lib/mcp.js";
|
|
7
|
+
import { inferAccessibleApplicationRefs } from "../lib/application-context.js";
|
|
8
|
+
function parseFlag(args, flag) {
|
|
9
|
+
return args.includes(flag);
|
|
10
|
+
}
|
|
11
|
+
function parseOptionValue(args, option) {
|
|
12
|
+
const idx = args.indexOf(option);
|
|
13
|
+
if (idx === -1)
|
|
14
|
+
return null;
|
|
15
|
+
const value = args[idx + 1];
|
|
16
|
+
if (!value || value.startsWith("--"))
|
|
17
|
+
return null;
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
function parseToolText(result) {
|
|
21
|
+
const text = result.content?.[0]?.text ?? "{}";
|
|
22
|
+
return JSON.parse(text);
|
|
23
|
+
}
|
|
24
|
+
function loadIdentity() {
|
|
25
|
+
const identityPath = join(homedir(), ".nexarch", "identity.json");
|
|
26
|
+
if (!existsSync(identityPath))
|
|
27
|
+
return { agentKey: null };
|
|
28
|
+
try {
|
|
29
|
+
const data = JSON.parse(readFileSync(identityPath, "utf8"));
|
|
30
|
+
return { agentKey: data.agentKey ?? null };
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return { agentKey: null };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export async function registerRuntime(args) {
|
|
37
|
+
const asJson = parseFlag(args, "--json");
|
|
38
|
+
const applicationRefArg = parseOptionValue(args, "--application-ref");
|
|
39
|
+
const client = parseOptionValue(args, "--client") ?? "nexarch-cli";
|
|
40
|
+
const version = parseOptionValue(args, "--version") ?? null;
|
|
41
|
+
const mode = process.env.CI ? "ci" : "interactive";
|
|
42
|
+
const creds = requireCredentials();
|
|
43
|
+
const identity = loadIdentity();
|
|
44
|
+
if (!identity.agentKey)
|
|
45
|
+
throw new Error("No agent key found. Run nexarch init-agent first.");
|
|
46
|
+
const applicationContext = await inferAccessibleApplicationRefs({
|
|
47
|
+
companyId: creds.companyId,
|
|
48
|
+
explicitApplicationRefs: applicationRefArg ? [applicationRefArg] : undefined,
|
|
49
|
+
});
|
|
50
|
+
const raw = await callMcpTool("nexarch_register_agent_runtime", {
|
|
51
|
+
agentRef: identity.agentKey,
|
|
52
|
+
runtime: {
|
|
53
|
+
client,
|
|
54
|
+
...(version ? { version } : {}),
|
|
55
|
+
mode,
|
|
56
|
+
nodeVersion: process.version,
|
|
57
|
+
},
|
|
58
|
+
host: {
|
|
59
|
+
hostname: hostname(),
|
|
60
|
+
osPlatform: platform(),
|
|
61
|
+
osType: osType(),
|
|
62
|
+
osRelease: release(),
|
|
63
|
+
arch: arch(),
|
|
64
|
+
},
|
|
65
|
+
applicationContext: applicationContext.accessibleApplicationRefs.length > 0
|
|
66
|
+
? { accessibleApplicationRefs: applicationContext.accessibleApplicationRefs }
|
|
67
|
+
: undefined,
|
|
68
|
+
companyId: creds.companyId,
|
|
69
|
+
}, { companyId: creds.companyId });
|
|
70
|
+
const result = parseToolText(raw);
|
|
71
|
+
if (asJson) {
|
|
72
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
console.log(`Registered runtime for ${identity.agentKey}.`);
|
|
76
|
+
console.log(`Application context: ${applicationContext.detail}`);
|
|
77
|
+
}
|
package/dist/commands/setup.js
CHANGED
|
@@ -67,9 +67,9 @@ export async function setup(args) {
|
|
|
67
67
|
if (clients.length > 0) {
|
|
68
68
|
const names = clients.map((c) => c.name);
|
|
69
69
|
const listed = names.length === 1 ? names[0] : `${names.slice(0, -1).join(", ")} and ${names[names.length - 1]}`;
|
|
70
|
-
console.log(`\nSetup complete. Open a new ${listed} session and ask your agent to
|
|
70
|
+
console.log(`\nSetup complete. Open a new ${listed} session and ask your agent to register its runtime with Nexarch. Check-in is a separate step when you want to preview work.`);
|
|
71
71
|
}
|
|
72
72
|
else {
|
|
73
|
-
console.log(
|
|
73
|
+
console.log("\nSetup complete. Once you have configured an MCP client (see above), open a new session and ask your agent to register its runtime with Nexarch. Check-in is a separate step when you want to preview work.");
|
|
74
74
|
}
|
|
75
75
|
}
|
package/dist/commands/status.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getCheckInLeaseState, loadCheckInLease } from "../lib/check-in-lease.js";
|
|
1
2
|
import { requireCredentials } from "../lib/credentials.js";
|
|
2
3
|
import { fetchAgentRegistryOrThrow } from "../lib/agent-registry.js";
|
|
3
4
|
import { callMcpTool } from "../lib/mcp.js";
|
|
@@ -6,6 +7,8 @@ export async function status(_args) {
|
|
|
6
7
|
const selectedCompanyId = creds.companyId;
|
|
7
8
|
const expiresAt = new Date(creds.expiresAt);
|
|
8
9
|
const daysLeft = Math.ceil((expiresAt.getTime() - Date.now()) / (1000 * 60 * 60 * 24));
|
|
10
|
+
const lease = loadCheckInLease();
|
|
11
|
+
const leaseState = lease && lease.companyId === selectedCompanyId ? getCheckInLeaseState(lease) : { status: "missing", expiresInSeconds: null };
|
|
9
12
|
process.stdout.write("Connecting to mcp.nexarch.ai… ");
|
|
10
13
|
let governance;
|
|
11
14
|
let registryInfo = null;
|
|
@@ -51,7 +54,19 @@ export async function status(_args) {
|
|
|
51
54
|
console.log(` Latest snapshot: none published yet`);
|
|
52
55
|
}
|
|
53
56
|
console.log(`\n Token expires: ${expiresAt.toLocaleDateString()} (${daysLeft} days)`);
|
|
57
|
+
if (leaseState.status === "valid" && lease) {
|
|
58
|
+
console.log(` Check-in lease: ${new Date(lease.leaseExpiresAt).toLocaleString()} (${Math.ceil((leaseState.expiresInSeconds ?? 0) / 3600)}h left)`);
|
|
59
|
+
}
|
|
60
|
+
else if (leaseState.status === "expired" && lease) {
|
|
61
|
+
console.log(` Check-in lease: expired at ${new Date(lease.leaseExpiresAt).toLocaleString()}`);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
console.log(" Check-in lease: none stored");
|
|
65
|
+
}
|
|
54
66
|
if (daysLeft <= 14) {
|
|
55
67
|
console.log(`\n ⚠ Your token expires soon. Run \`nexarch login\` to renew.`);
|
|
56
68
|
}
|
|
69
|
+
if (leaseState.status !== "valid") {
|
|
70
|
+
console.log(" ⚠ Run `nexarch check-in` to refresh your workspace lease.");
|
|
71
|
+
}
|
|
57
72
|
}
|
package/dist/index.js
CHANGED
|
@@ -25,6 +25,7 @@ import { policyAuditResults } from "./commands/policy-audit-results.js";
|
|
|
25
25
|
import { appliedPolicies } from "./commands/applied-policies.js";
|
|
26
26
|
import { governanceSummary } from "./commands/governance-summary.js";
|
|
27
27
|
import { proposalsStart } from "./commands/proposals-start.js";
|
|
28
|
+
import { registerRuntime } from "./commands/register-runtime.js";
|
|
28
29
|
const [, , command, ...args] = process.argv;
|
|
29
30
|
const commands = {
|
|
30
31
|
login,
|
|
@@ -43,6 +44,7 @@ const commands = {
|
|
|
43
44
|
"resolve-names": resolveNames,
|
|
44
45
|
"list-entities": listEntities,
|
|
45
46
|
"list-relationships": listRelationships,
|
|
47
|
+
"register-runtime": registerRuntime,
|
|
46
48
|
"check-in": checkIn,
|
|
47
49
|
"command-done": commandDone,
|
|
48
50
|
"command-fail": commandFail,
|
|
@@ -180,12 +182,20 @@ Usage:
|
|
|
180
182
|
--to <toExternalKey>
|
|
181
183
|
--limit <1-500>
|
|
182
184
|
--json
|
|
185
|
+
nexarch register-runtime
|
|
186
|
+
Register or refresh runtime + optional application context
|
|
187
|
+
without performing check-in.
|
|
188
|
+
Options: --application-ref <entityRef>
|
|
189
|
+
--client <name>
|
|
190
|
+
--version <semver>
|
|
191
|
+
--json
|
|
183
192
|
nexarch check-in Preview pending application-target commands (no auto-claim)
|
|
184
193
|
and report draft/proposed applications needing review so the
|
|
185
194
|
agent can prompt the user to explore and instantiate them.
|
|
186
195
|
Use command-claim to explicitly claim a specific command.
|
|
187
196
|
Scope is resolved server-side from active company context.
|
|
188
197
|
Options: --agent-key <key> override stored agent key
|
|
198
|
+
--application-ref <entityRef> narrow preview scope
|
|
189
199
|
--json JSON output includes draftApplications[] and proposedApplications[]
|
|
190
200
|
nexarch proposals start
|
|
191
201
|
Start a new application workspace from a proposed NexArch app.
|
|
@@ -205,6 +215,7 @@ Usage:
|
|
|
205
215
|
Explicitly claim a pending command by ID.
|
|
206
216
|
Options: --id <commandId> (required)
|
|
207
217
|
--agent-key <key> override stored agent key
|
|
218
|
+
--application-ref <entityRef> required for application-target commands
|
|
208
219
|
--json
|
|
209
220
|
nexarch command-done
|
|
210
221
|
Mark a claimed command as completed.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { resolve as resolvePath } from "node:path";
|
|
2
|
+
import { callMcpTool } from "./mcp.js";
|
|
3
|
+
import { detectSourceRepository, scanProject, scoreApplicationCandidate } from "../commands/init-project.js";
|
|
4
|
+
function parseToolText(result) {
|
|
5
|
+
const text = result.content?.[0]?.text ?? "{}";
|
|
6
|
+
return JSON.parse(text);
|
|
7
|
+
}
|
|
8
|
+
export async function inferAccessibleApplicationRefs(params) {
|
|
9
|
+
const explicit = Array.from(new Set((params.explicitApplicationRefs ?? []).map((value) => value.trim()).filter(Boolean)));
|
|
10
|
+
if (explicit.length > 0) {
|
|
11
|
+
return {
|
|
12
|
+
accessibleApplicationRefs: explicit,
|
|
13
|
+
source: "explicit",
|
|
14
|
+
detail: `Using explicit application context (${explicit.join(", ")}).`,
|
|
15
|
+
repoUrl: detectSourceRepository(params.dir ?? process.cwd())?.url ?? null,
|
|
16
|
+
projectNames: [],
|
|
17
|
+
candidates: [],
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const dir = resolvePath(params.dir ?? process.cwd());
|
|
21
|
+
const repoUrl = detectSourceRepository(dir)?.url ?? null;
|
|
22
|
+
const scan = scanProject(dir);
|
|
23
|
+
const projectNames = Array.from(new Set([scan.projectName, ...scan.subPackages.map((sp) => sp.name)])).filter(Boolean);
|
|
24
|
+
const appsRaw = await callMcpTool("nexarch_list_entities", { entityTypeCode: "application", status: "active", limit: 500, companyId: params.companyId }, { companyId: params.companyId });
|
|
25
|
+
const appsData = parseToolText(appsRaw);
|
|
26
|
+
const apps = (appsData.entities ?? []).filter((entity) => (entity.entityRef ?? entity.externalKey));
|
|
27
|
+
const candidateMap = new Map();
|
|
28
|
+
for (const projectName of projectNames) {
|
|
29
|
+
for (const app of apps) {
|
|
30
|
+
const match = scoreApplicationCandidate(app, projectName, repoUrl);
|
|
31
|
+
if (!match)
|
|
32
|
+
continue;
|
|
33
|
+
const existing = candidateMap.get(match.entityRef);
|
|
34
|
+
if (!existing || match.score > existing.score) {
|
|
35
|
+
candidateMap.set(match.entityRef, match);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const candidates = Array.from(candidateMap.values()).sort((a, b) => b.score - a.score);
|
|
40
|
+
const accessibleApplicationRefs = candidates.filter((candidate) => candidate.score >= 0.85).map((candidate) => candidate.entityRef);
|
|
41
|
+
return {
|
|
42
|
+
accessibleApplicationRefs,
|
|
43
|
+
source: accessibleApplicationRefs.length > 0 ? "inferred" : "none",
|
|
44
|
+
detail: accessibleApplicationRefs.length > 0
|
|
45
|
+
? `Inferred application context from current folder (${accessibleApplicationRefs.join(", ")}).`
|
|
46
|
+
: "No high-confidence application context inferred from current folder.",
|
|
47
|
+
repoUrl,
|
|
48
|
+
projectNames,
|
|
49
|
+
candidates,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
function leasePath() {
|
|
5
|
+
return join(homedir(), ".nexarch", "check-in-lease.json");
|
|
6
|
+
}
|
|
7
|
+
export function loadCheckInLease() {
|
|
8
|
+
const path = leasePath();
|
|
9
|
+
if (!existsSync(path))
|
|
10
|
+
return null;
|
|
11
|
+
try {
|
|
12
|
+
const raw = readFileSync(path, "utf8");
|
|
13
|
+
const lease = JSON.parse(raw);
|
|
14
|
+
if (!lease.leaseToken || !lease.companyId || !lease.agentRef || !lease.leaseExpiresAt || !lease.leaseIssuedAt) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
return lease;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function saveCheckInLease(lease) {
|
|
24
|
+
const dir = join(homedir(), ".nexarch");
|
|
25
|
+
mkdirSync(dir, { recursive: true });
|
|
26
|
+
writeFileSync(leasePath(), JSON.stringify(lease, null, 2), {
|
|
27
|
+
encoding: "utf8",
|
|
28
|
+
mode: 0o600,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
export function clearCheckInLease() {
|
|
32
|
+
const path = leasePath();
|
|
33
|
+
if (existsSync(path))
|
|
34
|
+
rmSync(path);
|
|
35
|
+
}
|
|
36
|
+
export function selectCheckInLease(companyId, agentRef) {
|
|
37
|
+
const lease = loadCheckInLease();
|
|
38
|
+
if (!lease)
|
|
39
|
+
return null;
|
|
40
|
+
if (lease.companyId !== companyId)
|
|
41
|
+
return null;
|
|
42
|
+
if (lease.agentRef !== agentRef)
|
|
43
|
+
return null;
|
|
44
|
+
return lease;
|
|
45
|
+
}
|
|
46
|
+
export function getCheckInLeaseState(lease) {
|
|
47
|
+
if (!lease)
|
|
48
|
+
return { status: "missing", expiresInSeconds: null };
|
|
49
|
+
const expiresAtMs = new Date(lease.leaseExpiresAt).getTime();
|
|
50
|
+
const expiresInMs = expiresAtMs - Date.now();
|
|
51
|
+
if (!Number.isFinite(expiresAtMs) || expiresInMs <= 0) {
|
|
52
|
+
return { status: "expired", expiresInSeconds: 0 };
|
|
53
|
+
}
|
|
54
|
+
return { status: "valid", expiresInSeconds: Math.floor(expiresInMs / 1000) };
|
|
55
|
+
}
|