Files
Hy-Memory/memhub/apps/cli/src/entry.ts
T
alvinfeiandClaude Opus 5.5 072e0e5e90 feat(memhub): simplify the CLI and drop the notes concept
API: adding one memory is POST /api/v2/memories with its status at
GET /api/v2/memories/additions/{request_id}; /api/v1/memory-notes is
removed. Error codes memory_note_* become memory_add_*, contracts types
MemoryAddInput/MemoryAddReceipt, broker /memories, MCP MemHub_write and
the web composer follow. Stored names (memory_note_requests,
one-memory-manual, direct_memory_note) stay. /api/v2/memory-library
takes an optional scene filter.

CLI:
- No plan files. memories add/add-status, models add/test/receipt and
  sources connect execute directly; async receipts stay.
- One --yes, only for irreversible operations (memories delete, models
  remove, delete-data, backup restore, graph-width rebuild); without it
  they preview and exit 0. --confirm is a hidden alias.
- Removed notes, calls, setup plan, sources import, models
  plan/configure/probe*, and the unreachable processing, extraction and
  import-file commands, with their dead allow-list entries.
- memories list gets --scene/--source/--query; show/update/delete take
  --kind/--variant (and --task). All memories JSON is schema_version 2.
- Fixes from the CLI review: global --data-dir, real
  --confirm-overrides and help that matches init, server failures exit 1
  not 2, catalog exit codes match, JSON errors on stdout, bad filters are
  rejected instead of "busy", write submit prints its request_id for
  retries, recall labels raw excerpts, stop says it disables login
  auto-start, backup help says memories are not included yet.
- Hidden aliases: path -> scan-roots, task -> write status,
  graph width -> graph-width, init --source -> sources connect.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-27 19:21:34 +08:00

128 lines
4.1 KiB
JavaScript

#!/usr/bin/env node
import os from "node:os";
import path from "node:path";
import {
DiagnosticLogger,
diagnosticError,
resolveRuntimeCliConfig,
} from "@one-memory/runtime-control";
import { run } from "./cli.js";
import { resolveEntryDiagnostics } from "./entry-diagnostics.js";
import { resolveDiagnosticsDataDir } from "./install-receipt.js";
function accountHomeDirectory(): string | undefined {
try {
return os.userInfo().homedir;
} catch {
return os.homedir();
}
}
async function main(): Promise<number> {
const arguments_ = process.argv.slice(2);
const command = arguments_[0];
// A preview (`--yes` absent) must not create or write any diagnostic file:
// the contract is that it only emits a plan. Diagnostics for these commands
// are initialized only once the caller has confirmed the mutation.
const confirmed = arguments_.includes("--yes");
const installLike = [
"install",
"upgrade",
"service-install",
"service-uninstall",
].includes(command ?? "");
const isPreview = installLike && !confirmed;
const mutating = [
"start",
"stop",
"restart",
"install",
"upgrade",
"service-install",
"service-uninstall",
].includes(command ?? "");
let diagnostics: DiagnosticLogger | null = null;
if (mutating && !isPreview) {
try {
// Apply the managed install receipt before resolving the config, so
// `memhub start/stop/...` on a custom data dir logs to the same place the
// command itself will act on instead of the default ~/.one-memory.
const diagnosticsDataDir = await resolveDiagnosticsDataDir(
arguments_,
process.env,
);
// An explicit but unusable --data-dir must not silently log elsewhere;
// skip diagnostics and let `run()` report the usage error.
if (arguments_.includes("--data-dir") && diagnosticsDataDir === null) {
throw new Error("cli_diagnostics_data_dir_invalid");
}
// An isolated HOME with no explicit data dir resolves to
// `<HOME>/.one-memory` only because that is the account-home default.
// Writing CLI diagnostics there would add `.one-memory/logs/...` to a
// directory the caller meant as an isolation root, not a user home
// (C8 P1 isolated-cli-session-writes-home). Skip entry diagnostics in
// that case; the command still runs and reports its own structured
// output.
const entryDecision = resolveEntryDiagnostics({
arguments_,
home: process.env.HOME,
accountHome: accountHomeDirectory(),
environmentDataDir: process.env.ONE_MEMORY_DATA_DIR,
});
if (entryDecision.kind === "skip") {
throw new Error(`cli_diagnostics_${entryDecision.reason}`);
}
const config = resolveRuntimeCliConfig(
diagnosticsDataDir === null
? process.env
: { ...process.env, ONE_MEMORY_DATA_DIR: diagnosticsDataDir },
process.argv[1],
);
diagnostics = new DiagnosticLogger({
dataDir: config.dataDir,
service: "cli",
});
await diagnostics.log({
level: "info",
component: "cli.command",
event: "command.started",
outcome: "started",
details: { action: command ?? "unknown" },
});
} catch {
diagnostics = null;
}
}
try {
const code = await run(arguments_);
await diagnostics?.log({
level: code === 0 ? "info" : "warn",
component: "cli.command",
event: "command.completed",
outcome: code === 0 ? "succeeded" : "failed",
details: { action: command ?? "unknown", status: String(code) },
});
return code;
} catch (error) {
await diagnostics?.log({
level: "error",
component: "cli.command",
event: "command.failed",
outcome: "failed",
error_code: "cli_command_failed",
...diagnosticError(error),
details: { action: command ?? "unknown" },
});
process.stderr.write(
`${error instanceof Error ? error.message : "MemHub CLI failed"}\n`,
);
return 1;
}
}
main().then((code) => {
process.exitCode = code;
});