@kya-os/cli
Version:
CLI for MCP-I setup and management
184 lines ⢠7.88 kB
JavaScript
import chalk from "chalk";
import { existsSync } from "fs";
import { join } from "path";
import { showSuccess, showError, showInfo } from "../utils/prompts.js";
import { formatReceiptTs, isDeprecatedTimestamp } from "../utils/time.js";
/**
* List and manage stored receipts
*/
export async function receipts(options = {}) {
const { json = false, path: receiptPath = ".mcpi/receipts", verify = false, } = options;
if (!json) {
console.log(chalk.cyan("\nš Receipt Management\n"));
}
if (!existsSync(receiptPath)) {
const message = "No receipts found. Receipts are saved after registration and claim operations.";
if (json) {
console.log(JSON.stringify({ success: true, receipts: [], message }, null, 2));
}
else {
showInfo(message);
}
return;
}
try {
const fs = await import("fs/promises");
const files = await fs.readdir(receiptPath);
const receiptFiles = files.filter((f) => f.endsWith(".json"));
if (receiptFiles.length === 0) {
const message = "No receipt files found in the receipts directory.";
if (json) {
console.log(JSON.stringify({ success: true, receipts: [], message }, null, 2));
}
else {
showInfo(message);
}
return;
}
const receipts = [];
for (const file of receiptFiles) {
try {
const content = await fs.readFile(join(receiptPath, file), "utf-8");
const receipt = JSON.parse(content);
// Basic validation
let valid = true;
let error;
if (!receipt.ref || !receipt.contentHash || !receipt.action) {
valid = false;
error = "Missing required fields";
}
else if (!receipt.contentHash.match(/^sha256:[a-f0-9]{64}$/)) {
valid = false;
error = "Invalid content hash format";
}
else if (!["issue", "revoke"].includes(receipt.action)) {
valid = false;
error = "Invalid action type";
}
receipts.push({
...receipt,
filename: file,
valid,
error,
});
}
catch (error) {
receipts.push({
filename: file,
valid: false,
error: "Invalid JSON format",
});
}
}
if (json) {
const receiptData = await Promise.all(receipts.map(async (r) => ({
filename: r.filename,
ref: r.ref,
contentHash: r.contentHash,
action: r.action,
timestamp: r.ts,
logIndex: r.logIndex,
valid: r.valid,
error: r.error,
verified: verify ? await verifyReceipt(r) : undefined,
})));
const output = {
success: true,
receipts: receiptData,
total: receipts.length,
valid: receipts.filter((r) => r.valid).length,
invalid: receipts.filter((r) => !r.valid).length,
};
console.log(JSON.stringify(output, null, 2));
}
else {
console.log(`${chalk.bold("Found receipts:")} ${chalk.gray(`(${receipts.length} total)`)}`);
const validReceipts = receipts.filter((r) => r.valid);
const invalidReceipts = receipts.filter((r) => !r.valid);
if (validReceipts.length > 0) {
console.log(`\n${chalk.bold.green("ā Valid Receipts:")}`);
for (const receipt of validReceipts) {
console.log(`\n${chalk.bold("š")} ${chalk.cyan(receipt.filename)}`);
console.log(` Reference: ${chalk.gray(receipt.ref)}`);
console.log(` Action: ${chalk.green(receipt.action)}`);
console.log(` Content Hash: ${chalk.gray(receipt.contentHash)}`);
console.log(` Timestamp: ${chalk.gray(formatReceiptTs(receipt.ts))}`);
console.log(` Log Index: ${chalk.gray(receipt.logIndex)}`);
if (isDeprecatedTimestamp(receipt.ts)) {
console.log(` ${chalk.yellow("Warning: Receipt uses deprecated numeric timestamp format.")}`);
console.log(` ${chalk.gray("Future versions will require ISO 8601 format.")}`);
}
if (verify) {
const verification = await verifyReceipt(receipt);
const verifyColor = verification.valid ? chalk.green : chalk.red;
console.log(` Verification: ${verifyColor(verification.valid ? "ā Valid" : "ā Invalid")}`);
if (!verification.valid && verification.error) {
console.log(` Error: ${chalk.red(verification.error)}`);
}
}
}
}
if (invalidReceipts.length > 0) {
console.log(`\n${chalk.bold.red("ā Invalid Receipts:")}`);
for (const receipt of invalidReceipts) {
console.log(`\n${chalk.bold("š")} ${chalk.red(receipt.filename)}`);
console.log(` Error: ${chalk.red(receipt.error || "Unknown error")}`);
}
}
if (validReceipts.length > 0) {
showSuccess(`Found ${validReceipts.length} valid receipt${validReceipts.length === 1 ? "" : "s"}`);
}
if (invalidReceipts.length > 0) {
showError(`Found ${invalidReceipts.length} invalid receipt${invalidReceipts.length === 1 ? "" : "s"}`);
}
if (!verify && validReceipts.length > 0) {
console.log(`\n${chalk.gray("Use --verify to check receipt integrity against KTA log")}`);
}
}
}
catch (error) {
const errorMessage = `Failed to read receipts: ${error instanceof Error ? error.message : "Unknown error"}`;
if (json) {
console.log(JSON.stringify({
success: false,
error: errorMessage,
code: "XMCP_I_ERECEIPTS",
}, null, 2));
}
else {
showError(errorMessage);
}
}
}
/**
* Verify a receipt against the KTA log
*/
async function verifyReceipt(receipt) {
try {
// TODO: Implement actual receipt verification against KTA log root
// This is a placeholder implementation
// Basic format validation
if (!receipt.logRoot ||
!receipt.inclusionProof ||
!Array.isArray(receipt.inclusionProof)) {
return { valid: false, error: "Missing log root or inclusion proof" };
}
// Mock verification - in real implementation, this would:
// 1. Fetch current log root from KTA
// 2. Verify inclusion proof against the log root
// 3. Check that the content hash matches the receipt
// For now, just return valid for well-formed receipts
const isWellFormed = receipt.logRoot.length > 0 && receipt.inclusionProof.length > 0;
return {
valid: isWellFormed,
error: isWellFormed ? undefined : "Malformed receipt structure",
};
}
catch (error) {
return {
valid: false,
error: error instanceof Error ? error.message : "Verification failed",
};
}
}
//# sourceMappingURL=receipts.js.map