feat(cli): 1.27.0 — state/memory through daemon + workspace alias
extend the daemon thin-client surface to two more verb families: state get/set/list now routes through `/v1/state`, and remember/recall/forget through `/v1/memory`. same warm-path pattern as 1.25.0 — try the unix socket first, fall back to the cold ws path when the daemon is absent. multi-mesh aware (aggregates on read, requires `--mesh` for writes when ambiguous). also ships an early `claudemesh workspace <verb>` alias surface — bare teaser for the 1.28.0 mesh→workspace public rename. no-arg falls through to launch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
import { withMesh } from "./connect.js";
|
||||
import { readConfig } from "~/services/config/facade.js";
|
||||
import { tryBridge } from "~/services/bridge/client.js";
|
||||
import { tryForgetViaDaemon } from "~/services/bridge/daemon-route.js";
|
||||
import { render } from "~/ui/render.js";
|
||||
import { bold, clay, dim } from "~/ui/styles.js";
|
||||
import { EXIT } from "~/constants/exit-codes.js";
|
||||
@@ -173,6 +174,14 @@ export async function runForget(id: string | undefined, opts: StateFlags): Promi
|
||||
render.err("Usage: claudemesh forget <memory-id>");
|
||||
return EXIT.INVALID_ARGS;
|
||||
}
|
||||
|
||||
// Daemon path first.
|
||||
if (await tryForgetViaDaemon(id, opts.mesh)) {
|
||||
if (opts.json) { console.log(JSON.stringify({ id, forgotten: true })); return EXIT.SUCCESS; }
|
||||
render.ok(`forgot ${dim(id.slice(0, 8))}`);
|
||||
return EXIT.SUCCESS;
|
||||
}
|
||||
|
||||
await withMesh({ meshSlug: opts.mesh ?? null }, async (client) => {
|
||||
await client.forget(id);
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { withMesh } from "./connect.js";
|
||||
import { tryRecallViaDaemon } from "~/services/bridge/daemon-route.js";
|
||||
import { render } from "~/ui/render.js";
|
||||
import { bold, clay, dim } from "~/ui/styles.js";
|
||||
import { EXIT } from "~/constants/exit-codes.js";
|
||||
@@ -11,6 +12,22 @@ export async function recall(
|
||||
render.err("Usage: claudemesh recall <query>");
|
||||
return EXIT.INVALID_ARGS;
|
||||
}
|
||||
|
||||
// Daemon path first.
|
||||
const daemonMatches = await tryRecallViaDaemon(query, opts.mesh);
|
||||
if (daemonMatches !== null) {
|
||||
if (opts.json) { console.log(JSON.stringify(daemonMatches, null, 2)); return EXIT.SUCCESS; }
|
||||
if (daemonMatches.length === 0) { render.info(dim("no memories found.")); return EXIT.SUCCESS; }
|
||||
render.section(`memories (${daemonMatches.length})`);
|
||||
for (const m of daemonMatches) {
|
||||
const tags = m.tags.length ? dim(` [${m.tags.map((t) => clay(t)).join(dim(", "))}]`) : "";
|
||||
process.stdout.write(` ${bold(m.id.slice(0, 8))}${tags}\n`);
|
||||
process.stdout.write(` ${m.content}\n`);
|
||||
process.stdout.write(` ${dim(m.rememberedBy + " · " + new Date(m.rememberedAt).toLocaleString())}\n\n`);
|
||||
}
|
||||
return EXIT.SUCCESS;
|
||||
}
|
||||
|
||||
return await withMesh({ meshSlug: opts.mesh ?? null }, async (client) => {
|
||||
const memories = await client.recall(query);
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { withMesh } from "./connect.js";
|
||||
import { tryRememberViaDaemon } from "~/services/bridge/daemon-route.js";
|
||||
import { render } from "~/ui/render.js";
|
||||
import { dim } from "~/ui/styles.js";
|
||||
import { EXIT } from "~/constants/exit-codes.js";
|
||||
@@ -12,6 +13,18 @@ export async function remember(
|
||||
return EXIT.INVALID_ARGS;
|
||||
}
|
||||
const tags = opts.tags?.split(",").map((t) => t.trim()).filter(Boolean);
|
||||
|
||||
// Daemon path first.
|
||||
const daemonRes = await tryRememberViaDaemon(content, tags, opts.mesh);
|
||||
if (daemonRes) {
|
||||
if (opts.json) {
|
||||
console.log(JSON.stringify({ id: daemonRes.id, content, tags, mesh: daemonRes.mesh }));
|
||||
return EXIT.SUCCESS;
|
||||
}
|
||||
render.ok("remembered", dim(daemonRes.id.slice(0, 8)));
|
||||
return EXIT.SUCCESS;
|
||||
}
|
||||
|
||||
return await withMesh({ meshSlug: opts.mesh ?? null }, async (client) => {
|
||||
const id = await client.remember(content, tags);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
import { withMesh } from "./connect.js";
|
||||
import { tryGetStateViaDaemon, tryListStateViaDaemon, trySetStateViaDaemon } from "~/services/bridge/daemon-route.js";
|
||||
import { render } from "~/ui/render.js";
|
||||
import { bold, dim } from "~/ui/styles.js";
|
||||
|
||||
@@ -14,6 +15,16 @@ export interface StateFlags {
|
||||
}
|
||||
|
||||
export async function runStateGet(flags: StateFlags, key: string): Promise<void> {
|
||||
// Daemon path first.
|
||||
const daemonEntry = await tryGetStateViaDaemon(key, flags.mesh);
|
||||
if (daemonEntry !== null) {
|
||||
if (!daemonEntry) { render.info(dim("(not set)")); return; }
|
||||
if (flags.json) { console.log(JSON.stringify(daemonEntry, null, 2)); return; }
|
||||
const val = typeof daemonEntry.value === "string" ? daemonEntry.value : JSON.stringify(daemonEntry.value);
|
||||
render.info(val);
|
||||
render.info(dim(` set by ${daemonEntry.updatedBy} at ${new Date(daemonEntry.updatedAt).toLocaleString()}`));
|
||||
return;
|
||||
}
|
||||
await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
|
||||
const entry = await client.getState(key);
|
||||
if (!entry) {
|
||||
@@ -38,6 +49,12 @@ export async function runStateSet(flags: StateFlags, key: string, value: string)
|
||||
parsed = value;
|
||||
}
|
||||
|
||||
// Daemon path first.
|
||||
const daemonOk = await trySetStateViaDaemon(key, parsed, flags.mesh);
|
||||
if (daemonOk) {
|
||||
render.ok(`${bold(key)} = ${JSON.stringify(parsed)}`);
|
||||
return;
|
||||
}
|
||||
await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
|
||||
await client.setState(key, parsed);
|
||||
render.ok(`${bold(key)} = ${JSON.stringify(parsed)}`);
|
||||
@@ -45,6 +62,19 @@ export async function runStateSet(flags: StateFlags, key: string, value: string)
|
||||
}
|
||||
|
||||
export async function runStateList(flags: StateFlags): Promise<void> {
|
||||
// Daemon path first.
|
||||
const daemonRows = await tryListStateViaDaemon(flags.mesh);
|
||||
if (daemonRows !== null) {
|
||||
if (flags.json) { console.log(JSON.stringify(daemonRows, null, 2)); return; }
|
||||
if (daemonRows.length === 0) { render.info(dim("(no state)")); return; }
|
||||
render.section(`state (${daemonRows.length})`);
|
||||
for (const e of daemonRows) {
|
||||
const val = typeof e.value === "string" ? e.value : JSON.stringify(e.value);
|
||||
process.stdout.write(` ${bold(e.key)}: ${val}\n`);
|
||||
process.stdout.write(` ${dim(e.updatedBy + " · " + new Date(e.updatedAt).toLocaleString())}\n`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
await withMesh({ meshSlug: flags.mesh ?? null }, async (client, mesh) => {
|
||||
const entries = await client.listState();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user