@sonenta/cli 0.16.0 → 0.17.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/README.md +3 -3
- package/dist/index.js +207 -44
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ npx @sonenta/cli --help
|
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
18
|
# 1. Authenticate (the key must carry the `mcp:*` scope — see Auth below)
|
|
19
|
-
sonenta login --host https://api.sonenta.
|
|
19
|
+
sonenta login --host https://api.sonenta.dev --token vrb_live_<prefix>.<secret>
|
|
20
20
|
|
|
21
21
|
# 2. Bind the current directory to a project
|
|
22
22
|
sonenta init --project 069fc15d-… --version main
|
|
@@ -51,9 +51,9 @@ The credentials file shape:
|
|
|
51
51
|
|
|
52
52
|
```json
|
|
53
53
|
{
|
|
54
|
-
"default": "https://api.sonenta.
|
|
54
|
+
"default": "https://api.sonenta.dev",
|
|
55
55
|
"hosts": {
|
|
56
|
-
"https://api.sonenta.
|
|
56
|
+
"https://api.sonenta.dev": { "api_key": "vrb_live_…", "user_email": "…" },
|
|
57
57
|
"https://api.dev.sonenta.ca": { "api_key": "vrb_live_…" }
|
|
58
58
|
}
|
|
59
59
|
}
|
package/dist/index.js
CHANGED
|
@@ -1106,6 +1106,86 @@ async function requireAuth(opts = {}) {
|
|
|
1106
1106
|
return ctx;
|
|
1107
1107
|
}
|
|
1108
1108
|
|
|
1109
|
+
// src/mcpserver.ts
|
|
1110
|
+
import { promises as fs4 } from "fs";
|
|
1111
|
+
import { resolve as resolve3 } from "path";
|
|
1112
|
+
var MCP_JSON_FILENAME = ".mcp.json";
|
|
1113
|
+
var MCP_SERVER_KEY = "sonenta";
|
|
1114
|
+
var MCP_PACKAGE = "@sonenta/mcp";
|
|
1115
|
+
function buildServerBlock(env, opts = {}) {
|
|
1116
|
+
const e = {};
|
|
1117
|
+
if (opts.embedKey && env.apiKey) e.SONENTA_API_KEY = env.apiKey;
|
|
1118
|
+
if (env.host) e.SONENTA_BASE_URL = env.host.replace(/\/+$/, "");
|
|
1119
|
+
if (env.projectUuid) e.SONENTA_PROJECT = env.projectUuid;
|
|
1120
|
+
return { command: "npx", args: ["-y", MCP_PACKAGE], env: e };
|
|
1121
|
+
}
|
|
1122
|
+
async function readMcpJson(path) {
|
|
1123
|
+
try {
|
|
1124
|
+
const raw = await fs4.readFile(path, "utf8");
|
|
1125
|
+
const trimmed = raw.trim();
|
|
1126
|
+
if (!trimmed) return { json: {}, existed: true };
|
|
1127
|
+
const parsed = JSON.parse(trimmed);
|
|
1128
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1129
|
+
throw new Error(`${MCP_JSON_FILENAME} is not a JSON object`);
|
|
1130
|
+
}
|
|
1131
|
+
return { json: parsed, existed: true };
|
|
1132
|
+
} catch (err) {
|
|
1133
|
+
if (err?.code === "ENOENT") {
|
|
1134
|
+
return { json: {}, existed: false };
|
|
1135
|
+
}
|
|
1136
|
+
throw err;
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
async function wireMcpServer(env, opts = {}) {
|
|
1140
|
+
const baseDir = opts.baseDir ?? process.cwd();
|
|
1141
|
+
const path = resolve3(baseDir, MCP_JSON_FILENAME);
|
|
1142
|
+
const { json, existed } = await readMcpJson(path);
|
|
1143
|
+
const embeddedKey = Boolean(opts.embedKey && env.apiKey);
|
|
1144
|
+
const block = buildServerBlock(env, { embedKey: opts.embedKey });
|
|
1145
|
+
const servers = json.mcpServers && typeof json.mcpServers === "object" ? json.mcpServers : {};
|
|
1146
|
+
const prior = servers[MCP_SERVER_KEY];
|
|
1147
|
+
const identical = prior !== void 0 && deepEqual(prior, block);
|
|
1148
|
+
servers[MCP_SERVER_KEY] = block;
|
|
1149
|
+
json.mcpServers = servers;
|
|
1150
|
+
const action = !existed ? "created" : identical ? "unchanged" : "updated";
|
|
1151
|
+
if (action !== "unchanged") {
|
|
1152
|
+
await fs4.writeFile(path, JSON.stringify(json, null, 2) + "\n", "utf8");
|
|
1153
|
+
}
|
|
1154
|
+
let gitignoreUpdated = false;
|
|
1155
|
+
const wantGitignore = opts.gitignore ?? embeddedKey;
|
|
1156
|
+
if (wantGitignore) {
|
|
1157
|
+
gitignoreUpdated = await ensureGitignored(baseDir, MCP_JSON_FILENAME);
|
|
1158
|
+
}
|
|
1159
|
+
return { path, action, serverKey: MCP_SERVER_KEY, gitignoreUpdated, embeddedKey };
|
|
1160
|
+
}
|
|
1161
|
+
async function ensureGitignored(baseDir, entry) {
|
|
1162
|
+
const path = resolve3(baseDir, ".gitignore");
|
|
1163
|
+
let current = "";
|
|
1164
|
+
try {
|
|
1165
|
+
current = await fs4.readFile(path, "utf8");
|
|
1166
|
+
} catch (err) {
|
|
1167
|
+
if (err?.code !== "ENOENT") throw err;
|
|
1168
|
+
}
|
|
1169
|
+
const already = current.split(/\r?\n/).map((l) => l.trim()).some((l) => l === entry || l === `/${entry}`);
|
|
1170
|
+
if (already) return false;
|
|
1171
|
+
const prefix = current.length === 0 || current.endsWith("\n") ? "" : "\n";
|
|
1172
|
+
const header = current.length === 0 ? "" : "\n# Sonenta MCP server config (contains an API key)\n";
|
|
1173
|
+
await fs4.writeFile(path, `${current}${prefix}${header}${entry}
|
|
1174
|
+
`, "utf8");
|
|
1175
|
+
return true;
|
|
1176
|
+
}
|
|
1177
|
+
function deepEqual(a, b) {
|
|
1178
|
+
if (a === b) return true;
|
|
1179
|
+
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
|
|
1180
|
+
if (Array.isArray(a) !== Array.isArray(b)) return false;
|
|
1181
|
+
const ak = Object.keys(a);
|
|
1182
|
+
const bk = Object.keys(b);
|
|
1183
|
+
if (ak.length !== bk.length) return false;
|
|
1184
|
+
return ak.every(
|
|
1185
|
+
(k) => deepEqual(a[k], b[k])
|
|
1186
|
+
);
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1109
1189
|
// src/commands/agents.ts
|
|
1110
1190
|
var agentsCommand = new Command("agents").description("Install bundled Claude agents (e.g. sonenta-a11y) into .claude/agents/.").addCommand(
|
|
1111
1191
|
new Command("list").description("List the bundled agents available to install.").option("--dir <path>", "Project directory (default: current directory)").action(async (opts) => {
|
|
@@ -1122,20 +1202,55 @@ var agentsCommand = new Command("agents").description("Install bundled Claude ag
|
|
|
1122
1202
|
Install with: sonenta agents add <name>`);
|
|
1123
1203
|
})
|
|
1124
1204
|
).addCommand(
|
|
1125
|
-
new Command("add").description("Write a bundled agent definition into <dir>/.claude/agents/<name>.md.").argument("<name>", "Agent name (e.g. sonenta-a11y)").option("--dir <path>", "Project directory (default: current directory)").option("--host <url>", "Override host (otherwise from config/credentials)").option("--force", "Overwrite an existing agent definition", false).
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1205
|
+
new Command("add").description("Write a bundled agent definition into <dir>/.claude/agents/<name>.md.").argument("<name>", "Agent name (e.g. sonenta-a11y)").option("--dir <path>", "Project directory (default: current directory)").option("--host <url>", "Override host (otherwise from config/credentials)").option("--force", "Overwrite an existing agent definition", false).option("--no-mcp", "Skip auto-wiring the @sonenta/mcp server into .mcp.json").option(
|
|
1206
|
+
"--embed-key",
|
|
1207
|
+
"Bake the API key into .mcp.json (for CI / no-login); otherwise the server reads it from ~/.sonenta",
|
|
1208
|
+
false
|
|
1209
|
+
).action(
|
|
1210
|
+
async (name, opts) => {
|
|
1211
|
+
const ctx = await requireAuth({ hostOverride: opts.host });
|
|
1212
|
+
const path = await writeAgent(name, { baseDir: opts.dir, force: opts.force });
|
|
1213
|
+
console.log(`Wrote ${path}`);
|
|
1214
|
+
if (opts.mcp) {
|
|
1215
|
+
const wired = await wireMcpServer(
|
|
1216
|
+
{ apiKey: ctx.apiKey, host: ctx.host, projectUuid: ctx.projectUuid },
|
|
1217
|
+
{ baseDir: opts.dir, embedKey: opts.embedKey }
|
|
1218
|
+
);
|
|
1219
|
+
const verb = wired.action === "created" ? "Created" : wired.action === "updated" ? "Updated" : "Verified";
|
|
1220
|
+
console.log(
|
|
1221
|
+
`${verb} ${MCP_JSON_FILENAME} \u2192 connected the "${wired.serverKey}" server (${"npx -y @sonenta/mcp"}, host ${ctx.host}${ctx.projectUuid ? `, project ${ctx.projectUuid}` : ""}).`
|
|
1222
|
+
);
|
|
1223
|
+
if (wired.embeddedKey) {
|
|
1224
|
+
console.log(
|
|
1225
|
+
`Embedded your API key in ${MCP_JSON_FILENAME}` + (wired.gitignoreUpdated ? " and added it to .gitignore" : "") + " \u2014 keep it out of git."
|
|
1226
|
+
);
|
|
1227
|
+
} else {
|
|
1228
|
+
console.log(
|
|
1229
|
+
`No secret stored in ${MCP_JSON_FILENAME} \u2014 the server reads your API key from ~/.sonenta at startup (run \`sonenta login\` if it can't). Safe to commit.`
|
|
1230
|
+
);
|
|
1231
|
+
}
|
|
1232
|
+
if (!ctx.projectUuid) {
|
|
1233
|
+
console.log(
|
|
1234
|
+
"Note: no project bound \u2014 run `sonenta init --project <uuid>` so the agent's tools default to one project (or pass project_uuid per call)."
|
|
1235
|
+
);
|
|
1236
|
+
}
|
|
1237
|
+
console.log(
|
|
1238
|
+
`
|
|
1239
|
+
\u27F3 Reload your Claude Code session (or restart the MCP client) so the "${wired.serverKey}" server connects \u2014 then ${name}'s tools are available.`
|
|
1240
|
+
);
|
|
1241
|
+
} else {
|
|
1242
|
+
console.log(
|
|
1243
|
+
`
|
|
1244
|
+
Skipped .mcp.json (--no-mcp). The ${name} agent needs the Sonenta MCP server (npx -y @sonenta/mcp) with an mcp:* SONENTA_API_KEY to have any tools.`
|
|
1245
|
+
);
|
|
1246
|
+
}
|
|
1247
|
+
console.log(`Agent dir: ${AGENTS_DIR}/`);
|
|
1248
|
+
}
|
|
1249
|
+
)
|
|
1135
1250
|
);
|
|
1136
1251
|
|
|
1137
1252
|
// src/commands/export.ts
|
|
1138
|
-
import { promises as
|
|
1253
|
+
import { promises as fs5 } from "fs";
|
|
1139
1254
|
import { join as join2 } from "path";
|
|
1140
1255
|
import { Command as Command2 } from "commander";
|
|
1141
1256
|
|
|
@@ -1275,9 +1390,9 @@ var exportCommand = new Command2("export").description(
|
|
|
1275
1390
|
for (const [lang, nss] of Object.entries(collected)) {
|
|
1276
1391
|
for (const [ns, flat] of Object.entries(nss)) {
|
|
1277
1392
|
const dir = join2(opts.out, lang);
|
|
1278
|
-
await
|
|
1393
|
+
await fs5.mkdir(dir, { recursive: true });
|
|
1279
1394
|
const p = join2(dir, `${ns}.json`);
|
|
1280
|
-
await
|
|
1395
|
+
await fs5.writeFile(p, JSON.stringify(shape(flat), null, 2) + "\n", "utf8");
|
|
1281
1396
|
console.log(` ${p} ${Object.keys(flat).length} keys`);
|
|
1282
1397
|
files++;
|
|
1283
1398
|
}
|
|
@@ -1295,7 +1410,7 @@ var exportCommand = new Command2("export").description(
|
|
|
1295
1410
|
);
|
|
1296
1411
|
|
|
1297
1412
|
// src/commands/import.ts
|
|
1298
|
-
import { promises as
|
|
1413
|
+
import { promises as fs6 } from "fs";
|
|
1299
1414
|
import { basename, dirname as dirname3 } from "path";
|
|
1300
1415
|
import { Command as Command3 } from "commander";
|
|
1301
1416
|
function resolveLangNs(filePath, optLang, optNs) {
|
|
@@ -1313,7 +1428,7 @@ function resolveLangNs(filePath, optLang, optNs) {
|
|
|
1313
1428
|
return { lang, ns };
|
|
1314
1429
|
}
|
|
1315
1430
|
async function readTree(filePath) {
|
|
1316
|
-
const parsed = JSON.parse(await
|
|
1431
|
+
const parsed = JSON.parse(await fs6.readFile(filePath, "utf8"));
|
|
1317
1432
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1318
1433
|
throw new Error(`${filePath} is not a JSON object`);
|
|
1319
1434
|
}
|
|
@@ -1370,13 +1485,13 @@ var importCommand = new Command3("import").description(
|
|
|
1370
1485
|
|
|
1371
1486
|
// src/commands/init.ts
|
|
1372
1487
|
import { existsSync } from "fs";
|
|
1373
|
-
import { resolve as
|
|
1488
|
+
import { resolve as resolve5 } from "path";
|
|
1374
1489
|
import { Command as Command4 } from "commander";
|
|
1375
1490
|
|
|
1376
1491
|
// src/repodoc.ts
|
|
1377
|
-
import { promises as
|
|
1378
|
-
import { resolve as
|
|
1379
|
-
var DOC_API_HOST = "https://api.sonenta.
|
|
1492
|
+
import { promises as fs7 } from "fs";
|
|
1493
|
+
import { resolve as resolve4 } from "path";
|
|
1494
|
+
var DOC_API_HOST = "https://api.sonenta.dev";
|
|
1380
1495
|
var DOC_CDN_HOST = "https://cdn.sonenta.com";
|
|
1381
1496
|
var REPO_DOC_FILES = ["CLAUDE.md", "AGENTS.md"];
|
|
1382
1497
|
var BLOCK_BEGIN = "<!-- SONENTA:BEGIN \u2014 managed by `sonenta init`; edits between these markers are overwritten -->";
|
|
@@ -1464,13 +1579,13 @@ function renderManagedBlock(d) {
|
|
|
1464
1579
|
async function upsertManagedBlock(filePath, block) {
|
|
1465
1580
|
let existing = null;
|
|
1466
1581
|
try {
|
|
1467
|
-
existing = await
|
|
1582
|
+
existing = await fs7.readFile(filePath, "utf8");
|
|
1468
1583
|
} catch {
|
|
1469
1584
|
existing = null;
|
|
1470
1585
|
}
|
|
1471
1586
|
const normalizedBlock = block.endsWith("\n") ? block : block + "\n";
|
|
1472
1587
|
if (existing === null) {
|
|
1473
|
-
await
|
|
1588
|
+
await fs7.writeFile(filePath, normalizedBlock, "utf8");
|
|
1474
1589
|
return "created";
|
|
1475
1590
|
}
|
|
1476
1591
|
const begin = existing.indexOf(BLOCK_BEGIN);
|
|
@@ -1479,18 +1594,18 @@ async function upsertManagedBlock(filePath, block) {
|
|
|
1479
1594
|
const before = existing.slice(0, begin);
|
|
1480
1595
|
const after = existing.slice(end + BLOCK_END.length);
|
|
1481
1596
|
const blockCore = block.slice(0, block.indexOf(BLOCK_END) + BLOCK_END.length);
|
|
1482
|
-
await
|
|
1597
|
+
await fs7.writeFile(filePath, before + blockCore + after, "utf8");
|
|
1483
1598
|
return "updated";
|
|
1484
1599
|
}
|
|
1485
1600
|
const sep = existing.length === 0 ? "" : existing.endsWith("\n\n") ? "" : existing.endsWith("\n") ? "\n" : "\n\n";
|
|
1486
|
-
await
|
|
1601
|
+
await fs7.writeFile(filePath, existing + sep + normalizedBlock, "utf8");
|
|
1487
1602
|
return "inserted";
|
|
1488
1603
|
}
|
|
1489
1604
|
async function writeRepoDocs(dir, data) {
|
|
1490
1605
|
const block = renderManagedBlock(data);
|
|
1491
1606
|
const results = [];
|
|
1492
1607
|
for (const file of REPO_DOC_FILES) {
|
|
1493
|
-
const path =
|
|
1608
|
+
const path = resolve4(dir, file);
|
|
1494
1609
|
const action = await upsertManagedBlock(path, block);
|
|
1495
1610
|
results.push({ file, path, action });
|
|
1496
1611
|
}
|
|
@@ -1498,7 +1613,7 @@ async function writeRepoDocs(dir, data) {
|
|
|
1498
1613
|
}
|
|
1499
1614
|
|
|
1500
1615
|
// src/commands/init.ts
|
|
1501
|
-
var DEFAULT_HOST = "https://api.sonenta.
|
|
1616
|
+
var DEFAULT_HOST = "https://api.sonenta.dev";
|
|
1502
1617
|
var ACTION_LABEL = {
|
|
1503
1618
|
created: "Created",
|
|
1504
1619
|
updated: "Updated",
|
|
@@ -1539,9 +1654,13 @@ async function gatherRepoDocData(opts) {
|
|
|
1539
1654
|
}
|
|
1540
1655
|
var initCommand = new Command4("init").description(
|
|
1541
1656
|
"Scaffold sonenta.config.json AND write a managed Sonenta block into CLAUDE.md / AGENTS.md so coding agents know how this repo uses Sonenta."
|
|
1542
|
-
).option("--host <url>", "API base URL", DEFAULT_HOST).option("--project <uuid>", "Project UUID").option("--version <slug>", "Version slug (default: main)", "main").option("--force", "Overwrite an existing sonenta.config.json", false).option("--no-repo-doc", "Skip writing the managed block into CLAUDE.md / AGENTS.md").
|
|
1657
|
+
).option("--host <url>", "API base URL", DEFAULT_HOST).option("--project <uuid>", "Project UUID").option("--version <slug>", "Version slug (default: main)", "main").option("--force", "Overwrite an existing sonenta.config.json", false).option("--no-repo-doc", "Skip writing the managed block into CLAUDE.md / AGENTS.md").option("--no-mcp", "Skip auto-wiring the @sonenta/mcp server into .mcp.json").option(
|
|
1658
|
+
"--embed-key",
|
|
1659
|
+
"Bake the API key into .mcp.json (for CI / no-login); otherwise the server reads it from ~/.sonenta",
|
|
1660
|
+
false
|
|
1661
|
+
).action(
|
|
1543
1662
|
async (opts) => {
|
|
1544
|
-
const path =
|
|
1663
|
+
const path = resolve5(process.cwd(), CONFIG_FILENAME);
|
|
1545
1664
|
if (existsSync(path) && !opts.force) {
|
|
1546
1665
|
console.error(
|
|
1547
1666
|
`${CONFIG_FILENAME} already exists at ${path}. Pass --force to overwrite.`
|
|
@@ -1568,6 +1687,50 @@ var initCommand = new Command4("init").description(
|
|
|
1568
1687
|
} else {
|
|
1569
1688
|
console.log("Skipped CLAUDE.md / AGENTS.md (--no-repo-doc).");
|
|
1570
1689
|
}
|
|
1690
|
+
if (opts.mcp) {
|
|
1691
|
+
let liveHost = opts.host !== DEFAULT_HOST ? opts.host : void 0;
|
|
1692
|
+
if (!liveHost) {
|
|
1693
|
+
const creds = await readCredentials().catch(() => null);
|
|
1694
|
+
liveHost = creds?.default ?? void 0;
|
|
1695
|
+
}
|
|
1696
|
+
let resolved = null;
|
|
1697
|
+
try {
|
|
1698
|
+
const ctx = await resolveContext({ hostOverride: liveHost });
|
|
1699
|
+
resolved = { apiKey: ctx.apiKey, host: ctx.host, projectUuid: ctx.projectUuid };
|
|
1700
|
+
} catch {
|
|
1701
|
+
resolved = opts.embedKey ? null : { host: liveHost ?? opts.host, projectUuid: opts.project };
|
|
1702
|
+
}
|
|
1703
|
+
if (resolved) {
|
|
1704
|
+
const wired = await wireMcpServer(
|
|
1705
|
+
{
|
|
1706
|
+
apiKey: resolved.apiKey,
|
|
1707
|
+
host: resolved.host,
|
|
1708
|
+
projectUuid: resolved.projectUuid ?? opts.project
|
|
1709
|
+
},
|
|
1710
|
+
{ embedKey: opts.embedKey }
|
|
1711
|
+
);
|
|
1712
|
+
const verb = wired.action === "created" ? "Created" : wired.action === "updated" ? "Updated" : "Verified";
|
|
1713
|
+
console.log(
|
|
1714
|
+
`${verb} ${MCP_JSON_FILENAME} \u2192 connected the "${wired.serverKey}" server (npx -y @sonenta/mcp${resolved.host ? `, host ${resolved.host}` : ""}).`
|
|
1715
|
+
);
|
|
1716
|
+
if (wired.embeddedKey) {
|
|
1717
|
+
console.log(
|
|
1718
|
+
`Embedded your API key in ${MCP_JSON_FILENAME}` + (wired.gitignoreUpdated ? " and added it to .gitignore" : "") + " \u2014 keep it out of git."
|
|
1719
|
+
);
|
|
1720
|
+
} else {
|
|
1721
|
+
console.log(
|
|
1722
|
+
`No secret stored in ${MCP_JSON_FILENAME} \u2014 the server reads your API key from ~/.sonenta at startup (run \`sonenta login\` if it can't). Safe to commit.`
|
|
1723
|
+
);
|
|
1724
|
+
}
|
|
1725
|
+
console.log(
|
|
1726
|
+
`\u27F3 Reload your Claude Code session so the "${wired.serverKey}" server connects.`
|
|
1727
|
+
);
|
|
1728
|
+
} else {
|
|
1729
|
+
console.log(
|
|
1730
|
+
`Note: skipped ${MCP_JSON_FILENAME} wiring (--embed-key needs a login). Run \`sonenta login\` then \`sonenta agents add <name>\`.`
|
|
1731
|
+
);
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1571
1734
|
if (!opts.project) {
|
|
1572
1735
|
console.log(
|
|
1573
1736
|
"Tip: pass --project <uuid> to bind this directory to a specific project (or edit project_uuid in the file later), then re-run `sonenta init --force`."
|
|
@@ -1596,8 +1759,8 @@ async function promptLine(message) {
|
|
|
1596
1759
|
if (!process.stdin.isTTY) return "";
|
|
1597
1760
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1598
1761
|
try {
|
|
1599
|
-
return await new Promise((
|
|
1600
|
-
rl.question(message, (answer) =>
|
|
1762
|
+
return await new Promise((resolve7) => {
|
|
1763
|
+
rl.question(message, (answer) => resolve7(answer.trim()));
|
|
1601
1764
|
});
|
|
1602
1765
|
} finally {
|
|
1603
1766
|
rl.close();
|
|
@@ -1613,7 +1776,7 @@ async function promptSecret(message) {
|
|
|
1613
1776
|
process.stdout.write(message);
|
|
1614
1777
|
process.stdin.setRawMode(true);
|
|
1615
1778
|
process.stdin.resume();
|
|
1616
|
-
return await new Promise((
|
|
1779
|
+
return await new Promise((resolve7) => {
|
|
1617
1780
|
let buffer = "";
|
|
1618
1781
|
const onData = (chunk) => {
|
|
1619
1782
|
for (const byte of chunk) {
|
|
@@ -1622,7 +1785,7 @@ async function promptSecret(message) {
|
|
|
1622
1785
|
process.stdin.removeListener("data", onData);
|
|
1623
1786
|
process.stdin.setRawMode(false);
|
|
1624
1787
|
process.stdin.pause();
|
|
1625
|
-
|
|
1788
|
+
resolve7(buffer);
|
|
1626
1789
|
return;
|
|
1627
1790
|
}
|
|
1628
1791
|
if (byte === CTRL_C) {
|
|
@@ -1651,10 +1814,10 @@ async function promptSecret(message) {
|
|
|
1651
1814
|
var TOKEN_REGEX = /^vrb_[a-z]+_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/;
|
|
1652
1815
|
var loginCommand = new Command6("login").description(
|
|
1653
1816
|
"Store an API key for a host. Token resolution order: --token, SONENTA_TOKEN env, then interactive prompt (TTY only)."
|
|
1654
|
-
).option("--host <url>", "API base URL", "https://api.sonenta.
|
|
1817
|
+
).option("--host <url>", "API base URL", "https://api.sonenta.dev").option("--token <vrb_live_\u2026>", "API key token (prefix.secret form)").option("--email <email>", "User email associated with the token (optional)").option("--default", "Set this host as the default for future commands", false).action(async (opts) => {
|
|
1655
1818
|
let host = opts.host;
|
|
1656
1819
|
if (!host && process.stdin.isTTY) {
|
|
1657
|
-
host = await promptLine("Host (default https://api.sonenta.
|
|
1820
|
+
host = await promptLine("Host (default https://api.sonenta.dev): ") || "https://api.sonenta.dev";
|
|
1658
1821
|
}
|
|
1659
1822
|
let token = opts.token ?? (process.env.SONENTA_TOKEN ?? process.env.VERBUMIA_TOKEN) ?? "";
|
|
1660
1823
|
if (!token && process.stdin.isTTY) {
|
|
@@ -1773,14 +1936,14 @@ var projectsCommand = new Command9("projects").description("Inspect Verbumia pro
|
|
|
1773
1936
|
import { Command as Command10 } from "commander";
|
|
1774
1937
|
|
|
1775
1938
|
// src/locales.ts
|
|
1776
|
-
import { promises as
|
|
1777
|
-
import { join as join3, resolve as
|
|
1939
|
+
import { promises as fs8 } from "fs";
|
|
1940
|
+
import { join as join3, resolve as resolve6 } from "path";
|
|
1778
1941
|
var DEFAULT_LOCALES_DIR = "locales";
|
|
1779
1942
|
async function listLocaleFiles(rootDir) {
|
|
1780
|
-
const root =
|
|
1943
|
+
const root = resolve6(rootDir);
|
|
1781
1944
|
let langDirs;
|
|
1782
1945
|
try {
|
|
1783
|
-
langDirs = await
|
|
1946
|
+
langDirs = await fs8.readdir(root);
|
|
1784
1947
|
} catch {
|
|
1785
1948
|
return [];
|
|
1786
1949
|
}
|
|
@@ -1789,12 +1952,12 @@ async function listLocaleFiles(rootDir) {
|
|
|
1789
1952
|
const langPath = join3(root, lang);
|
|
1790
1953
|
let stat;
|
|
1791
1954
|
try {
|
|
1792
|
-
stat = await
|
|
1955
|
+
stat = await fs8.stat(langPath);
|
|
1793
1956
|
} catch {
|
|
1794
1957
|
continue;
|
|
1795
1958
|
}
|
|
1796
1959
|
if (!stat.isDirectory()) continue;
|
|
1797
|
-
const files = await
|
|
1960
|
+
const files = await fs8.readdir(langPath);
|
|
1798
1961
|
for (const f of files) {
|
|
1799
1962
|
if (!f.endsWith(".json")) continue;
|
|
1800
1963
|
out.push({ lang, namespace: f.replace(/\.json$/, ""), path: join3(langPath, f) });
|
|
@@ -1803,7 +1966,7 @@ async function listLocaleFiles(rootDir) {
|
|
|
1803
1966
|
return out;
|
|
1804
1967
|
}
|
|
1805
1968
|
async function readLocaleFile(path) {
|
|
1806
|
-
const raw = await
|
|
1969
|
+
const raw = await fs8.readFile(path, "utf8");
|
|
1807
1970
|
const parsed = JSON.parse(raw);
|
|
1808
1971
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1809
1972
|
throw new Error(`${path} is not a flat object`);
|
|
@@ -1819,12 +1982,12 @@ async function readLocaleFile(path) {
|
|
|
1819
1982
|
}
|
|
1820
1983
|
async function writeLocaleFile(rootDir, lang, namespace, values) {
|
|
1821
1984
|
const dir = join3(rootDir, lang);
|
|
1822
|
-
await
|
|
1985
|
+
await fs8.mkdir(dir, { recursive: true });
|
|
1823
1986
|
const path = join3(dir, `${namespace}.json`);
|
|
1824
1987
|
const sorted = Object.fromEntries(
|
|
1825
1988
|
Object.entries(values).sort(([a], [b]) => a.localeCompare(b))
|
|
1826
1989
|
);
|
|
1827
|
-
await
|
|
1990
|
+
await fs8.writeFile(path, JSON.stringify(sorted, null, 2) + "\n", "utf8");
|
|
1828
1991
|
return path;
|
|
1829
1992
|
}
|
|
1830
1993
|
function diffFlat(local, remote) {
|
|
@@ -1963,7 +2126,7 @@ var releasesCommand = new Command12("releases").description("Manage CDN releases
|
|
|
1963
2126
|
);
|
|
1964
2127
|
|
|
1965
2128
|
// src/commands/snapshot.ts
|
|
1966
|
-
import { promises as
|
|
2129
|
+
import { promises as fs9 } from "fs";
|
|
1967
2130
|
import { Command as Command13 } from "commander";
|
|
1968
2131
|
function bundleUrl(cdnBase, project, version, lang, ns) {
|
|
1969
2132
|
return `${cdnBase.replace(/\/+$/, "")}/p/${project}/${version}/latest/${lang}/${ns}.json`;
|
|
@@ -2030,7 +2193,7 @@ var snapshotCommand = new Command13("snapshot").description(
|
|
|
2030
2193
|
opts.format === "json" ? "json" : "ts"
|
|
2031
2194
|
);
|
|
2032
2195
|
if (opts.out) {
|
|
2033
|
-
await
|
|
2196
|
+
await fs9.writeFile(opts.out, output, "utf8");
|
|
2034
2197
|
console.log(
|
|
2035
2198
|
`wrote ${opts.out}: ${fetched} bundle(s)` + (missing ? `, ${missing} not published (404)` : "")
|
|
2036
2199
|
);
|