@nathapp/nax 0.89.0 → 0.90.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.
- package/dist/nax.js +90 -34
- package/package.json +2 -2
package/dist/nax.js
CHANGED
|
@@ -48669,7 +48669,7 @@ var package_default;
|
|
|
48669
48669
|
var init_package = __esm(() => {
|
|
48670
48670
|
package_default = {
|
|
48671
48671
|
name: "@nathapp/nax",
|
|
48672
|
-
version: "0.
|
|
48672
|
+
version: "0.90.0",
|
|
48673
48673
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
48674
48674
|
type: "module",
|
|
48675
48675
|
bin: {
|
|
@@ -48755,7 +48755,7 @@ var init_package = __esm(() => {
|
|
|
48755
48755
|
"@agentclientprotocol/sdk": "~1.7.0",
|
|
48756
48756
|
"@anthropic-ai/sandbox-runtime": "0.0.77",
|
|
48757
48757
|
"@modelcontextprotocol/sdk": "^1.32.1",
|
|
48758
|
-
"@nathapp/nax-ai": "0.
|
|
48758
|
+
"@nathapp/nax-ai": "0.90.0",
|
|
48759
48759
|
chalk: "^5.6.2",
|
|
48760
48760
|
commander: "^13.1.0",
|
|
48761
48761
|
ink: "^6.7.0",
|
|
@@ -73074,8 +73074,8 @@ var init_version = __esm(() => {
|
|
|
73074
73074
|
NAX_AI_VERSION = CATALOG_VERSION;
|
|
73075
73075
|
NAX_COMMIT = (() => {
|
|
73076
73076
|
try {
|
|
73077
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
73078
|
-
return "
|
|
73077
|
+
if (/^[0-9a-f]{6,10}$/.test("297c3c7"))
|
|
73078
|
+
return "297c3c7";
|
|
73079
73079
|
} catch {}
|
|
73080
73080
|
try {
|
|
73081
73081
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -136471,6 +136471,48 @@ var init_trust2 = __esm(() => {
|
|
|
136471
136471
|
};
|
|
136472
136472
|
});
|
|
136473
136473
|
|
|
136474
|
+
// src/workflow/supplementary-items.ts
|
|
136475
|
+
function listedDecisionIds(openItems) {
|
|
136476
|
+
return new Set(openItems.flatMap((item) => [item.id, ...item.ruling?.decisionId ? [item.ruling.decisionId] : []]));
|
|
136477
|
+
}
|
|
136478
|
+
async function advisorItems2(dir, feature, openItems, outputDir) {
|
|
136479
|
+
const listed = listedDecisionIds(openItems);
|
|
136480
|
+
return (await readTrustedDecisions(dir, feature, outputDir)).filter((d) => !listed.has(d.id)).map((d) => ({ id: d.id, kind: "advisor", issue: oneLine3(`${d.kind} ${d.action.type}: ${d.rationale}`) }));
|
|
136481
|
+
}
|
|
136482
|
+
function cannotVerifyItems(lines) {
|
|
136483
|
+
const counts = new Map;
|
|
136484
|
+
return lines.map((line) => {
|
|
136485
|
+
const n = (counts.get(line.storyId) ?? 0) + 1;
|
|
136486
|
+
counts.set(line.storyId, n);
|
|
136487
|
+
return { id: `${line.storyId}:cv${n}`, kind: "cannot-verify", issue: line.text };
|
|
136488
|
+
});
|
|
136489
|
+
}
|
|
136490
|
+
async function orNone(source, read) {
|
|
136491
|
+
try {
|
|
136492
|
+
return await read();
|
|
136493
|
+
} catch (err) {
|
|
136494
|
+
getSafeLogger2()?.warn("workflow", "Skipping unreadable supplementary items", {
|
|
136495
|
+
storyId: "_run",
|
|
136496
|
+
source,
|
|
136497
|
+
error: errorMessage(err)
|
|
136498
|
+
});
|
|
136499
|
+
return [];
|
|
136500
|
+
}
|
|
136501
|
+
}
|
|
136502
|
+
async function loadSupplementaryItems(dir, feature, openItems, outputDir) {
|
|
136503
|
+
const advisor = await orNone("advisor ledger", () => advisorItems2(dir, feature, openItems, outputDir));
|
|
136504
|
+
const ledger = await orNone("run ledgers", async () => cannotVerifyItems((await collectLedgerItems(workflowFeatureDir(dir, feature))).cannotVerify));
|
|
136505
|
+
return [...advisor, ...ledger];
|
|
136506
|
+
}
|
|
136507
|
+
var oneLine3 = (text) => text.replace(/\s+/g, " ").trim();
|
|
136508
|
+
var init_supplementary_items = __esm(() => {
|
|
136509
|
+
init_internal();
|
|
136510
|
+
init_advisor();
|
|
136511
|
+
init_logger2();
|
|
136512
|
+
init_ledger_items();
|
|
136513
|
+
init_paths5();
|
|
136514
|
+
});
|
|
136515
|
+
|
|
136474
136516
|
// src/workflow/run-verdict.ts
|
|
136475
136517
|
import { basename as basename34 } from "path";
|
|
136476
136518
|
async function newestResult(dir, feature) {
|
|
@@ -136493,7 +136535,7 @@ function noResult(feature) {
|
|
|
136493
136535
|
feature
|
|
136494
136536
|
});
|
|
136495
136537
|
}
|
|
136496
|
-
async function loadRunVerdict(dir, feature) {
|
|
136538
|
+
async function loadRunVerdict(dir, feature, resolveOutputDir) {
|
|
136497
136539
|
validateFeatureName(feature);
|
|
136498
136540
|
const found = await newestResult(dir, feature);
|
|
136499
136541
|
if (!found)
|
|
@@ -136503,13 +136545,15 @@ async function loadRunVerdict(dir, feature) {
|
|
|
136503
136545
|
throw notStarted(feature, found.runId);
|
|
136504
136546
|
const filled = fillStoryStates(record, found.result);
|
|
136505
136547
|
const verdict = buildVerdict(filled);
|
|
136548
|
+
const openItems = verdict?.openItems ?? buildOpenItems(filled);
|
|
136506
136549
|
return {
|
|
136507
136550
|
runId: found.runId,
|
|
136508
136551
|
runDir: found.runDir,
|
|
136509
136552
|
result: found.result,
|
|
136510
136553
|
record,
|
|
136511
|
-
openItems
|
|
136512
|
-
reasons: record.reasons
|
|
136554
|
+
openItems,
|
|
136555
|
+
reasons: record.reasons,
|
|
136556
|
+
supplementaryItems: await loadSupplementaryItems(dir, feature, openItems, await resolveOutputDir(dir))
|
|
136513
136557
|
};
|
|
136514
136558
|
}
|
|
136515
136559
|
var init_run_verdict = __esm(() => {
|
|
@@ -136518,17 +136562,25 @@ var init_run_verdict = __esm(() => {
|
|
|
136518
136562
|
init_open_items();
|
|
136519
136563
|
init_paths5();
|
|
136520
136564
|
init_result();
|
|
136565
|
+
init_supplementary_items();
|
|
136521
136566
|
});
|
|
136522
136567
|
|
|
136523
136568
|
// src/cli/workflow-forge.ts
|
|
136569
|
+
import { basename as basename35 } from "path";
|
|
136524
136570
|
var _workflowForgeDeps;
|
|
136525
136571
|
var init_workflow_forge = __esm(() => {
|
|
136572
|
+
init_config();
|
|
136526
136573
|
init_forge();
|
|
136574
|
+
init_runtime3();
|
|
136527
136575
|
init_ci_wait();
|
|
136528
136576
|
_workflowForgeDeps = {
|
|
136529
136577
|
forge: defaultForgeDeps,
|
|
136530
136578
|
now: () => new Date,
|
|
136531
|
-
waitForChecks
|
|
136579
|
+
waitForChecks,
|
|
136580
|
+
resolveOutputDir: async (dir) => {
|
|
136581
|
+
const config = await loadConfig(dir).catch(() => null);
|
|
136582
|
+
return projectOutputDir(config?.name?.trim() || basename35(dir), config?.outputDir);
|
|
136583
|
+
}
|
|
136532
136584
|
};
|
|
136533
136585
|
});
|
|
136534
136586
|
|
|
@@ -136591,8 +136643,8 @@ function sidecarKindFor(item) {
|
|
|
136591
136643
|
function openIds(items) {
|
|
136592
136644
|
return items.length === 0 ? "none" : items.map((item) => item.id).join(", ");
|
|
136593
136645
|
}
|
|
136594
|
-
function checkEntriesAgainstItems(entries, items) {
|
|
136595
|
-
const byId = new Map(items.map((item) => [item.id, item]));
|
|
136646
|
+
function checkEntriesAgainstItems(entries, items, supplementary = []) {
|
|
136647
|
+
const byId = new Map([...supplementary, ...items].map((item) => [item.id, item]));
|
|
136596
136648
|
for (const entry of entries) {
|
|
136597
136649
|
const item = byId.get(entry.id);
|
|
136598
136650
|
if (item === undefined) {
|
|
@@ -136955,13 +137007,13 @@ function reportOf(input) {
|
|
|
136955
137007
|
}
|
|
136956
137008
|
async function promote(opts, forgeDeps) {
|
|
136957
137009
|
validateFeatureName(opts.feature);
|
|
136958
|
-
const loaded = await loadRunVerdict(opts.dir, opts.feature);
|
|
137010
|
+
const loaded = await loadRunVerdict(opts.dir, opts.feature, forgeDeps.resolveOutputDir);
|
|
136959
137011
|
const url = draftUrl(loaded, opts.feature);
|
|
136960
137012
|
const forge = await draftForge(forgeDeps, opts.dir, url);
|
|
136961
137013
|
const expectedSha = await headSha2(forgeDeps.forge, opts.dir);
|
|
136962
137014
|
const found = await newestSidecar(loaded.runDir);
|
|
136963
137015
|
if (found)
|
|
136964
|
-
checkEntriesAgainstItems(found.sidecar.decisions, loaded.openItems);
|
|
137016
|
+
checkEntriesAgainstItems(found.sidecar.decisions, loaded.openItems, loaded.supplementaryItems);
|
|
136965
137017
|
const checks = [itemsRuled(loaded, found), await ciGreen({ forgeDeps, forge, url, dir: opts.dir, expectedSha })];
|
|
136966
137018
|
const target = { opts, loaded, url, forge, forgeDeps, found };
|
|
136967
137019
|
if (!opts.yes || !allPassed(checks))
|
|
@@ -137076,7 +137128,7 @@ function updatedSidecar(existing, input) {
|
|
|
137076
137128
|
async function writeRecorded(input) {
|
|
137077
137129
|
const found = await newestSidecar(input.loaded.runDir);
|
|
137078
137130
|
if (found) {
|
|
137079
|
-
checkEntriesAgainstItems(found.sidecar.decisions, input.loaded.openItems);
|
|
137131
|
+
checkEntriesAgainstItems(found.sidecar.decisions, input.loaded.openItems, input.loaded.supplementaryItems);
|
|
137080
137132
|
const sidecar = updatedSidecar(found.sidecar, input);
|
|
137081
137133
|
await writeSidecar(found.path, sidecar);
|
|
137082
137134
|
return { path: found.path, sidecar };
|
|
@@ -137104,9 +137156,9 @@ async function record4(opts, forgeDeps) {
|
|
|
137104
137156
|
validateFeatureName(opts.feature);
|
|
137105
137157
|
const outcome = outcomeOf2(opts);
|
|
137106
137158
|
const ctx = { dir: opts.dir, forgeDeps };
|
|
137107
|
-
const loaded = await loadRunVerdict(opts.dir, opts.feature);
|
|
137159
|
+
const loaded = await loadRunVerdict(opts.dir, opts.feature, forgeDeps.resolveOutputDir);
|
|
137108
137160
|
const entries = opts.decisions === undefined ? [] : await readDecisionsFile(opts.decisions);
|
|
137109
|
-
checkEntriesAgainstItems(entries, loaded.openItems);
|
|
137161
|
+
checkEntriesAgainstItems(entries, loaded.openItems, loaded.supplementaryItems);
|
|
137110
137162
|
const prUrl = outcome !== undefined && PR_OUTCOMES.includes(outcome) ? await readyPrUrl(outcome, loaded, ctx) : undefined;
|
|
137111
137163
|
const written = await writeRecorded({ opts, loaded, entries, outcome, prUrl, now: forgeDeps.now() });
|
|
137112
137164
|
return reportOf2(opts, loaded, written);
|
|
@@ -137159,11 +137211,12 @@ function reportFor2(dir, feature, loaded) {
|
|
|
137159
137211
|
...record.ci === undefined ? {} : { ci: record.ci },
|
|
137160
137212
|
...record.url === undefined ? {} : { url: record.url },
|
|
137161
137213
|
reasons: record.reasons,
|
|
137162
|
-
openItems: loaded.openItems
|
|
137214
|
+
openItems: loaded.openItems,
|
|
137215
|
+
supplementaryItems: loaded.supplementaryItems
|
|
137163
137216
|
};
|
|
137164
137217
|
}
|
|
137165
|
-
async function buildReport(dir, feature) {
|
|
137166
|
-
return reportFor2(dir, feature, await loadRunVerdict(dir, feature));
|
|
137218
|
+
async function buildReport(dir, feature, forgeDeps) {
|
|
137219
|
+
return reportFor2(dir, feature, await loadRunVerdict(dir, feature, forgeDeps.resolveOutputDir));
|
|
137167
137220
|
}
|
|
137168
137221
|
function exitCodeFor(report) {
|
|
137169
137222
|
return report.outcome === "ready" && report.openItems.length === 0 ? 0 : 2;
|
|
@@ -137171,16 +137224,19 @@ function exitCodeFor(report) {
|
|
|
137171
137224
|
function printPlain4(report, deps) {
|
|
137172
137225
|
const outcome = report.outcome ?? NOT_PUBLISHED2;
|
|
137173
137226
|
deps.log(`${report.feature}: ${outcome}${report.url ? ` ${report.url}` : ""}`);
|
|
137174
|
-
if (report.openItems.length === 0)
|
|
137227
|
+
if (report.openItems.length === 0)
|
|
137175
137228
|
deps.log(NO_ITEMS);
|
|
137176
|
-
return;
|
|
137177
|
-
}
|
|
137178
137229
|
for (const item of report.openItems)
|
|
137179
|
-
deps.log(
|
|
137230
|
+
deps.log(itemLine(item));
|
|
137231
|
+
if (report.supplementaryItems.length === 0)
|
|
137232
|
+
return;
|
|
137233
|
+
deps.log(SUPPLEMENTARY_HEADING);
|
|
137234
|
+
for (const item of report.supplementaryItems)
|
|
137235
|
+
deps.log(itemLine(item));
|
|
137180
137236
|
}
|
|
137181
|
-
async function workflowStatusCommand(opts, deps = _workflowCliDeps) {
|
|
137237
|
+
async function workflowStatusCommand(opts, deps = _workflowCliDeps, forgeDeps = _workflowForgeDeps) {
|
|
137182
137238
|
try {
|
|
137183
|
-
const report = await buildReport(opts.dir, opts.feature);
|
|
137239
|
+
const report = await buildReport(opts.dir, opts.feature, forgeDeps);
|
|
137184
137240
|
if (opts.json)
|
|
137185
137241
|
deps.log(JSON.stringify(report));
|
|
137186
137242
|
else
|
|
@@ -137205,7 +137261,7 @@ async function guarded4(deps, body) {
|
|
|
137205
137261
|
}
|
|
137206
137262
|
function registerWorkflowCommand(program, deps = _workflowCliDeps, forgeDeps = _workflowForgeDeps) {
|
|
137207
137263
|
const group = program.command("workflow").description("Inspect a feature's workflow run");
|
|
137208
|
-
group.command("status").description("Print the verdict for the feature's newest workflow run").requiredOption("-f, --feature <name>", "Feature name").option("-d, --dir <path>", "Project directory", process.cwd()).option("--json", "Emit one machine-readable JSON document", false).action((o) => guarded4(deps, () => workflowStatusCommand({ dir: o.dir, feature: o.feature, json: o.json === true }, deps)));
|
|
137264
|
+
group.command("status").description("Print the verdict for the feature's newest workflow run").requiredOption("-f, --feature <name>", "Feature name").option("-d, --dir <path>", "Project directory", process.cwd()).option("--json", "Emit one machine-readable JSON document", false).action((o) => guarded4(deps, () => workflowStatusCommand({ dir: o.dir, feature: o.feature, json: o.json === true }, deps, forgeDeps)));
|
|
137209
137265
|
group.command("record").description("Record a human's rulings and the run's outcome in the decisions sidecar").requiredOption("-f, --feature <name>", "Feature name").option("--decisions <file>", "A JSON array of decision entries").option("--outcome <outcome>", "One of pending, promoted, verified-ready, abandoned").option("-d, --dir <path>", "Project directory", process.cwd()).option("--json", "Emit one machine-readable JSON document", false).action((o) => guarded4(deps, () => workflowRecordCommand({
|
|
137210
137266
|
dir: o.dir,
|
|
137211
137267
|
feature: o.feature,
|
|
@@ -137216,7 +137272,7 @@ function registerWorkflowCommand(program, deps = _workflowCliDeps, forgeDeps = _
|
|
|
137216
137272
|
group.command("promote").description("Check a draft run and report whether its PR can be promoted").requiredOption("-f, --feature <name>", "Feature name").option("--yes", "Promote for real once every check passes", false).option("-d, --dir <path>", "Project directory", process.cwd()).option("--json", "Emit one machine-readable JSON document", false).action((o) => guarded4(deps, () => workflowPromoteCommand({ dir: o.dir, feature: o.feature, yes: o.yes === true, json: o.json === true }, deps, forgeDeps)));
|
|
137217
137273
|
return group;
|
|
137218
137274
|
}
|
|
137219
|
-
var _workflowCliDeps, NOT_PUBLISHED2 = "not published", NO_ITEMS = "No open items."
|
|
137275
|
+
var _workflowCliDeps, NOT_PUBLISHED2 = "not published", NO_ITEMS = "No open items.", SUPPLEMENTARY_HEADING = "Also to verify (not blocking):", itemLine = (item) => `- [${item.kind}] ${item.id} \u2014 ${item.issue}`;
|
|
137220
137276
|
var init_workflow2 = __esm(() => {
|
|
137221
137277
|
init_internal();
|
|
137222
137278
|
init_open_items();
|
|
@@ -149849,7 +149905,7 @@ var init_worktree_id2 = __esm(() => {
|
|
|
149849
149905
|
});
|
|
149850
149906
|
|
|
149851
149907
|
// src/bakeoff/contestant.ts
|
|
149852
|
-
import { basename as
|
|
149908
|
+
import { basename as basename39, join as join193 } from "path";
|
|
149853
149909
|
function aggregateTotals(metrics) {
|
|
149854
149910
|
let costUsd = 0;
|
|
149855
149911
|
let wallTimeMs = 0;
|
|
@@ -149879,7 +149935,7 @@ async function runContestant(agent, options, deps) {
|
|
|
149879
149935
|
}
|
|
149880
149936
|
};
|
|
149881
149937
|
const worktree = storyWorktreePath(options.projectRoot, storyId);
|
|
149882
|
-
const projectKey = options.config.name?.trim() ||
|
|
149938
|
+
const projectKey = options.config.name?.trim() || basename39(options.projectRoot);
|
|
149883
149939
|
const outputDir = join193(projectOutputDir(projectKey, options.outputDir), "bakeoff", feature, agent);
|
|
149884
149940
|
const context = {
|
|
149885
149941
|
profile: agent,
|
|
@@ -151773,7 +151829,7 @@ init_checkpoint();
|
|
|
151773
151829
|
init_runtime3();
|
|
151774
151830
|
init_feature_name();
|
|
151775
151831
|
import { existsSync as existsSync45 } from "fs";
|
|
151776
|
-
import { basename as
|
|
151832
|
+
import { basename as basename36, join as join188 } from "path";
|
|
151777
151833
|
async function defaultCheckpointExists(featureDir) {
|
|
151778
151834
|
if (!featureDir || !existsSync45(featureDir))
|
|
151779
151835
|
return false;
|
|
@@ -151870,7 +151926,7 @@ function registerResumeCommand(program) {
|
|
|
151870
151926
|
const globalNaxDir = globalConfigDir();
|
|
151871
151927
|
const hooks = await loadHooksConfig(naxDir, globalNaxDir);
|
|
151872
151928
|
applyResumeModeDeps(opts.featureDir ?? "", "auto");
|
|
151873
|
-
const projectKey = config.name?.trim() ||
|
|
151929
|
+
const projectKey = config.name?.trim() || basename36(dir);
|
|
151874
151930
|
const outputDir = projectOutputDir(projectKey, config.outputDir);
|
|
151875
151931
|
const statusFilePath = join188(outputDir, "status.json");
|
|
151876
151932
|
const runsDir = join188(outputDir, "features", feature, "runs");
|
|
@@ -152052,7 +152108,7 @@ init_execution2();
|
|
|
152052
152108
|
init_logger2();
|
|
152053
152109
|
init_runtime3();
|
|
152054
152110
|
import { mkdir as mkdir50, unlink as unlink13 } from "fs/promises";
|
|
152055
|
-
import { basename as
|
|
152111
|
+
import { basename as basename37, join as join190 } from "path";
|
|
152056
152112
|
var _unlockDeps = {
|
|
152057
152113
|
findProjectDir,
|
|
152058
152114
|
loadConfig,
|
|
@@ -152083,7 +152139,7 @@ function parseFeatureLockRecord(raw) {
|
|
|
152083
152139
|
async function resolveOutputDir3(workdir) {
|
|
152084
152140
|
const naxDir = _unlockDeps.findProjectDir(workdir);
|
|
152085
152141
|
const config = await _unlockDeps.loadConfig(naxDir ?? undefined);
|
|
152086
|
-
const projectKey = config.name?.trim() ||
|
|
152142
|
+
const projectKey = config.name?.trim() || basename37(workdir);
|
|
152087
152143
|
const outputDir = _unlockDeps.projectOutputDir(projectKey, config.outputDir);
|
|
152088
152144
|
return { outputDir, naxDir };
|
|
152089
152145
|
}
|
|
@@ -159895,7 +159951,7 @@ init_version();
|
|
|
159895
159951
|
init_source();
|
|
159896
159952
|
init_cli2();
|
|
159897
159953
|
import { existsSync as existsSync47, mkdirSync as mkdirSync9 } from "fs";
|
|
159898
|
-
import { basename as
|
|
159954
|
+
import { basename as basename38, join as join192 } from "path";
|
|
159899
159955
|
|
|
159900
159956
|
// src/cli/run-max-iterations.ts
|
|
159901
159957
|
function parseMaxIterationsFlag(raw) {
|
|
@@ -160268,7 +160324,7 @@ async function displayPlanConfirmation(prd, options) {
|
|
|
160268
160324
|
}
|
|
160269
160325
|
function initRunLogging(params) {
|
|
160270
160326
|
const { options, config, workdir, isTTY, logLevel, formatterMode } = params;
|
|
160271
|
-
const projectKey = config.name?.trim() ||
|
|
160327
|
+
const projectKey = config.name?.trim() || basename38(workdir);
|
|
160272
160328
|
const outputDir = projectOutputDir(projectKey, config.outputDir);
|
|
160273
160329
|
const runsDir = join192(outputDir, "features", options.feature, "runs");
|
|
160274
160330
|
mkdirSync9(runsDir, { recursive: true });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nathapp/nax",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.90.0",
|
|
4
4
|
"description": "AI Coding Agent Orchestrator — loops until done",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"@agentclientprotocol/sdk": "~1.7.0",
|
|
87
87
|
"@anthropic-ai/sandbox-runtime": "0.0.77",
|
|
88
88
|
"@modelcontextprotocol/sdk": "^1.32.1",
|
|
89
|
-
"@nathapp/nax-ai": "0.
|
|
89
|
+
"@nathapp/nax-ai": "0.90.0",
|
|
90
90
|
"chalk": "^5.6.2",
|
|
91
91
|
"commander": "^13.1.0",
|
|
92
92
|
"ink": "^6.7.0",
|