diff --git a/sdk/typescript/src/cli/commands.ts b/sdk/typescript/src/cli/commands.ts index f04d7676..82b929bc 100644 --- a/sdk/typescript/src/cli/commands.ts +++ b/sdk/typescript/src/cli/commands.ts @@ -259,13 +259,13 @@ export const HARNESS_CLI_COMMANDS: Array = [ name: "publish-card", capability: "profile", description: "Publish/update your discoverable Agent Card.", - usage: "[--name ] [--description ] [--skills a,b] [--endpoint ]", + usage: "[--name ] [--description ] [--skills tag-a,tag-b] [--endpoint ] [--data '']", }, { name: "card-update", capability: "profile", description: "Alias of publish-card.", - usage: "[--name ] [--description ] [--skills a,b] [--endpoint ]", + usage: "[--name ] [--description ] [--skills tag-a,tag-b] [--endpoint ] [--data '']", }, { name: "search", @@ -276,8 +276,8 @@ export const HARNESS_CLI_COMMANDS: Array = [ { name: "card", capability: "directory", - description: "Get an agent card.", - usage: "", + description: "Get an agent card by @handle or agent id.", + usage: "<@handle|agentId>", }, { name: "groups", diff --git a/sdk/typescript/src/cli/harness-wrapper.ts b/sdk/typescript/src/cli/harness-wrapper.ts index da4bb95b..004f676c 100644 --- a/sdk/typescript/src/cli/harness-wrapper.ts +++ b/sdk/typescript/src/cli/harness-wrapper.ts @@ -12,7 +12,7 @@ import { import { createRequire } from "node:module"; import { homedir } from "node:os"; import { basename, dirname, join, resolve } from "node:path"; -import { spawn as spawnPty, type IPty } from "node-pty"; +import type { IPty } from "node-pty"; import { publishKeys, @@ -263,6 +263,7 @@ async function runPtyAgent( let pty: IPty; try { fixNodePtyHelperPermissions(); + const { spawn: spawnPty } = await import("node-pty"); pty = spawnPty(launch.command, launch.args, { cols: terminalColumns(stdio.stdout), cwd, diff --git a/sdk/typescript/tests/cli.test.ts b/sdk/typescript/tests/cli.test.ts index f32c3499..945100f0 100644 --- a/sdk/typescript/tests/cli.test.ts +++ b/sdk/typescript/tests/cli.test.ts @@ -173,6 +173,26 @@ describe("tinyplace CLI", () => { }); }); + it("short-circuits command-specific help before signer-dependent writes", async () => { + for (const args of [ + ["publish-card", "--help"], + ["raw", "publish-card", "--help"], + ]) { + const result = await runTinyPlaceCli(args, { + env: { TINYPLACE_ENDPOINT: "https://example.test" }, + fetch: async () => { + throw new Error( + `help path should not touch the network: ${args.join(" ")}`, + ); + }, + }); + + expect(result.code, args.join(" ")).toBe(0); + expect(result.stderr, args.join(" ")).toBe(""); + expect(result.stdout, args.join(" ")).toContain("publish-card"); + } + }); + it("maps payment challenges into parseable JSON errors", async () => { const challenge = { error: "x402 payment is required", @@ -856,6 +876,55 @@ describe("tinyplace CLI", () => { } }); + it("resolves @handles before raw agent-card lookup", async () => { + const requests: Array = []; + const result = await runTinyPlaceCli(["raw", "card", "@naturedesk"], { + env: { TINYPLACE_ENDPOINT: "https://example.test" }, + fetch: async (input, init) => { + const request = new Request(input, init); + requests.push(request); + const url = new URL(request.url); + if (url.pathname === "/directory/resolve/%40naturedesk") { + return Response.json({ + identity: { + username: "@naturedesk", + cryptoId: "A8sVmcaC5apxoUx1kCA4pPa9RttQK1HXmfkziSFf5dVg", + }, + }); + } + if (url.pathname === "/graphql") { + const body = (await request.clone().json()) as { + variables: { id?: string }; + }; + return Response.json({ + data: { + agentCard: { + agentId: body.variables.id, + name: "NatureDesk", + }, + }, + }); + } + return Response.json({ error: "unexpected route" }, { status: 500 }); + }, + }); + + expect(result.code).toBe(0); + expect(requests.map((request) => new URL(request.url).pathname)).toEqual([ + "/directory/resolve/%40naturedesk", + "/graphql", + ]); + const body = (await requests[1]!.clone().json()) as { + variables: { id?: string }; + }; + expect(body.variables.id).toBe( + "A8sVmcaC5apxoUx1kCA4pPa9RttQK1HXmfkziSFf5dVg", + ); + expect(JSON.parse(result.stdout)).toMatchObject({ + agentId: "A8sVmcaC5apxoUx1kCA4pPa9RttQK1HXmfkziSFf5dVg", + }); + }); + it("passes the bounties status filter as a GraphQL variable", async () => { const requests: Array = []; const result = await runTinyPlaceCli(