@plune-ai/cli 0.3.0 → 0.5.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 +19 -0
- package/dist/cli.cjs +351 -9
- package/dist/index.cjs +220 -3
- package/dist/index.d.cts +588 -1
- package/dist/index.d.ts +588 -1
- package/dist/index.js +206 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -315,7 +315,7 @@ function normalizeProviderError(err, secret, envVar) {
|
|
|
315
315
|
}
|
|
316
316
|
|
|
317
317
|
// src/providers/retry.ts
|
|
318
|
-
var defaultSleep = (ms) => new Promise((
|
|
318
|
+
var defaultSleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
319
319
|
function retryAfterMs(err) {
|
|
320
320
|
if (typeof err !== "object" || err === null || !("headers" in err)) return void 0;
|
|
321
321
|
const raw = readHeader(err.headers, "retry-after");
|
|
@@ -1460,6 +1460,210 @@ async function handleRun(options, depsFactory) {
|
|
|
1460
1460
|
deps.cache.close();
|
|
1461
1461
|
}
|
|
1462
1462
|
}
|
|
1463
|
+
|
|
1464
|
+
// src/cli/commands/sync.ts
|
|
1465
|
+
import * as fs6 from "fs";
|
|
1466
|
+
import * as path5 from "path";
|
|
1467
|
+
|
|
1468
|
+
// src/types/run-result-schema.ts
|
|
1469
|
+
import { z as z2 } from "zod";
|
|
1470
|
+
var RUN_RESULT_SCHEMA_VERSION = 1;
|
|
1471
|
+
var binaryVerdictSchema = z2.object({
|
|
1472
|
+
question: z2.string().min(1),
|
|
1473
|
+
passed: z2.boolean()
|
|
1474
|
+
});
|
|
1475
|
+
var assertionResultRecordSchema = z2.object({
|
|
1476
|
+
type: z2.string(),
|
|
1477
|
+
passed: z2.boolean(),
|
|
1478
|
+
score: z2.number().optional(),
|
|
1479
|
+
reason: z2.string().optional(),
|
|
1480
|
+
binaryVerdicts: z2.array(binaryVerdictSchema).optional()
|
|
1481
|
+
});
|
|
1482
|
+
var usageSchema = z2.object({
|
|
1483
|
+
input_tokens: z2.number(),
|
|
1484
|
+
output_tokens: z2.number(),
|
|
1485
|
+
cost_usd: z2.number()
|
|
1486
|
+
});
|
|
1487
|
+
var runErrorSchema = z2.object({
|
|
1488
|
+
code: z2.string(),
|
|
1489
|
+
message: z2.string()
|
|
1490
|
+
});
|
|
1491
|
+
var rowResultSchema = z2.object({
|
|
1492
|
+
vars: z2.record(z2.unknown()),
|
|
1493
|
+
output: z2.string().nullable(),
|
|
1494
|
+
cached: z2.boolean(),
|
|
1495
|
+
usage: usageSchema.optional(),
|
|
1496
|
+
latency_ms: z2.number().optional(),
|
|
1497
|
+
error: runErrorSchema.optional(),
|
|
1498
|
+
assertions: z2.array(assertionResultRecordSchema)
|
|
1499
|
+
});
|
|
1500
|
+
var summarySchema = z2.object({
|
|
1501
|
+
total: z2.number(),
|
|
1502
|
+
passed: z2.number(),
|
|
1503
|
+
failed: z2.number(),
|
|
1504
|
+
errored: z2.number(),
|
|
1505
|
+
cost_usd: z2.number(),
|
|
1506
|
+
duration_ms: z2.number()
|
|
1507
|
+
});
|
|
1508
|
+
var evalResultSchema = z2.object({
|
|
1509
|
+
id: z2.string(),
|
|
1510
|
+
tags: z2.array(z2.string()),
|
|
1511
|
+
rows: z2.array(rowResultSchema),
|
|
1512
|
+
passed: z2.boolean()
|
|
1513
|
+
});
|
|
1514
|
+
var runResultSchema = z2.object({
|
|
1515
|
+
schemaVersion: z2.literal(RUN_RESULT_SCHEMA_VERSION),
|
|
1516
|
+
plune_version: z2.string(),
|
|
1517
|
+
started_at: z2.string(),
|
|
1518
|
+
finished_at: z2.string(),
|
|
1519
|
+
config_hash: z2.string(),
|
|
1520
|
+
summary: summarySchema,
|
|
1521
|
+
evals: z2.array(evalResultSchema)
|
|
1522
|
+
});
|
|
1523
|
+
function parseRunResult(value) {
|
|
1524
|
+
return runResultSchema.parse(value);
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
// src/cli/credentials.ts
|
|
1528
|
+
import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync3, rmSync, writeFileSync as writeFileSync2 } from "fs";
|
|
1529
|
+
import { homedir } from "os";
|
|
1530
|
+
import { dirname as dirname3, join as join3 } from "path";
|
|
1531
|
+
function credentialsFile() {
|
|
1532
|
+
const base = process.env["XDG_CONFIG_HOME"]?.trim() || join3(homedir(), ".config");
|
|
1533
|
+
return join3(base, "plune", "credentials.json");
|
|
1534
|
+
}
|
|
1535
|
+
function loadToken() {
|
|
1536
|
+
try {
|
|
1537
|
+
const parsed = JSON.parse(readFileSync3(credentialsFile(), "utf-8"));
|
|
1538
|
+
return typeof parsed.token === "string" && parsed.token.length > 0 ? parsed.token : null;
|
|
1539
|
+
} catch {
|
|
1540
|
+
return null;
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
// src/cli/commands/sync.ts
|
|
1545
|
+
var DEFAULT_API_URL = "https://beta-api.plune.ai";
|
|
1546
|
+
var NotLoggedInError = class extends Error {
|
|
1547
|
+
constructor() {
|
|
1548
|
+
super('Not logged in. Run "plune login" first to save your API token.');
|
|
1549
|
+
this.name = "NotLoggedInError";
|
|
1550
|
+
}
|
|
1551
|
+
};
|
|
1552
|
+
var TokenRejectedError = class extends Error {
|
|
1553
|
+
constructor() {
|
|
1554
|
+
super(
|
|
1555
|
+
'Your API token was rejected (revoked or invalid). Run "plune login" with a fresh token.'
|
|
1556
|
+
);
|
|
1557
|
+
this.name = "TokenRejectedError";
|
|
1558
|
+
}
|
|
1559
|
+
};
|
|
1560
|
+
var SyncFileError = class extends Error {
|
|
1561
|
+
constructor(message) {
|
|
1562
|
+
super(message);
|
|
1563
|
+
this.name = "SyncFileError";
|
|
1564
|
+
}
|
|
1565
|
+
};
|
|
1566
|
+
var SyncNetworkError = class extends Error {
|
|
1567
|
+
constructor(apiUrl) {
|
|
1568
|
+
super(
|
|
1569
|
+
`Could not reach the Plune API at ${apiUrl}. Check your connection (or PLUNE_API_URL) and try again.`
|
|
1570
|
+
);
|
|
1571
|
+
this.name = "SyncNetworkError";
|
|
1572
|
+
}
|
|
1573
|
+
};
|
|
1574
|
+
var SyncHttpError = class extends Error {
|
|
1575
|
+
constructor(status, detail) {
|
|
1576
|
+
super(`Sync failed \u2014 the server returned HTTP ${status}${detail ? ` (${detail})` : ""}.`);
|
|
1577
|
+
this.status = status;
|
|
1578
|
+
this.name = "SyncHttpError";
|
|
1579
|
+
}
|
|
1580
|
+
status;
|
|
1581
|
+
};
|
|
1582
|
+
function resolveApiUrl(explicit) {
|
|
1583
|
+
const raw = explicit ?? process.env["PLUNE_API_URL"] ?? DEFAULT_API_URL;
|
|
1584
|
+
return raw.replace(/\/+$/, "");
|
|
1585
|
+
}
|
|
1586
|
+
function readRun(cwd, file) {
|
|
1587
|
+
const target = file !== void 0 ? path5.resolve(cwd, file) : path5.join(cwd, ".plune", "last-run.json");
|
|
1588
|
+
let raw;
|
|
1589
|
+
try {
|
|
1590
|
+
raw = fs6.readFileSync(target, "utf8");
|
|
1591
|
+
} catch {
|
|
1592
|
+
throw new SyncFileError(
|
|
1593
|
+
file !== void 0 ? `Run file not found: ${target}` : 'No run to sync. Run "plune run" first (or pass --file <path>).'
|
|
1594
|
+
);
|
|
1595
|
+
}
|
|
1596
|
+
let json;
|
|
1597
|
+
try {
|
|
1598
|
+
json = JSON.parse(raw);
|
|
1599
|
+
} catch {
|
|
1600
|
+
throw new SyncFileError(`Run file is not valid JSON: ${target}`);
|
|
1601
|
+
}
|
|
1602
|
+
try {
|
|
1603
|
+
return parseRunResult(json);
|
|
1604
|
+
} catch {
|
|
1605
|
+
throw new SyncFileError(`Run file does not match the RunResult schema: ${target}`);
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
async function safeErrorDetail(res) {
|
|
1609
|
+
try {
|
|
1610
|
+
const body = await res.json();
|
|
1611
|
+
return typeof body.error === "string" ? body.error : "";
|
|
1612
|
+
} catch {
|
|
1613
|
+
return "";
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
async function handleSync(deps = {}) {
|
|
1617
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
1618
|
+
const apiUrl = resolveApiUrl(deps.apiUrl);
|
|
1619
|
+
const token = (deps.loadToken ?? loadToken)();
|
|
1620
|
+
if (token === null) throw new NotLoggedInError();
|
|
1621
|
+
const run = readRun(cwd, deps.file);
|
|
1622
|
+
const doFetch = deps.fetchImpl ?? fetch;
|
|
1623
|
+
const endpoint = `${apiUrl}/v1/runs`;
|
|
1624
|
+
let res;
|
|
1625
|
+
try {
|
|
1626
|
+
res = await doFetch(endpoint, {
|
|
1627
|
+
method: "POST",
|
|
1628
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
|
|
1629
|
+
body: JSON.stringify(run)
|
|
1630
|
+
});
|
|
1631
|
+
} catch {
|
|
1632
|
+
throw new SyncNetworkError(apiUrl);
|
|
1633
|
+
}
|
|
1634
|
+
if (!res.ok) {
|
|
1635
|
+
const detail = await safeErrorDetail(res);
|
|
1636
|
+
if (res.status === 401) throw new TokenRejectedError();
|
|
1637
|
+
throw new SyncHttpError(res.status, detail);
|
|
1638
|
+
}
|
|
1639
|
+
const body = await res.json().catch(() => ({}));
|
|
1640
|
+
const id = typeof body.id === "string" ? body.id : "";
|
|
1641
|
+
return { id, url: `${endpoint}/${id}` };
|
|
1642
|
+
}
|
|
1643
|
+
function reportSyncFailure(err, write) {
|
|
1644
|
+
if (err instanceof NotLoggedInError || err instanceof TokenRejectedError || err instanceof SyncFileError) {
|
|
1645
|
+
write(err.message + "\n");
|
|
1646
|
+
return 2;
|
|
1647
|
+
}
|
|
1648
|
+
if (err instanceof SyncNetworkError || err instanceof SyncHttpError) {
|
|
1649
|
+
write(err.message + "\n");
|
|
1650
|
+
return 1;
|
|
1651
|
+
}
|
|
1652
|
+
return null;
|
|
1653
|
+
}
|
|
1463
1654
|
export {
|
|
1464
|
-
|
|
1655
|
+
NotLoggedInError,
|
|
1656
|
+
RUN_RESULT_SCHEMA_VERSION,
|
|
1657
|
+
SyncFileError,
|
|
1658
|
+
SyncHttpError,
|
|
1659
|
+
SyncNetworkError,
|
|
1660
|
+
TokenRejectedError,
|
|
1661
|
+
assertionConfigSchema,
|
|
1662
|
+
assertionResultRecordSchema,
|
|
1663
|
+
binaryVerdictSchema,
|
|
1664
|
+
parseRunResult,
|
|
1665
|
+
reportSyncFailure,
|
|
1666
|
+
handleRun as run,
|
|
1667
|
+
runResultSchema,
|
|
1668
|
+
handleSync as sync
|
|
1465
1669
|
};
|
package/package.json
CHANGED