Skip to content
Open

. #114

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
"singleQuote": false,
"trailingComma": "none",
"printWidth": 100

}
86 changes: 86 additions & 0 deletions apps/agent-client/src/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,92 @@ describe("CLI Validation", () => {
expect(error.stdout).toContain("Usage:");
}
});

it("exits with error when query is missing with --receipt flag", async () => {
try {
await execAsync(`${tsx} "${cliPath}" search --receipt`);
expect.fail("Should have failed");
} catch (error: any) {
expect(error.code).toBe(1);
expect(error.stderr).toContain("Missing query for search mode.");
}
});

it("exits with error when query is missing with --json flag", async () => {
try {
await execAsync(`${tsx} "${cliPath}" news --json`);
expect.fail("Should have failed");
} catch (error: any) {
expect(error.code).toBe(1);
expect(error.stderr).toContain("Missing query for news mode.");
}
});
});

describe("redactInput", () => {
it("returns short inputs unchanged", async () => {
const { redactInput } = await import("./cli.js");
expect(redactInput("short query")).toBe("short query");
expect(redactInput("a".repeat(50))).toBe("a".repeat(50));
});

it("truncates long inputs with ellipsis", async () => {
const { redactInput } = await import("./cli.js");
const long = "a".repeat(100);
expect(redactInput(long)).toBe("a".repeat(47) + "...");
});
});

describe("buildReceipt", () => {
it("builds correct receipt structure", async () => {
const { buildReceipt } = await import("./cli.js");
const receipt = buildReceipt({
mode: "search",
provider: "search.basic",
term: "test query",
price: 0.01,
traceId: "trace_abc123",
});

expect(receipt).toEqual({
command: "search",
provider: "search.basic",
input: "test query",
price: 0.01,
traceId: "trace_abc123",
});
});

it("handles missing price and traceId", async () => {
const { buildReceipt } = await import("./cli.js");
const receipt = buildReceipt({
mode: "scrape",
provider: "scrape.page",
term: "https://example.com",
});

expect(receipt).toEqual({
command: "scrape",
provider: "scrape.page",
input: "https://example.com",
price: null,
traceId: null,
});
});

it("redacts long inputs in receipt", async () => {
const { buildReceipt } = await import("./cli.js");
const long = "a".repeat(100);
const receipt = buildReceipt({
mode: "search",
provider: "search.basic",
term: long,
price: 0.05,
traceId: "trace_xyz",
});

expect(receipt.input).toBe("a".repeat(47) + "...");
});
});

describe("formatSummary", () => {
Expand Down
29 changes: 29 additions & 0 deletions apps/agent-client/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ function usage() {
console.log(' npm run cli -- search "latest soroban updates" --provider search.basic');
console.log(' npm run cli -- news "stablecoin micropayments" --provider news.fast');
console.log(' npm run cli -- scrape "https://example.com" --provider scrape.page');
console.log("Options:");
console.log(" --provider <id> Provider ID (default: search.basic / news.fast / scrape.page)");
console.log(" --receipt Output structured JSON receipt only");
}

function readArg(flag: string, args: string[]) {
Expand All @@ -52,10 +55,36 @@ function readArg(flag: string, args: string[]) {
return args[index + 1];
}

function hasFlag(flag: string, args: string[]) {
return args.includes(flag);
}

export function redactInput(input: string): string {
if (input.length <= 50) return input;
return input.slice(0, 47) + "...";
}

export function buildReceipt(input: {
mode: QueryMode;
provider: string;
term: string;
price?: number;
traceId?: string;
}) {
return {
command: input.mode,
provider: input.provider,
input: redactInput(input.term),
price: input.price ?? null,
traceId: input.traceId ?? null,
};
}

async function main() {
const args = process.argv.slice(2);
const modeArg = args[0];
const term = args[1];
const receiptMode = hasFlag("--receipt", args) || hasFlag("--json", args);

if (!modeArg || !["search", "news", "scrape"].includes(modeArg)) {
usage();
Expand Down