moria-im 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/moria.mjs +109 -20
- package/package.json +1 -1
- package/npm-shrinkwrap.json +0 -1830
package/moria.mjs
CHANGED
|
@@ -364,6 +364,11 @@ function validatePresence(m) {
|
|
|
364
364
|
if (b.v !== 1) errs.push("v must be 1");
|
|
365
365
|
if (b.state !== "typing" && b.state !== "idle") errs.push("state must be typing or idle");
|
|
366
366
|
if (b.ttlMs !== void 0 && !(typeof b.ttlMs === "number" && Number.isInteger(b.ttlMs) && b.ttlMs > 0 && b.ttlMs <= PRESENCE_TTL_MS)) errs.push(`ttlMs must be an integer 1..${PRESENCE_TTL_MS}`);
|
|
367
|
+
if (b.stage !== void 0) {
|
|
368
|
+
if (b.stage !== "received" && b.stage !== "working") errs.push("stage must be received or working");
|
|
369
|
+
if (b.state !== "typing") errs.push("stage goes only with state typing");
|
|
370
|
+
}
|
|
371
|
+
if (b.startedAt !== void 0 && !(typeof b.startedAt === "number" && Number.isInteger(b.startedAt) && b.startedAt > 0)) errs.push("startedAt must be a positive integer (ms)");
|
|
367
372
|
return errs;
|
|
368
373
|
}
|
|
369
374
|
function encodePresence(m) {
|
|
@@ -804,7 +809,7 @@ init_src();
|
|
|
804
809
|
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
805
810
|
import { existsSync as existsSync5, mkdirSync as mkdirSync6, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "node:fs";
|
|
806
811
|
import { homedir as homedir2, hostname as hostname2, platform as platform2 } from "node:os";
|
|
807
|
-
import { join as join4 } from "node:path";
|
|
812
|
+
import { join as join4, resolve } from "node:path";
|
|
808
813
|
import { parseArgs } from "node:util";
|
|
809
814
|
import { generatePrivateKey as generatePrivateKey2, privateKeyToAccount as privateKeyToAccount4 } from "viem/accounts";
|
|
810
815
|
|
|
@@ -829,10 +834,17 @@ function networkFor(host) {
|
|
|
829
834
|
}
|
|
830
835
|
var DEFAULT_NETWORK = networkFor(process.env.MORIA_NETWORK || "moria.im");
|
|
831
836
|
var PUBLIC_NETWORK = networkFor("moria.im");
|
|
832
|
-
|
|
837
|
+
var shellQuote = (s) => /^[\p{L}\p{N}._\-/:@+=,]+$/u.test(s) ? s : `'${s.replace(/'/g, `'\\''`)}'`;
|
|
838
|
+
function enrollCommand(token, runtime, n, d = {}) {
|
|
833
839
|
const pub = n.apiUrl === PUBLIC_NETWORK.apiUrl && n.historySyncUrl === PUBLIC_NETWORK.historySyncUrl && n.directoryUrl === PUBLIC_NETWORK.directoryUrl;
|
|
834
840
|
const flags = pub ? "" : ` --directory ${n.directoryUrl} --api ${n.apiUrl} --history ${n.historySyncUrl}`;
|
|
835
|
-
|
|
841
|
+
const name = d.name?.trim() && AGENT_NAME_RE.test(d.name.trim()) ? d.name.trim() : "<your name>";
|
|
842
|
+
const model = d.model?.trim() ? shellQuote(d.model.trim()) : "<your model>";
|
|
843
|
+
const bio = d.bio?.trim() ? ` --bio ${shellQuote(d.bio.trim().slice(0, 280))}` : "";
|
|
844
|
+
return `moria agent enroll --token ${token} --name ${name} --runtime ${runtime} --model ${model}${bio}${flags}`;
|
|
845
|
+
}
|
|
846
|
+
function enrollInstruction(token, runtime, n, d = {}) {
|
|
847
|
+
return `Join my Moria chats as an agent, following https://get.moria.im/agents.md. If the moria command is missing, install it: curl -fsSL https://get.moria.im/moria | sh (or: npm install -g moria-im). Then run: ${enrollCommand(token, runtime, n, d)}. The code expires in 15 minutes. Tell me the 4 emoji it shows.`;
|
|
836
848
|
}
|
|
837
849
|
var configPath = () => join(MORIA_HOME, "config.json");
|
|
838
850
|
var dataDir = () => join(MORIA_HOME, "data");
|
|
@@ -1754,15 +1766,15 @@ async function serveMcp(cfg) {
|
|
|
1754
1766
|
}, async ({ timeoutSec }) => {
|
|
1755
1767
|
const mine = () => queue.filter((e) => e.addressedToMe);
|
|
1756
1768
|
if (!mine().length) {
|
|
1757
|
-
await new Promise((
|
|
1769
|
+
await new Promise((resolve2) => {
|
|
1758
1770
|
const t = setTimeout(() => {
|
|
1759
1771
|
wake = null;
|
|
1760
|
-
|
|
1772
|
+
resolve2();
|
|
1761
1773
|
}, (timeoutSec ?? 60) * 1e3);
|
|
1762
1774
|
wake = () => {
|
|
1763
1775
|
clearTimeout(t);
|
|
1764
1776
|
wake = null;
|
|
1765
|
-
|
|
1777
|
+
resolve2();
|
|
1766
1778
|
};
|
|
1767
1779
|
});
|
|
1768
1780
|
}
|
|
@@ -2106,6 +2118,7 @@ async function serveLocal(opts = {}) {
|
|
|
2106
2118
|
a.operatorName = people.get(op)?.name ?? names.get(op) ?? a.operatorHandle;
|
|
2107
2119
|
}
|
|
2108
2120
|
const typing = {};
|
|
2121
|
+
const progress = {};
|
|
2109
2122
|
const now = Date.now();
|
|
2110
2123
|
for (const c of conversations) {
|
|
2111
2124
|
const raw = msgs.get(c.id) ?? [];
|
|
@@ -2115,7 +2128,9 @@ async function serveLocal(opts = {}) {
|
|
|
2115
2128
|
const last = [...raw].reverse().find((m) => m.senderInboxId === aid);
|
|
2116
2129
|
if (!last || !isPresenceType(last.contentType) || !last.content || typeof last.content !== "object") return false;
|
|
2117
2130
|
const p = last.content;
|
|
2118
|
-
|
|
2131
|
+
const on = p.state === "typing" && now < Number(last.sentAtNs / 1000000n) + (p.ttlMs ?? 3e4);
|
|
2132
|
+
if (on && (p.stage === "received" || p.stage === "working")) (progress[c.id] ??= {})[aid] = { stage: p.stage, ...p.startedAt ? { startedAt: p.startedAt } : {} };
|
|
2133
|
+
return on;
|
|
2119
2134
|
});
|
|
2120
2135
|
if (active.length) typing[c.id] = active;
|
|
2121
2136
|
}
|
|
@@ -2123,7 +2138,7 @@ async function serveLocal(opts = {}) {
|
|
|
2123
2138
|
conversations.sort((a, b) => lastAt(b) - lastAt(a));
|
|
2124
2139
|
if (Object.keys(typing).length) setTimeout(() => void rebuild().catch(() => {
|
|
2125
2140
|
}), 30500);
|
|
2126
|
-
doc = { me, project: projectName ? { id: "local", name: projectName, emoji: projectEmoji } : null, people: [...people.values()], agents: [...agents.values()], hosts: hosts(), conversations, messages, tasks, approvals, enrollments, waitingFor, invitesCreated, typing };
|
|
2141
|
+
doc = { me, project: projectName ? { id: "local", name: projectName, emoji: projectEmoji } : null, people: [...people.values()], agents: [...agents.values()], hosts: hosts(), conversations, messages, tasks, approvals, enrollments, waitingFor, invitesCreated, typing, progress };
|
|
2127
2142
|
bump();
|
|
2128
2143
|
const unknown = [.../* @__PURE__ */ new Set([...people.keys(), ...[...agents.values()].map((a) => a.operatorId)])].filter((id) => id !== meId && !handles.has(id));
|
|
2129
2144
|
if (unknown.length && dir) void (async () => {
|
|
@@ -2384,7 +2399,7 @@ async function serveLocal(opts = {}) {
|
|
|
2384
2399
|
const { d } = need();
|
|
2385
2400
|
const r = await d.createEnrollment(opts.projectTag ?? projectName ?? "project", 900);
|
|
2386
2401
|
const n = cfg.network;
|
|
2387
|
-
const instruction = enrollInstruction(r.token, RUNTIME_OF[b.app ?? "Other"] ?? "other", n);
|
|
2402
|
+
const instruction = enrollInstruction(r.token, RUNTIME_OF[b.app ?? "Other"] ?? "other", n, { name: b.name, model: b.model, bio: b.bio });
|
|
2388
2403
|
waitingFor = b.app ?? "Other";
|
|
2389
2404
|
if (enrollTimer) clearInterval(enrollTimer);
|
|
2390
2405
|
const poll = async () => {
|
|
@@ -2523,12 +2538,12 @@ async function serveLocal(opts = {}) {
|
|
|
2523
2538
|
res.writeHead(status2, { "content-type": "application/json" });
|
|
2524
2539
|
res.end(JSON.stringify(body));
|
|
2525
2540
|
};
|
|
2526
|
-
const readBody = (req) => new Promise((
|
|
2541
|
+
const readBody = (req) => new Promise((resolve2, reject) => {
|
|
2527
2542
|
let t = "";
|
|
2528
2543
|
req.on("data", (c) => t += c);
|
|
2529
2544
|
req.on("end", () => {
|
|
2530
2545
|
try {
|
|
2531
|
-
|
|
2546
|
+
resolve2(t ? JSON.parse(t) : {});
|
|
2532
2547
|
} catch (e) {
|
|
2533
2548
|
reject(new HttpError(400, "bad json"));
|
|
2534
2549
|
}
|
|
@@ -2624,6 +2639,7 @@ import { Dm as Dm3, Group as Group4 } from "@xmtp/node-sdk";
|
|
|
2624
2639
|
// src/llm.ts
|
|
2625
2640
|
import Anthropic from "@anthropic-ai/sdk";
|
|
2626
2641
|
import OpenAI from "openai";
|
|
2642
|
+
import { spawn } from "node:child_process";
|
|
2627
2643
|
function makeLlm(env, ctx = {}) {
|
|
2628
2644
|
const provider = process.env.LLM_PROVIDER || env.LLM_PROVIDER || "openai";
|
|
2629
2645
|
const botName = env.BOT_NAME ?? "";
|
|
@@ -2639,6 +2655,7 @@ function makeLlm(env, ctx = {}) {
|
|
|
2639
2655
|
const m = mockLlm(botName, ctx.otherBots ?? []);
|
|
2640
2656
|
return async (sy, us) => ({ text: await m(sy, us), usage: { in: us.length >> 2, out: 8 } });
|
|
2641
2657
|
}
|
|
2658
|
+
if (provider === "claude-code") return claudeCodeLlm(env, clean);
|
|
2642
2659
|
const model = required(env, "LLM_MODEL");
|
|
2643
2660
|
const maxTokens = Number(env.LLM_MAX_TOKENS ?? 1024);
|
|
2644
2661
|
if (provider === "anthropic") {
|
|
@@ -2680,6 +2697,59 @@ function makeLlm(env, ctx = {}) {
|
|
|
2680
2697
|
return { text: clean(res.choices[0]?.message?.content ?? ""), ...u ? { usage: { in: u.prompt_tokens, out: u.completion_tokens } } : {} };
|
|
2681
2698
|
};
|
|
2682
2699
|
}
|
|
2700
|
+
function claudeCodeLlm(env, clean = (t) => t.trim()) {
|
|
2701
|
+
const bin = process.env.CLAUDE_BIN || env.CLAUDE_BIN || "claude";
|
|
2702
|
+
const cwd = required(env, "LLM_WORKDIR");
|
|
2703
|
+
const toolList = env.LLM_TOOLS || "Read Grep Glob";
|
|
2704
|
+
const tools = toolList.split(toolList.includes(",") ? /\s*,\s*/ : /\s+/).filter(Boolean);
|
|
2705
|
+
const model = env.LLM_MODEL ?? "";
|
|
2706
|
+
const timeoutMs = Number(env.LLM_TIMEOUT_MS ?? 6e5);
|
|
2707
|
+
return (system, user) => new Promise((resolve2, reject) => {
|
|
2708
|
+
const args = [
|
|
2709
|
+
"-p",
|
|
2710
|
+
"--output-format",
|
|
2711
|
+
"json",
|
|
2712
|
+
"--no-session-persistence",
|
|
2713
|
+
"--permission-mode",
|
|
2714
|
+
"dontAsk",
|
|
2715
|
+
"--strict-mcp-config",
|
|
2716
|
+
"--append-system-prompt",
|
|
2717
|
+
system
|
|
2718
|
+
];
|
|
2719
|
+
if (/^(claude|opus|sonnet|haiku|fable)/i.test(model)) args.push("--model", model);
|
|
2720
|
+
args.push("--allowedTools", ...tools);
|
|
2721
|
+
const child = spawn(bin, args, { cwd, stdio: ["pipe", "pipe", "pipe"] });
|
|
2722
|
+
let out2 = "", err = "";
|
|
2723
|
+
const timer = setTimeout(() => {
|
|
2724
|
+
child.kill("SIGTERM");
|
|
2725
|
+
reject(new Error(`claude -p took longer than ${timeoutMs / 1e3}s`));
|
|
2726
|
+
}, timeoutMs);
|
|
2727
|
+
child.stdout.on("data", (d) => {
|
|
2728
|
+
out2 += d;
|
|
2729
|
+
});
|
|
2730
|
+
child.stderr.on("data", (d) => {
|
|
2731
|
+
err += d;
|
|
2732
|
+
});
|
|
2733
|
+
child.on("error", (e) => {
|
|
2734
|
+
clearTimeout(timer);
|
|
2735
|
+
reject(new Error(`could not start ${bin}: ${e.message} (is Claude Code installed?)`));
|
|
2736
|
+
});
|
|
2737
|
+
child.on("close", (code) => {
|
|
2738
|
+
clearTimeout(timer);
|
|
2739
|
+
let r;
|
|
2740
|
+
try {
|
|
2741
|
+
r = JSON.parse(out2);
|
|
2742
|
+
} catch {
|
|
2743
|
+
return reject(new Error(`claude -p exited ${code}: ${(err || out2).trim().slice(0, 300)}`));
|
|
2744
|
+
}
|
|
2745
|
+
if (r.is_error || typeof r.result !== "string") return reject(new Error(`claude -p failed (${r.subtype ?? code}): ${String(r.result ?? err).slice(0, 300)}`));
|
|
2746
|
+
const u = r.usage ?? {};
|
|
2747
|
+
const tokensIn = (u.input_tokens ?? 0) + (u.cache_creation_input_tokens ?? 0) + (u.cache_read_input_tokens ?? 0);
|
|
2748
|
+
resolve2({ text: clean(r.result), usage: { in: tokensIn, out: u.output_tokens ?? 0 } });
|
|
2749
|
+
});
|
|
2750
|
+
child.stdin.end(user);
|
|
2751
|
+
});
|
|
2752
|
+
}
|
|
2683
2753
|
function mockLlm(botName, otherBots = []) {
|
|
2684
2754
|
return async (_system, user) => {
|
|
2685
2755
|
const tagged = [...user.matchAll(/<msg[^>]*\sfrom="([^"]+)"[^>]*>([\s\S]*?)<\/msg>/g)].map((m) => [m[1], unescapeXml(m[2])]);
|
|
@@ -2963,9 +3033,13 @@ Write your next message as @${this.cfg.name}. Reply with the message text only.`
|
|
|
2963
3033
|
}
|
|
2964
3034
|
const { system, user } = this.prompt(conv, reg);
|
|
2965
3035
|
const t0 = Date.now();
|
|
3036
|
+
const presence = (p) => conv.send(encodePresence(p)).catch((e) => this.log(`presence failed ${e.message}`));
|
|
3037
|
+
await presence({ v: 1, state: "typing", ttlMs: 3e4, stage: "received" });
|
|
3038
|
+
const working = () => presence({ v: 1, state: "typing", ttlMs: 3e4, stage: "working", startedAt: t0 });
|
|
3039
|
+
await working();
|
|
3040
|
+
const heartbeat = setInterval(() => void working(), 2e4);
|
|
2966
3041
|
try {
|
|
2967
|
-
await
|
|
2968
|
-
const out2 = await this.llm(system, user);
|
|
3042
|
+
const out2 = await this.llm(system, user).finally(() => clearInterval(heartbeat));
|
|
2969
3043
|
if (!out2.text) return this.log("empty answer, skipping");
|
|
2970
3044
|
const text = out2.text.slice(0, 4e3);
|
|
2971
3045
|
const body = {
|
|
@@ -3226,8 +3300,21 @@ async function agentSet() {
|
|
|
3226
3300
|
const cfg = requireConfig();
|
|
3227
3301
|
const name = argv[0];
|
|
3228
3302
|
if (!name || !cfg.agents[name]) fail(`unknown agent "${name ?? ""}"`);
|
|
3229
|
-
const { values } = parseArgs({ args: argv.slice(1), options: { limit: { type: "string" }, dm: { type: "string" }, a2a: { type: "string" }, public: { type: "string" }, bio: { type: "string" }, skills: { type: "string" }, operator: { type: "string" } }, strict: true });
|
|
3303
|
+
const { values } = parseArgs({ args: argv.slice(1), options: { limit: { type: "string" }, dm: { type: "string" }, a2a: { type: "string" }, public: { type: "string" }, bio: { type: "string" }, skills: { type: "string" }, operator: { type: "string" }, drive: { type: "string" }, workdir: { type: "string" }, tools: { type: "string" }, "drive-model": { type: "string" } }, strict: true });
|
|
3230
3304
|
const a = cfg.agents[name];
|
|
3305
|
+
if (values.drive !== void 0) {
|
|
3306
|
+
if (values.drive === "none") {
|
|
3307
|
+
delete a.drive;
|
|
3308
|
+
out(`${name}: answered only by its own runtime again (moria mcp serve)`);
|
|
3309
|
+
} else if (values.drive === "claude-code") {
|
|
3310
|
+
if (a.provider !== "mcp") fail(`${name} is run by moria with ${a.provider}; --drive is for agents enrolled from a coding app`);
|
|
3311
|
+
const workdir = resolve(values.workdir ?? a.drive?.workdir ?? fail("--workdir DIR required: the project folder Claude Code works in"));
|
|
3312
|
+
if (!existsSync5(workdir)) fail(`--workdir: ${workdir} does not exist`);
|
|
3313
|
+
a.drive = { kind: "claude-code", workdir, ...values.tools ? { tools: values.tools } : a.drive?.tools ? { tools: a.drive.tools } : {}, ...values["drive-model"] ? { model: values["drive-model"] } : a.drive?.model ? { model: a.drive.model } : {} };
|
|
3314
|
+
out(`${name}: moria run answers its mentions with Claude Code in ${workdir} (tools: ${a.drive.tools ?? "Read Grep Glob, read-only"}).`);
|
|
3315
|
+
out(`Don't keep a Claude Code session with the moria MCP server open for ${name} at the same time: both would answer.`);
|
|
3316
|
+
} else fail("--drive must be claude-code or none");
|
|
3317
|
+
} else if (values.workdir || values.tools || values["drive-model"]) fail("--workdir, --tools and --drive-model go with --drive claude-code");
|
|
3231
3318
|
if (values.operator !== void 0) {
|
|
3232
3319
|
const v = values.operator.trim();
|
|
3233
3320
|
const inbox = v === "me" ? cfg.owner.inboxId : /^[0-9a-f]{64}$/.test(v) ? v : (await directoryClient(cfg.network.directoryUrl).profileOf(v.replace(/^@/, "").toLowerCase()))?.inboxId;
|
|
@@ -3321,8 +3408,8 @@ async function run() {
|
|
|
3321
3408
|
const { values } = parseArgs({ args: argv, options: { "legacy-text-mentions": { type: "boolean" }, "agents-may-address-agents": { type: "boolean" }, "stale-ms": { type: "string" } }, strict: true });
|
|
3322
3409
|
const cfg = requireConfig();
|
|
3323
3410
|
const s = await secrets();
|
|
3324
|
-
const active = Object.values(cfg.agents).filter((a) => a.state === "active" && a.provider !== "mcp");
|
|
3325
|
-
if (!active.length) fail(
|
|
3411
|
+
const active = Object.values(cfg.agents).filter((a) => a.state === "active" && (a.provider !== "mcp" || a.drive));
|
|
3412
|
+
if (!active.length) fail("no active agents to run (agents from coding apps answer through their app, or: moria agent set NAME --drive claude-code --workdir DIR)");
|
|
3326
3413
|
const localAgents = Object.fromEntries(Object.values(cfg.agents).filter((a) => a.inboxId).map((a) => [a.name, a.inboxId]));
|
|
3327
3414
|
const runners = [];
|
|
3328
3415
|
const hostLog = (l) => out((/* @__PURE__ */ new Date()).toISOString(), "[moria]", l);
|
|
@@ -3331,7 +3418,7 @@ async function run() {
|
|
|
3331
3418
|
const walletKey = s.get(secretNames.agentWallet(a.name));
|
|
3332
3419
|
const dbKeyHex = s.get(secretNames.agentDbKey(a.name));
|
|
3333
3420
|
if (!walletKey || !dbKeyHex) throw new Error(`missing keys for ${a.name} in the ${s.kind} secrets store`);
|
|
3334
|
-
const llm = { LLM_PROVIDER: a.provider, LLM_MODEL: a.model };
|
|
3421
|
+
const llm = a.drive ? { LLM_PROVIDER: a.drive.kind, LLM_WORKDIR: a.drive.workdir, LLM_MODEL: a.drive.model ?? "", ...a.drive.tools ? { LLM_TOOLS: a.drive.tools } : {} } : { LLM_PROVIDER: a.provider, LLM_MODEL: a.model };
|
|
3335
3422
|
if (a.baseUrl) llm.LLM_BASE_URL = a.baseUrl;
|
|
3336
3423
|
const apiKey = s.get(secretNames.agentApiKey(a.name));
|
|
3337
3424
|
if (apiKey) llm[a.provider === "anthropic" ? "ANTHROPIC_API_KEY" : "LLM_API_KEY"] = apiKey;
|
|
@@ -3384,13 +3471,14 @@ async function enroll() {
|
|
|
3384
3471
|
const cfg = requireConfig();
|
|
3385
3472
|
const dir = directoryClient(cfg.network.directoryUrl, await ownerKey());
|
|
3386
3473
|
if (sub === "create") {
|
|
3387
|
-
const { values } = parseArgs({ args: rest, options: { project: { type: "string" }, ttl: { type: "string" } }, strict: true });
|
|
3474
|
+
const { values } = parseArgs({ args: rest, options: { project: { type: "string" }, ttl: { type: "string" }, name: { type: "string" }, model: { type: "string" }, bio: { type: "string" }, runtime: { type: "string" } }, strict: true });
|
|
3475
|
+
if (values.name && !AGENT_NAME_RE.test(values.name)) fail("--name: 1..32 chars of a-z 0-9 _ -");
|
|
3388
3476
|
const r = await dir.createEnrollment(values.project ?? "default", values.ttl ? Number(values.ttl) : 900);
|
|
3389
3477
|
out(`enrollment ${r.id} (expires ${new Date(r.expiresAt).toISOString()})`);
|
|
3390
3478
|
out(`token: ${r.token}`);
|
|
3391
3479
|
out(`
|
|
3392
3480
|
Paste into the agent's runtime:
|
|
3393
|
-
${enrollInstruction(r.token, "<claude-code|codex|gemini|junie|claude-desktop>", cfg.network)}`);
|
|
3481
|
+
${enrollInstruction(r.token, values.runtime ?? "<claude-code|codex|gemini|junie|claude-desktop>", cfg.network, { name: values.name, model: values.model, bio: values.bio })}`);
|
|
3394
3482
|
out(`Then: moria enroll list \u2192 moria enroll approve ${r.id} [--group <conversation id>]`);
|
|
3395
3483
|
return;
|
|
3396
3484
|
}
|
|
@@ -3641,10 +3729,11 @@ var HELP = `moria \u2014 Moria agent host
|
|
|
3641
3729
|
moria status
|
|
3642
3730
|
moria agent add --name NAME --provider openai|anthropic|mock [--model M] [--base-url URL] [--api-key KEY] [--runtime R] [--egress \u2026] [--no-think] [--handle H] [--bio TEXT] [--dm owner|anyone] [--a2a block|allow]
|
|
3643
3731
|
moria agent list | pause NAME | resume NAME | remove NAME | publish NAME
|
|
3732
|
+
moria agent set NAME [--drive claude-code --workdir DIR [--tools "Read Grep Glob"] [--drive-model M] | --drive none]
|
|
3644
3733
|
moria agent set NAME [--limit 200k/day|none] [--dm owner|anyone] [--a2a block|allow] [--public yes|no] [--bio TEXT] [--skills a,b] [--operator INBOX|@user|me]
|
|
3645
3734
|
moria agent enroll --token qet_\u2026 --name NAME --runtime R --model M [--bio TEXT] [--directory URL] [--api URL] [--history URL]
|
|
3646
3735
|
moria agent connect claude-code|codex|gemini|junie|claude-desktop --agent NAME
|
|
3647
|
-
moria enroll create [--project TAG] [--ttl SEC] | list [status] | approve ID [--group GID]\u2026 | deny ID
|
|
3736
|
+
moria enroll create [--project TAG] [--ttl SEC] [--name N --runtime R --model M --bio TEXT] | list [status] | approve ID [--group GID]\u2026 | deny ID
|
|
3648
3737
|
moria mcp serve --agent NAME
|
|
3649
3738
|
moria serve --local [--port N] (loopback facade for the desktop app)
|
|
3650
3739
|
moria run [--legacy-text-mentions] [--agents-may-address-agents]
|