nexarch 0.11.3 → 0.12.0
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.
|
@@ -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 { saveCheckInLease } 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) {
|
|
@@ -55,11 +56,28 @@ export async function checkIn(args) {
|
|
|
55
56
|
const result = parseToolText(raw);
|
|
56
57
|
const draftApplications = result.draftApplications ?? [];
|
|
57
58
|
const proposedApplications = result.proposedApplications ?? [];
|
|
59
|
+
if (result.leaseToken && result.leaseIssuedAt && result.leaseExpiresAt) {
|
|
60
|
+
saveCheckInLease({
|
|
61
|
+
leaseToken: result.leaseToken,
|
|
62
|
+
leaseIssuedAt: result.leaseIssuedAt,
|
|
63
|
+
leaseExpiresAt: result.leaseExpiresAt,
|
|
64
|
+
policyBundleHash: result.policyBundleHash ?? null,
|
|
65
|
+
companyId: creds.companyId,
|
|
66
|
+
agentRef: agentKey,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
58
69
|
if (asJson) {
|
|
59
70
|
process.stdout.write(JSON.stringify(result) + "\n");
|
|
60
71
|
return;
|
|
61
72
|
}
|
|
62
73
|
const commands = result.commands ?? [];
|
|
74
|
+
if (result.leaseExpiresAt) {
|
|
75
|
+
console.log(`Check-in lease active until ${new Date(result.leaseExpiresAt).toLocaleString()}.`);
|
|
76
|
+
}
|
|
77
|
+
for (const warning of result.warnings ?? []) {
|
|
78
|
+
if (warning.message)
|
|
79
|
+
console.log(`Warning: ${warning.message}`);
|
|
80
|
+
}
|
|
63
81
|
if (commands.length === 0 && draftApplications.length === 0 && proposedApplications.length === 0) {
|
|
64
82
|
console.log("No pending application-target commands found.");
|
|
65
83
|
console.log("No draft or proposed applications need attention.");
|
|
@@ -124,10 +142,16 @@ export async function checkIn(args) {
|
|
|
124
142
|
console.log(` Desc : ${app.description}`);
|
|
125
143
|
if (app.recommendationBasis)
|
|
126
144
|
console.log(` Basis : ${app.recommendationBasis}`);
|
|
145
|
+
if (app.recommendationStatus)
|
|
146
|
+
console.log(` Status : ${app.recommendationStatus}`);
|
|
127
147
|
if (typeof app.recommendedTechnologyCount === "number")
|
|
128
148
|
console.log(` Tech : ${app.recommendedTechnologyCount} recommended`);
|
|
149
|
+
if (typeof app.capabilityGapCount === "number")
|
|
150
|
+
console.log(` Gaps : ${app.capabilityGapCount} unresolved`);
|
|
129
151
|
if (typeof app.confirmedPolicyCount === "number")
|
|
130
152
|
console.log(` Policy : ${app.confirmedPolicyCount} confirmed`);
|
|
153
|
+
if (app.requiresPolicyReview)
|
|
154
|
+
console.log(` Gate : policy review required`);
|
|
131
155
|
console.log(` ID : ${app.id}`);
|
|
132
156
|
console.log(` Realise : npx nexarch proposals start --id "${app.id}"`);
|
|
133
157
|
}
|
|
@@ -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) {
|
|
@@ -47,10 +48,12 @@ export async function commandClaim(args) {
|
|
|
47
48
|
console.error("error: no agent key found. Pass --agent-key or run nexarch init-agent first.");
|
|
48
49
|
process.exit(1);
|
|
49
50
|
}
|
|
51
|
+
const lease = selectCheckInLease(creds.companyId, agentKey);
|
|
50
52
|
const raw = await callMcpTool("nexarch_claim_command_by_id", {
|
|
51
53
|
commandId: id,
|
|
52
54
|
agentRef: agentKey,
|
|
53
55
|
agentKey,
|
|
56
|
+
leaseToken: lease?.leaseToken,
|
|
54
57
|
companyId: creds.companyId,
|
|
55
58
|
}, { companyId: creds.companyId });
|
|
56
59
|
const result = parseToolText(raw);
|
|
@@ -58,6 +61,13 @@ export async function commandClaim(args) {
|
|
|
58
61
|
process.stdout.write(JSON.stringify(result) + "\n");
|
|
59
62
|
return;
|
|
60
63
|
}
|
|
64
|
+
if ((result.warnings ?? []).length > 0) {
|
|
65
|
+
for (const warning of result.warnings ?? []) {
|
|
66
|
+
if (warning.message)
|
|
67
|
+
console.log(`Warning: ${warning.message}`);
|
|
68
|
+
}
|
|
69
|
+
console.log("Tip: run `npx nexarch check-in` to refresh your lease before delivery actions.");
|
|
70
|
+
}
|
|
61
71
|
if (!result.command) {
|
|
62
72
|
console.log(`Command ${id} was not claimed.`);
|
|
63
73
|
if (result.reason)
|
|
@@ -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,11 +213,13 @@ 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."}
|
|
205
221
|
|
|
206
|
-
## Starter template
|
|
222
|
+
${proposal.proposal.requiresPolicyReview ? `> Policy review is required before activation. Review docs/architecture/policy-controls.md and satisfy each required control before marking this application active.\n\n` : ""}## Starter template
|
|
207
223
|
|
|
208
224
|
- Template: ${template.label}
|
|
209
225
|
- Runtime: ${template.runtime}
|
|
@@ -216,25 +232,38 @@ ${proposal.application.description ?? "Application scaffold generated from a Nex
|
|
|
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
|
|
|
229
|
-
## Getting started
|
|
246
|
+
${proposal.proposal.preScaffoldInstructions ? `## Pre-scaffold instruction\n\n${proposal.proposal.preScaffoldInstructions}\n\n` : ""}## Getting started
|
|
230
247
|
|
|
231
248
|
${template.nextSteps.map((step, index) => `${index + 1}. ${step}`).join("\n")}
|
|
232
249
|
`;
|
|
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
|
|
263
|
+
? `- Activation gate: policy review required\n- Required policy controls: ${proposal.proposal.requiredPolicyControlIds?.length ?? 0}\n`
|
|
264
|
+
: "";
|
|
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`;
|
|
238
267
|
}
|
|
239
268
|
function buildPolicyMarkdown(proposal) {
|
|
240
269
|
if (proposal.policyControls.length === 0) {
|
|
@@ -242,12 +271,17 @@ function buildPolicyMarkdown(proposal) {
|
|
|
242
271
|
}
|
|
243
272
|
return `# Policy controls\n\n${proposal.policyControls.map((control) => {
|
|
244
273
|
const guidance = trimText(typeof control.controlAttributes.summary === "string" ? control.controlAttributes.summary : null);
|
|
245
|
-
const rules =
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
274
|
+
const rules = (control.rules ?? [])
|
|
275
|
+
.map((rule) => {
|
|
276
|
+
const title = trimText(rule.title) ?? trimText(rule.ruleId) ?? trimText(rule.name) ?? "Unnamed rule";
|
|
277
|
+
const level = trimText(rule.requirementLevel);
|
|
278
|
+
const mode = trimText(rule.ruleMode);
|
|
279
|
+
const text = trimText(rule.ruleText);
|
|
280
|
+
return `${title}${level ? ` [${level}]` : ""}${mode ? ` (${mode})` : ""}${text ? ` — ${text}` : ""}`;
|
|
281
|
+
})
|
|
282
|
+
.filter((value) => Boolean(value));
|
|
283
|
+
const auditRules = Array.isArray(control.auditRules) ? control.auditRules.length : 0;
|
|
284
|
+
return `## ${control.name}\n\n${control.description ?? "No description provided."}\n\n${guidance ? `Guidance: ${guidance}\n\n` : ""}${control.policyMarkdown ? `Policy markdown\n\n${control.policyMarkdown}\n\n` : ""}${rules.length > 0 ? `Rules\n\n${formatBulletList(rules)}\n\n` : ""}${auditRules > 0 ? `Audit rules: ${auditRules}\n` : ""}`;
|
|
251
285
|
}).join("\n")}`;
|
|
252
286
|
}
|
|
253
287
|
function buildTsConfig() {
|
|
@@ -490,6 +524,9 @@ export async function proposalsStart(args) {
|
|
|
490
524
|
const reason = parseOptionValue(args, "--reason") ?? "Application scaffold started from approved proposal workflow";
|
|
491
525
|
const repoUrl = parseOptionValue(args, "--repo");
|
|
492
526
|
const creds = requireCredentials();
|
|
527
|
+
const identity = loadIdentity();
|
|
528
|
+
const agentRef = identity.agentKey;
|
|
529
|
+
const lease = agentRef ? selectCheckInLease(creds.companyId, agentRef) : null;
|
|
493
530
|
const listRaw = await callMcpTool("nexarch_list_proposed_applications", { companyId: creds.companyId }, { companyId: creds.companyId });
|
|
494
531
|
const list = parseToolText(listRaw);
|
|
495
532
|
const proposals = list.proposals ?? [];
|
|
@@ -559,7 +596,10 @@ export async function proposalsStart(args) {
|
|
|
559
596
|
initiatedBy: "nexarch-cli",
|
|
560
597
|
command: "proposals start",
|
|
561
598
|
operatorEmail: creds.email,
|
|
599
|
+
agentRef,
|
|
562
600
|
},
|
|
601
|
+
reviewedPolicyControlIds: proposal.proposal.requiredPolicyControlIds ?? proposal.proposal.confirmedPolicyControlIds,
|
|
602
|
+
leaseToken: lease?.leaseToken,
|
|
563
603
|
}, { companyId: creds.companyId });
|
|
564
604
|
activated = parseToolText(activateRaw);
|
|
565
605
|
}
|
|
@@ -582,6 +622,10 @@ export async function proposalsStart(args) {
|
|
|
582
622
|
console.log(`- Runtime : ${scaffold.template.runtime}`);
|
|
583
623
|
console.log(`- Files : ${scaffold.createdFiles.length} created`);
|
|
584
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
|
+
}
|
|
585
629
|
console.log("\nNext steps:");
|
|
586
630
|
scaffold.template.nextSteps.forEach((step, index) => console.log(`${index + 1}. ${step}`));
|
|
587
631
|
console.log(`${scaffold.template.nextSteps.length + 1}. Use docs/architecture/nexarch-proposal.md and policy-controls.md while building.`);
|
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
|
@@ -189,8 +189,9 @@ Usage:
|
|
|
189
189
|
--json JSON output includes draftApplications[] and proposedApplications[]
|
|
190
190
|
nexarch proposals start
|
|
191
191
|
Start a new application workspace from a proposed NexArch app.
|
|
192
|
-
Lists proposed apps,
|
|
193
|
-
project scaffold, and activates the proposal to active
|
|
192
|
+
Lists proposed apps, shows policy review gates, writes a starter
|
|
193
|
+
project scaffold, and activates the proposal to active once
|
|
194
|
+
required policy controls are acknowledged.
|
|
194
195
|
Options: --id <applicationId>
|
|
195
196
|
--dir <path>
|
|
196
197
|
--reason <text>
|
|
@@ -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
|
+
}
|