Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
9af20c2
feat(asm): add coding_execution routing contract and coding packet fo…
Mar 17, 2026
82bbfcd
fix: add package-name bin alias for npx UX
Mar 18, 2026
babeda2
fix: ship asm cli runtime dependencies in npm package
Mar 19, 2026
330b0e0
fix: bundle asm CLI runtime files for npx install flow
Mar 19, 2026
60fa813
fix: remove symlink-sensitive asm CLI entry guard
Mar 19, 2026
9f3ff6b
feat: default bare install to openclaw
Mar 19, 2026
0db1876
fix: support npx package-name install UX
Mar 19, 2026
040460b
fix: avoid slow plugin inventory during openclaw install
Mar 19, 2026
c089509
fix: separate cli bootstrap from platform install semantics
Mar 19, 2026
3883e54
feat: offer shell profile PATH patch during CLI bootstrap
Mar 19, 2026
dd8f7aa
docs: clarify supported install flows
Mar 19, 2026
e1cb458
release: 5.1.11 clarify install flows and improve CLI bootstrap
Mar 19, 2026
e6debe8
fix: prioritize asmConfigPath over hardcoded runtime defaults
Mar 19, 2026
2467273
fix: stop runtime defaults from masking asm shared config
Mar 19, 2026
6241ca3
fix: finalize ASM config-boundary and installer cleanup
Mar 19, 2026
b15ad72
chore(release): bump version to 5.1.14
Mar 19, 2026
5b10690
chore(release): narrow version bump to 5.1.14
Mar 19, 2026
722866b
chore(release): bump version to 5.1.14
Mar 19, 2026
b3df52b
fix: split init-setup wizard from openclaw binding flow
Mar 19, 2026
b7221ce
chore(release): bump version to 5.1.15
Mar 19, 2026
a50ad1a
fix: auto-create qdrant collection from runtime config
Mar 19, 2026
3e68b91
chore(release): bump version to 5.1.16
Mar 19, 2026
ee32890
feat(asm-122): implement memory foundation slices 115-121
Mar 20, 2026
d62507e
refactor(memory-foundation): rename ASM-115 migration surfaces to dom…
Mar 20, 2026
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
37 changes: 30 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,17 @@ asm install opencode

## 3) Install ASM

### Global install
There are currently **two supported install flows**.

### Flow A — CLI-first (recommended for ASM CLI usage)
Install the CLI globally first:

```bash
npm install -g @mrc2204/agent-smart-memo
```

### Initialize shared ASM config
Then initialize shared config:

```bash
asm init-setup --yes
```
Expand All @@ -106,19 +111,32 @@ This creates or updates:
~/.config/asm/config.json
```

### Install into a platform
Then install a runtime target:

```bash
asm install openclaw
asm install paperclip
asm install opencode
```

Legacy compatibility still exists for older OpenClaw flows, but the preferred path is now:
### Flow B — Plugin-first (direct OpenClaw plugin install)
If you only want the OpenClaw plugin directly, install it through OpenClaw:

```text
asm init-setup -> asm install <platform>
```bash
openclaw plugins install @mrc2204/agent-smart-memo
```

Then continue with OpenClaw-side config/bootstrap as needed.

### Important note
The command below is **not the recommended primary flow right now**:

```bash
npx @mrc2204/agent-smart-memo install
```

Use the two supported flows above until CLI bootstrap is fully separated/standardized.

---

## 4) Shared config source-of-truth
Expand Down Expand Up @@ -177,13 +195,18 @@ This keeps `openclaw.json` from becoming a second core source-of-truth.

## 5) OpenClaw quick start

### Install from npm
### Install from npm (CLI-first)
```bash
npm install -g @mrc2204/agent-smart-memo
asm init-setup --yes
asm install openclaw --yes
```

### Install plugin directly into OpenClaw (plugin-first)
```bash
openclaw plugins install @mrc2204/agent-smart-memo
```

### Install locally from source
```bash
npm install
Expand Down
222 changes: 182 additions & 40 deletions bin/asm.mjs
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
#!/usr/bin/env node
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import { chmodSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { spawnSync } from "node:child_process";
import { dirname, join, resolve } from "node:path";
import { runInitOpenClaw } from "../scripts/init-openclaw.mjs";
import { createShellRunner, runInitSetupFlow, runInstallPlatformFlow } from "../src/cli/platform-installers.ts";
import { createShellRunner, runInitSetupFlow, runInstallPlatformFlow } from "../dist/cli/platform-installers.js";
import { runOpencodeMcpServer } from "./opencode-mcp-server.mjs";
import { resolveAsmRuntimeConfig } from "../dist/shared/asm-config.js";

const ASM_PLUGIN_PACKAGE = "@mrc2204/agent-smart-memo";
const ASM_PLUGIN_ID = "agent-smart-memo";

console.error("[ASM-TRACE] import.meta.url=", import.meta.url);
console.error("[ASM-TRACE] argv=", JSON.stringify(process.argv));
console.error("[ASM-TRACE] cwd=", process.cwd());

function text(value) {
return typeof value === "string" ? value.trim() : "";
}
Expand All @@ -21,7 +27,10 @@ function includesAsmPlugin(output) {
}

export function parseAsmCliArgs(argv = []) {
const args = Array.isArray(argv) ? argv.map((x) => String(x)) : [];
let args = Array.isArray(argv) ? argv.map((x) => String(x)) : [];
if (args[0] === 'agent-smart-memo' || args[0] === '@mrc2204/agent-smart-memo') {
args = args.slice(1);
}
const first = args[0] || "";

if (!first || first === "help" || first === "--help" || first === "-h") {
Expand All @@ -36,10 +45,15 @@ export function parseAsmCliArgs(argv = []) {
return { command: "setup-openclaw", argv: args.slice(2) };
}

if (first === "install" && (args[1] || "")) {
if (first === "install") {
const hasExplicitPlatform = Boolean(args[1]);
if (!hasExplicitPlatform) {
return { command: "install-cli", argv: [] };
}
const platform = String(args[1] || "").trim().toLowerCase();
return {
command: "install-platform",
platform: String(args[1] || "").trim().toLowerCase(),
platform,
argv: args.slice(2),
};
}
Expand All @@ -64,6 +78,22 @@ export function parseAsmCliArgs(argv = []) {
return { command: "project-event", argv: args.slice(1) };
}

if (first === "check-memory-foundation") {
return { command: "check-memory-foundation", argv: args.slice(1) };
}

if (first === "migrate-memory-foundation") {
return { command: "migrate-memory-foundation", argv: args.slice(1) };
}

if (first === "memory" && (args[1] || "") === "migrate") {
return { command: "migrate-memory-foundation", argv: args.slice(2) };
}

if (first === "memory" && (args[1] || "") === "check") {
return { command: "check-memory-foundation", argv: args.slice(2) };
}

if (first === "mcp" && (args[1] || "") === "opencode") {
return { command: "mcp-opencode", argv: args.slice(2) };
}
Expand All @@ -75,6 +105,7 @@ export function printHelp(log = console.log) {
log("asm - Agent Smart Memo CLI");
log("");
log("Usage:");
log(" asm install # install / expose CLI only");
log(" asm setup-openclaw [--yes]");
log(" asm setup openclaw [--yes]");
log(" asm install openclaw [--yes]");
Expand All @@ -85,6 +116,10 @@ export function printHelp(log = console.log) {
log(" asm init-openclaw [--non-interactive]");
log(" asm init openclaw [--non-interactive]");
log(" asm project-event --project-id <id> --repo-root <path> [--event-type post_commit|post_merge|post_rewrite|manual] [--source-rev <sha>] [--changed-files a,b] [--deleted-files x,y] [--trusted-sync 0|1] [--full-snapshot 0|1]");
log(" asm migrate-memory-foundation <preflight|plan|apply|verify|rollback> [--user-id <id>] [--agent-id <id>] [--snapshot-dir <path>] [--rollback-snapshot <path>] [--preflight-limit <n>]");
log(" asm memory migrate <preflight|plan|apply|verify|rollback> [flags...]");
log(" asm check-memory-foundation [--user-id <id>] [--agent-id <id>] [--preflight-limit <n>] # alias: verify status/version");
log(" asm memory check [--user-id <id>] [--agent-id <id>] [--preflight-limit <n>]");
log(" asm help");
log("");
log("Roadmap commands (not implemented yet):");
Expand Down Expand Up @@ -162,6 +197,84 @@ function parseProjectEventArgs(argv = []) {
return out;
}

function resolveUserBinDir() {
const home = process.env.HOME || process.cwd();
return join(home, '.local', 'bin');
}

function pathContains(dir) {
return String(process.env.PATH || '').split(':').includes(dir);
}

function detectShellProfile() {
const shell = String(process.env.SHELL || '').trim();
const home = process.env.HOME || process.cwd();
if (shell.endsWith('/zsh')) return { shell: 'zsh', profilePath: join(home, '.zshrc') };
if (shell.endsWith('/bash')) return { shell: 'bash', profilePath: join(home, '.bashrc') };
return { shell: shell || 'unknown', profilePath: join(home, '.profile') };
}

function profileHasPathLine(profilePath, userBin) {
try {
const content = readFileSync(profilePath, 'utf8');
return content.includes(userBin) || content.includes('$HOME/.local/bin');
} catch {
return false;
}
}

function appendPathLine(profilePath, userBin) {
const exportLine = `\n# Added by ASM CLI installer\nexport PATH=\"${userBin}:$PATH\"\n`;
const existing = (() => { try { return readFileSync(profilePath, 'utf8'); } catch { return ''; } })();
if (!existing.includes(userBin) && !existing.includes('$HOME/.local/bin')) {
writeFileSync(profilePath, `${existing}${exportLine}`, 'utf8');
}
}

function createAsmLauncher() {
const userBin = resolveUserBinDir();
mkdirSync(userBin, { recursive: true });
const launcherPath = join(userBin, 'asm');
const packageRoot = resolve(dirname(new URL(import.meta.url).pathname), '..');
const launcher = `#!/usr/bin/env bash\nnode \"${join(packageRoot, 'bin', 'asm.mjs')}\" \"$@\"\n`;
writeFileSync(launcherPath, launcher, 'utf8');
chmodSync(launcherPath, 0o755);
return { launcherPath, userBin };
}

export async function runCliBootstrapFlow({ log = console.log } = {}) {
log('[ASM-CLI] Installing / exposing ASM CLI only...');
log(`[ASM-CLI] Package: ${ASM_PLUGIN_PACKAGE}`);
const installed = createAsmLauncher();
log(`[ASM-CLI] Installed launcher: ${installed.launcherPath}`);
if (!pathContains(installed.userBin)) {
const detected = detectShellProfile();
log(`[ASM-CLI] ${installed.userBin} is not currently on PATH.`);
const shouldPatch = process.stdin.isTTY
? await askYesNo(`[ASM-CLI] Add ${installed.userBin} to ${detected.profilePath} now? [y/N] `)
: false;
if (shouldPatch) {
appendPathLine(detected.profilePath, installed.userBin);
log(`[ASM-CLI] Updated ${detected.profilePath}`);
log(`[ASM-CLI] Run: source ${detected.profilePath} (or open a new terminal)`);
process.env.PATH = `${installed.userBin}:${process.env.PATH || ''}`;
} else {
log(`[ASM-CLI] To enable 'asm' in future shells, add this line to ${detected.profilePath}:`);
log(` export PATH=\"${installed.userBin}:$PATH\"`);
}
}
const verify = createShellRunner()('bash', ['-lc', `"${installed.launcherPath}" --help`]);
if (!verify.ok) {
return { ok: false, step: 'verify-cli-launcher', details: { stdout: verify.stdout, stderr: verify.stderr, launcherPath: installed.launcherPath } };
}
log('[ASM-CLI] asm launcher verified successfully.');
log('[ASM-CLI] Next steps:');
log(' 1) asm install openclaw');
log(' 2) asm install opencode');
log(' 3) asm install paperclip');
return { ok: true, step: 'install-cli', details: installed };
}

export async function runSetupOpenClawFlow({
runner = createShellRunner(),
initOpenClaw = runInitOpenClaw,
Expand Down Expand Up @@ -245,6 +358,11 @@ export async function main(argv = process.argv.slice(2)) {
return 0;
}

if (parsed.command === "install-cli") {
const result = await runCliBootstrapFlow({ log: console.log });
return result.ok ? 0 : 1;
}

if (parsed.command === "setup-openclaw") {
const result = await runSetupOpenClawFlow({ argv: parsed.argv });
return result.ok ? 0 : 1;
Expand Down Expand Up @@ -290,39 +408,29 @@ export async function main(argv = process.argv.slice(2)) {
console.error('[ASM-87] project-event requires --project-id and --repo-root');
return 1;
}
const pluginId = 'agent-smart-memo';
const cfgPath = resolve(process.env.HOME || '', '.openclaw', 'openclaw.json');
let qdrantCollection = 'mrc_bot';
let llmBaseUrl = 'http://localhost:8317/v1';
let llmApiKey = 'proxypal-local';
let llmModel = 'gpt-5.4';
let embedModel = 'qwen3-embedding:0.6b';
let embedDimensions = 1024;
let slotDbDir = resolve(process.env.HOME || '', '.openclaw', 'agent-memo');
try {
const raw = JSON.parse(readFileSync(cfgPath, 'utf8'));
const cfg = raw?.plugins?.entries?.[pluginId]?.config || {};
qdrantCollection = cfg.qdrantCollection || qdrantCollection;
llmBaseUrl = cfg.llmBaseUrl || llmBaseUrl;
llmApiKey = cfg.llmApiKey || llmApiKey;
llmModel = cfg.llmModel || llmModel;
embedModel = cfg.embedModel || embedModel;
embedDimensions = cfg.embedDimensions || embedDimensions;
slotDbDir = cfg.slotDbDir || slotDbDir;
} catch {}

process.env.OPENCLAW_SLOTDB_DIR = slotDbDir;
const runtime = resolveAsmRuntimeConfig({ env: process.env, homeDir: process.env.HOME });

process.env.OPENCLAW_SLOTDB_DIR = runtime.slotDbDir;
process.env.AGENT_MEMO_PROJECT_WORKSPACE_ROOT = event.repoRoot;
process.env.AGENT_MEMO_REPO_CLONE_ROOT = event.repoRoot;
process.env.PROJECT_WORKSPACE_ROOT = event.repoRoot;
process.env.REPO_CLONE_ROOT = event.repoRoot;
process.env.QDRANT_COLLECTION = qdrantCollection;
process.env.LLM_BASE_URL = llmBaseUrl;
process.env.LLM_API_KEY = llmApiKey;
process.env.LLM_MODEL = llmModel;
process.env.EMBED_MODEL = embedModel;
process.env.EMBEDDING_MODEL = embedModel;
process.env.EMBEDDING_DIMENSIONS = String(embedDimensions);
process.env.QDRANT_COLLECTION = runtime.qdrantCollection;
process.env.LLM_BASE_URL = runtime.llmBaseUrl;
process.env.LLM_API_KEY = runtime.llmApiKey;
process.env.LLM_MODEL = runtime.llmModel;
process.env.EMBED_MODEL = runtime.embedModel;
process.env.EMBEDDING_MODEL = runtime.embedModel;
process.env.EMBEDDING_DIMENSIONS = String(runtime.embedDimensions);
process.env.AGENT_MEMO_QDRANT_HOST = runtime.qdrantHost;
process.env.AGENT_MEMO_QDRANT_PORT = String(runtime.qdrantPort);
process.env.AGENT_MEMO_QDRANT_COLLECTION = runtime.qdrantCollection;
process.env.AGENT_MEMO_QDRANT_VECTOR_SIZE = String(runtime.qdrantVectorSize);
process.env.AGENT_MEMO_EMBED_BASE_URL = runtime.embedBaseUrl;
process.env.AGENT_MEMO_EMBED_MODEL = runtime.embedModel;
process.env.AGENT_MEMO_EMBED_DIMENSIONS = String(runtime.embedDimensions);

const slotDbDir = runtime.slotDbDir;

const { SlotDB } = await import('../dist/db/slot-db.js');
const { DefaultMemoryUseCasePort } = await import('../dist/core/usecases/default-memory-usecase-port.js');
Expand Down Expand Up @@ -353,13 +461,47 @@ export async function main(argv = process.argv.slice(2)) {
}
}

if (parsed.command === "migrate-memory-foundation") {
try {
const proc = spawnSync(
"npx",
["tsx", "scripts/migrate-memory-foundation.ts", ...(parsed.argv || [])],
{
stdio: "inherit",
cwd: process.cwd(),
env: process.env,
},
);
return typeof proc.status === "number" ? proc.status : 1;
} catch (error) {
console.error(`[ASM-115] migrate failed: ${error instanceof Error ? error.message : String(error)}`);
return 1;
}
}

if (parsed.command === "check-memory-foundation") {
try {
const proc = spawnSync(
"npx",
["tsx", "scripts/migrate-memory-foundation.ts", "verify", ...(parsed.argv || [])],
{
stdio: "inherit",
cwd: process.cwd(),
env: process.env,
},
);
return typeof proc.status === "number" ? proc.status : 1;
} catch (error) {
console.error(`[ASM-115] check failed: ${error instanceof Error ? error.message : String(error)}`);
return 1;
}
}

console.error(`[ASM-84] Unknown command: ${argv.join(" ") || "(empty)"}`);
printHelp(console.error);
return 1;
}

if (import.meta.url === `file://${process.argv[1]}`) {
main().then((code) => {
process.exitCode = code;
});
}
main().then((code) => {
process.exitCode = code;
});
Loading
Loading