diff --git a/src/cli.ts b/src/cli.ts index 471037e..aa57175 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -31,11 +31,28 @@ interface CommonOptions { interface SaveOptions extends CommonOptions { source?: string; sourceContext?: string; + response?: string; + vendorId?: string; } interface SearchOptions extends CommonOptions { includePublic?: boolean; filterUsername?: string; + limit?: string; + since?: string; + until?: string; + timezone?: string; + filter?: string; +} + +interface AggregateOptions extends CommonOptions { + metrics?: string[]; + limit?: string; + since?: string; + until?: string; + timezone?: string; + filter?: string; + kg?: string; } interface FetchOptions extends CommonOptions { @@ -471,15 +488,14 @@ async function cmdLogout(options: CommonOptions): Promise { async function cmdSave(content: string[], options: SaveOptions): Promise { const format = parseOutputFormat(options.output); - await callAndPrint( - "save_memory", - { - content: content.join(" "), - source: options.source ?? "cli", - sourceContext: options.sourceContext, - }, - format, - ); + const args: Record = { + content: content.join(" "), + source: options.source ?? "cli", + sourceContext: options.sourceContext, + }; + if (options.response) args.response = options.response; + if (options.vendorId) args.vendorId = options.vendorId; + await callAndPrint("save_memory", args, format); } async function cmdSearch(queries: string[], options: SearchOptions): Promise { @@ -487,9 +503,29 @@ async function cmdSearch(queries: string[], options: SearchOptions): Promise = { queries }; if (options.includePublic) args.includePublic = true; if (options.filterUsername) args.filterUsername = options.filterUsername; + const limit = parseIntegerOption(options.limit, "--limit"); + if (limit !== undefined) args.limit = limit; + if (options.since) args.since = options.since; + if (options.until) args.until = options.until; + if (options.timezone) args.timezone = options.timezone; + if (options.filter) args.filter = parseJSONOption(options.filter, "--filter"); await callAndPrint("search_memories", args, format); } +async function cmdAggregate(groupBy: string, options: AggregateOptions): Promise { + const format = parseOutputFormat(options.output); + const args: Record = { groupBy }; + if (options.metrics && options.metrics.length > 0) args.metrics = options.metrics; + const limit = parseIntegerOption(options.limit, "--limit"); + if (limit !== undefined) args.limit = limit; + if (options.since) args.since = options.since; + if (options.until) args.until = options.until; + if (options.timezone) args.timezone = options.timezone; + if (options.filter) args.filter = parseJSONOption(options.filter, "--filter"); + if (options.kg) args.kg = options.kg; + await callAndPrint("aggregate_memories", args, format); +} + async function cmdFetch(ids: string[], options: FetchOptions): Promise { const format = parseOutputFormat(options.output); const memoryFormat = parseMemoryFormat(options.memoryFormat); @@ -826,6 +862,14 @@ Environment: .argument("", "memory content") .option("--source ", "memory source", "cli") .option("--source-context ", "source context, such as a filename") + .option( + "--response ", + "optional assistant side of the exchange; enables memory-network growth", + ) + .option( + "--vendor-id ", + "caller-stable id for deduplication (e.g. session::turn-id)", + ) .addOption(outputOption()) .action(cmdSave), ` heyditto save "Project X uses Bun + SolidJS" @@ -840,12 +884,40 @@ Environment: .argument("", "one or more search queries") .option("--include-public", "include public DittoHub memories") .option("--filter-username ", "scope public results to a username") + .option("--limit ", "maximum memories to return per query (default 5, max 50)") + .option("--since ", 'time lower bound, e.g. "last week", "30d", "2026-03"') + .option("--until ", 'time upper bound, e.g. "2026-04"') + .option("--timezone ", 'IANA timezone, e.g. "America/New_York"') + .option("--filter ", "structured filter object as JSON") .addOption(outputOption()) .action(cmdSearch), ` heyditto search "typescript preferences" heyditto search "launch notes" --include-public --filter-username peyton`, ); + addExamples( + program + .command("aggregate") + .alias("aggregate_memories") + .description("count and group memories by source, time, subject or theme") + .summary("count and group memories") + .argument("", 'dimension to group by: "source", "cluster", "subject", "week", "month", "year"') + .option( + "--metrics ", + "metric to compute (repeatable); e.g. count, sum", + ) + .option("--limit ", "maximum groups to return (default 50, max 500)") + .option("--since ", 'time lower bound, e.g. "last week", "30d"') + .option("--until ", 'time upper bound') + .option("--timezone ", 'IANA timezone') + .option("--filter ", "structured filter object as JSON") + .option("--kg ", "knowledge graph alias") + .addOption(outputOption()) + .action(cmdAggregate), + ` heyditto aggregate source --limit 20 --output json + heyditto aggregate week --since "last month" --timezone America/New_York`, + ); + program .command("fetch") .description("fetch memories by id") diff --git a/test/cli-help.test.mjs b/test/cli-help.test.mjs index 0276ea6..83e888d 100644 --- a/test/cli-help.test.mjs +++ b/test/cli-help.test.mjs @@ -46,6 +46,22 @@ test("search --help prints command help without auth", () => { assert.match(result.stdout, /Usage: heyditto search/); assert.match(result.stdout, /--include-public/); assert.match(result.stdout, /--filter-username/); + assert.match(result.stdout, /--limit/); + assert.match(result.stdout, /--since/); + assert.match(result.stdout, /--until/); + assert.match(result.stdout, /--timezone/); + assert.match(result.stdout, /--filter/); + assert.equal(result.stderr, ""); +}); + +test("aggregate --help prints command help without auth", () => { + const result = run(["aggregate", "--help"]); + assert.equal(result.status, 0); + assert.match(result.stdout, /Usage: heyditto aggregate/); + assert.match(result.stdout, /--metrics/); + assert.match(result.stdout, /--limit/); + assert.match(result.stdout, /--since/); + assert.match(result.stdout, /--filter/); assert.equal(result.stderr, ""); }); @@ -54,6 +70,8 @@ test("save -h prints source options", () => { assert.equal(result.status, 0); assert.match(result.stdout, /--source/); assert.match(result.stdout, /--source-context/); + assert.match(result.stdout, /--response/); + assert.match(result.stdout, /--vendor-id/); }); test("update --help prints file and revision options", () => { @@ -89,6 +107,7 @@ test("new MCP-backed command help is exposed", () => { assert.match(result.stdout, /subject-edges\|get_subject_edges/); assert.match(result.stdout, /graph-sharing\|set_knowledge_graph_sharing/); assert.match(result.stdout, /delete\|delete_memory/); + assert.match(result.stdout, /aggregate\|aggregate_memories/); }); test("graph sharing help exposes enable and disable", () => {