owlmem 0.7.3 → 0.7.6
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/README.md +11 -8
- package/owlmem.mjs +100 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -29,7 +29,10 @@ page, where you sign in and click **Allow**. AI agents can read the setup guide
|
|
|
29
29
|
|
|
30
30
|
An AI on your computer normally sees a task only when someone opens it. Auto-wake starts it as soon as it gets one:
|
|
31
31
|
a small background program long-polls OwlMem and runs that AI's command line unattended — Claude Code with
|
|
32
|
-
`claude -p`, Codex with `codex exec`, Antigravity with `agy -p` (for Codex and Antigravity, which refuse tool calls when nobody is watching, auto-wake approves just the two OwlMem tools used to report back: in `~/.codex/config.toml` and `~/.gemini/antigravity-cli/settings.json`) — with the task as the prompt. The AI marks the task done or declined as usual.
|
|
32
|
+
`claude -p`, Codex with `codex exec`, Antigravity with `agy -p`, Hermes with `hermes -z` (for Codex and Antigravity, which refuse tool calls when nobody is watching, auto-wake approves just the two OwlMem tools used to report back: in `~/.codex/config.toml` and `~/.gemini/antigravity-cli/settings.json`; for Hermes, only if OwlMem is marked `trust: untrusted`, under `command_allowlist` in `~/.hermes/config.yaml`) — with the task as the prompt. The AI marks the task done or declined as usual.
|
|
33
|
+
|
|
34
|
+
When someone turns auto-wake on for an AI in the console and no computer runs it yet, that AI is told so the next time it connects:
|
|
35
|
+
with your OK it runs `npx -y owlmem wake enable` itself and sends you the pairing link to approve.
|
|
33
36
|
|
|
34
37
|
```sh
|
|
35
38
|
npx -y owlmem install --wake # or answer "y" when install asks; --yes alone leaves it off
|
|
@@ -71,13 +74,13 @@ npx owlmem vault import bitwarden_export.json # also 1Password / KeePass CSV,
|
|
|
71
74
|
Vault items are encrypted on your machine with a key only you hold; OwlMem stores ciphertext it cannot read.
|
|
72
75
|
Every read or write needs the space owner's approval in the console (passkey, authenticator app or GitHub).
|
|
73
76
|
|
|
74
|
-
For unattended jobs (scheduled tasks, overnight deploys) the owner can
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
If an agent without access asks for an item, the answer names up to three agents in the space with a standing grant
|
|
77
|
+
For unattended jobs (scheduled tasks, overnight deploys) the owner can turn on an agent's **standing grant**: in the console
|
|
78
|
+
(Agents, one switch per agent), or while approving one of its requests. The agent can then use any vault item without asking,
|
|
79
|
+
on computers the owner approved (a new computer asks once; approving remembers it), and only through `owlmem run` / `vault_run`,
|
|
80
|
+
so the secret goes into a command. The program signs every vault request with a device key kept next to its config, so the same
|
|
81
|
+
token on another machine gets nothing (`npx owlmem device` prints this machine's fingerprint). Viewing a secret, saving, deleting
|
|
82
|
+
and exporting always need approval.
|
|
83
|
+
If an agent without access asks for an item, the answer names up to three agents in the space with a standing grant,
|
|
81
84
|
so it can hand the job to one of them (`kb_send` with `kind` `task`): that agent runs it with `vault_run` and sends back only
|
|
82
85
|
the result. Secrets themselves never go into messages, documents or memory — OwlMem refuses text that looks like a key.
|
|
83
86
|
|
package/owlmem.mjs
CHANGED
|
@@ -19,18 +19,19 @@
|
|
|
19
19
|
// owlmem vault export [--format bitwarden-json|keepass-csv|env] > file
|
|
20
20
|
// owlmem run --env VAR=<name>[:field] [...] -- <command> [args] run a command with secrets in its environment
|
|
21
21
|
// owlmem vault keygen (setup without a browser) print a new recovery code and its key check
|
|
22
|
-
// owlmem device
|
|
22
|
+
// owlmem device show this machine's fingerprint (standing grants are bound to machines)
|
|
23
23
|
// owlmem mcp local MCP server for your AI (vault_list, vault_run, vault_get, vault_put)
|
|
24
24
|
// e.g. claude mcp add owlmem-vault -- node ~/owlmem.mjs mcp
|
|
25
25
|
//
|
|
26
26
|
// Reading, writing, deleting and exporting need the space owner's approval in the console; this program waits
|
|
27
27
|
// for it (up to 5 minutes). Secrets go to stdout only; messages go to stderr.
|
|
28
28
|
//
|
|
29
|
-
// Standing grants (for unattended jobs): the owner
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
//
|
|
29
|
+
// Standing grants (for unattended jobs): the owner turns one on for an agent in the console (Agents), or while approving
|
|
30
|
+
// one of its requests. The agent can then use any vault item without asking, on computers the owner approved (a new
|
|
31
|
+
// computer asks once; approving remembers it), and only through `owlmem run` / vault_run. This program signs every vault
|
|
32
|
+
// request with a device key kept on this machine (device.json next to config.json), and says whether the secret goes into
|
|
33
|
+
// a command (`owlmem run`, vault_run) or is shown (`vault get`, vault_get). A token copied to another machine has no
|
|
34
|
+
// device key there, so standing grants don't apply. Viewing, writing, deleting and exporting always need approval.
|
|
34
35
|
|
|
35
36
|
import { webcrypto as crypto } from "node:crypto";
|
|
36
37
|
import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync, copyFileSync, rmSync, statSync, realpathSync, openSync, closeSync, appendFileSync } from "node:fs";
|
|
@@ -39,7 +40,7 @@ import { join, dirname, basename, relative, isAbsolute } from "node:path";
|
|
|
39
40
|
import { fileURLToPath } from "node:url";
|
|
40
41
|
import { spawn, spawnSync } from "node:child_process";
|
|
41
42
|
|
|
42
|
-
export const VERSION = "0.7.
|
|
43
|
+
export const VERSION = "0.7.6";
|
|
43
44
|
const BASE = (process.env.OWLMEM_URL || "https://owlmem.com").replace(/\/$/, "");
|
|
44
45
|
const DIR = process.env.OWLMEM_HOME || join(process.env.XDG_CONFIG_HOME || join(homedir(), ".config"), "owlmem");
|
|
45
46
|
const enc = new TextEncoder(), dec = new TextDecoder();
|
|
@@ -232,7 +233,7 @@ async function approved(method, path, body, kind = "access", mode = "other") {
|
|
|
232
233
|
if (r.status < 400) return r.data;
|
|
233
234
|
if (r.data.error !== "approval_required") throw new Fail(r.data.detail || r.data.error || "HTTP " + r.status);
|
|
234
235
|
const id = r.data.request;
|
|
235
|
-
// the server may name other agents that could do this for us (a standing grant
|
|
236
|
+
// the server may name other agents that could do this for us (they have a standing grant) — names only
|
|
236
237
|
let hint = r.data.hint || "";
|
|
237
238
|
say(`Waiting for approval in the OwlMem console (request ${id}${kind === "access" ? "" : ", " + kind}) …`);
|
|
238
239
|
if (hint) say(hint);
|
|
@@ -245,7 +246,7 @@ async function approved(method, path, body, kind = "access", mode = "other") {
|
|
|
245
246
|
if (st !== "pending") break;
|
|
246
247
|
}
|
|
247
248
|
const tail = hint ? " " + hint : "";
|
|
248
|
-
if (st === "pending") throw new Fail(`Waiting for approval (request ${id}). Ask the space owner to approve it in the OwlMem console (Vault), then try again. For unattended jobs, ask them
|
|
249
|
+
if (st === "pending") throw new Fail(`Waiting for approval (request ${id}). Ask the space owner to approve it in the OwlMem console (Vault), then try again. For unattended jobs, ask them to turn on your standing grant (console → Agents, or when they approve).` + tail);
|
|
249
250
|
if (st !== "approved") throw new Fail((st === "denied" ? "The request was denied." : "The request expired before it was approved.") + tail);
|
|
250
251
|
say("Approved.");
|
|
251
252
|
if (kind !== "access") extra = (path.includes("?") ? "&" : "?") + "request=" + encodeURIComponent(id);
|
|
@@ -430,7 +431,7 @@ function pick(body, field) {
|
|
|
430
431
|
const MCP_TOOLS = [
|
|
431
432
|
{ name: "vault_list", description: "List the credentials in the team vault (names and descriptions only; no secrets).", inputSchema: { type: "object", properties: {} } },
|
|
432
433
|
{ name: "vault_run",
|
|
433
|
-
description: "PREFERRED way to use a secret: run a shell command with vault items injected as environment variables. The secret values are masked as **** in the output returned to you, so they never enter the conversation. Needs the space owner's approval in the OwlMem console the first time (then 15 min – 2 h), unless they
|
|
434
|
+
description: "PREFERRED way to use a secret: run a shell command with vault items injected as environment variables. The secret values are masked as **** in the output returned to you, so they never enter the conversation. Needs the space owner's approval in the OwlMem console the first time (then 15 min – 2 h), unless they turned on your standing grant and approved this computer — standing grants cover vault_run only, never vault_get.",
|
|
434
435
|
inputSchema: { type: "object", properties: {
|
|
435
436
|
command: { type: "string", description: "Shell command to run (sh -c)" },
|
|
436
437
|
env: { type: "object", description: 'Map of ENV_VAR to "item" or "item:field", e.g. {"DB_PASSWORD": "prod/db:password"}', additionalProperties: { type: "string" } },
|
|
@@ -806,14 +807,22 @@ export const WAKE_CLIENTS = {
|
|
|
806
807
|
command: ["claude", "-p", "--allowedTools", "mcp__owlmem__kb_task_update,mcp__owlmem__kb_inbox"], prompt: "stdin" },
|
|
807
808
|
codex: { name: "Codex", bin: "codex", command: ["codex", "exec", "--skip-git-repo-check", "-"], prompt: "stdin" },
|
|
808
809
|
// agy -p takes the prompt as an argument; headless it denies MCP tools unless settings.json allows them (see allowAntigravity)
|
|
809
|
-
antigravity: { name: "Antigravity", bin: "agy", home: [".gemini", "bin", "agy"], command: ["agy", "-p"], prompt: "arg" },
|
|
810
|
+
antigravity: { name: "Antigravity", bin: "agy", home: [[".gemini", "bin", "agy"]], command: ["agy", "-p"], prompt: "arg" },
|
|
811
|
+
// hermes -z runs one prompt (an argument) and exits; it calls MCP tools without asking unless OwlMem is marked untrusted (see allowHermes).
|
|
812
|
+
// Its installer puts hermes under ~/.hermes/hermes-agent or ~/.local/bin; a system-wide service install under /var/lib/hermes
|
|
813
|
+
hermes: { name: "Hermes", bin: "hermes", command: ["hermes", "-z"], prompt: "arg",
|
|
814
|
+
home: [[".hermes", "hermes-agent", ".hermes", "bin", "hermes"], [".local", "bin", "hermes"], "/var/lib/hermes/hermes-agent/.hermes/bin/hermes"] },
|
|
810
815
|
};
|
|
811
|
-
export const WAKE_APP_IDS = ["claude-code", "codex", "antigravity", "gemini", "custom"];
|
|
812
|
-
// Where an app's command is: on PATH, or
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
816
|
+
export const WAKE_APP_IDS = ["claude-code", "codex", "antigravity", "hermes", "gemini", "custom"];
|
|
817
|
+
// Where an app's command is: on PATH, or the first of its default install places that exists (path parts under your home
|
|
818
|
+
// folder, or an absolute path), used by full path
|
|
819
|
+
export function wakeBin(c, home_ = home(), exists = existsSync, path = onPath) {
|
|
820
|
+
if (path(c.bin)) return c.bin;
|
|
821
|
+
for (const w of c.home || []) {
|
|
822
|
+
const p = typeof w === "string" ? w : join(home_, ...w);
|
|
823
|
+
if (exists(p)) return p;
|
|
824
|
+
}
|
|
825
|
+
return null;
|
|
817
826
|
}
|
|
818
827
|
// codex exec runs with approval "never", so an MCP call that needs approval is refused: approve just the two OwlMem
|
|
819
828
|
// tools it needs to report back, as tool tables under the [mcp_servers.owlmem] that install wrote (append only)
|
|
@@ -840,6 +849,56 @@ export function allowAntigravity(home_ = home()) {
|
|
|
840
849
|
writeFileSync(f, JSON.stringify(j, null, 2) + "\n");
|
|
841
850
|
return true;
|
|
842
851
|
}
|
|
852
|
+
// Hermes calls MCP tools without asking unless the server is marked `trust: untrusted` (or an unknown value) under mcp_servers in
|
|
853
|
+
// ~/.hermes/config.yaml; then unattended runs refuse its write tools. Only in that case, add the two OwlMem tools it reports back
|
|
854
|
+
// with to the top-level command_allowlist as mcp:<server>/<tool> (what Hermes itself writes for "Always"). Adds lines only,
|
|
855
|
+
// never rewrites what's there; a command_allowlist it can't extend safely (not a plain block list) returns "manual".
|
|
856
|
+
// false: nothing to do (no config, no OwlMem server in it, a trusted one, or the entries are already there).
|
|
857
|
+
export function allowHermes(home_ = home(), env = process.env) {
|
|
858
|
+
const f = join(env.HERMES_HOME || join(home_, ".hermes"), "config.yaml");
|
|
859
|
+
let t; try { t = readFileSync(f, "utf8"); } catch { return false; }
|
|
860
|
+
const lines = t.split(/\r?\n/), ind = (l) => l.match(/^ */)[0].length, live = (l) => !!l.trim() && !l.trim().startsWith("#");
|
|
861
|
+
const top = lines.findIndex((l) => /^mcp_servers:\s*(#.*)?$/.test(l));
|
|
862
|
+
if (top < 0) return false;
|
|
863
|
+
let end = top + 1;
|
|
864
|
+
while (end < lines.length && (!live(lines[end]) || ind(lines[end]) > 0)) end++;
|
|
865
|
+
const kids = [];
|
|
866
|
+
let step = 0;
|
|
867
|
+
for (let i = top + 1; i < end; i++) if (live(lines[i])) { step ||= ind(lines[i]); if (ind(lines[i]) === step) kids.push(i); }
|
|
868
|
+
let server = null, trust = "full";
|
|
869
|
+
for (const [k, i] of kids.entries()) { // the OwlMem entry: named owlmem, or its url is ours
|
|
870
|
+
const m = lines[i].trim().match(/^["']?([A-Za-z0-9_.-]+)["']?:\s*(#.*)?$/);
|
|
871
|
+
if (!m) continue;
|
|
872
|
+
const body = lines.slice(i + 1, k + 1 < kids.length ? kids[k + 1] : end), first = body.find(live);
|
|
873
|
+
if (m[1] !== "owlmem" && !body.some((l) => l.includes("owlmem.com/mcp"))) continue;
|
|
874
|
+
server = m[1];
|
|
875
|
+
const tr = first && body.find((l) => ind(l) === ind(first) && /^\s*trust:/.test(l));
|
|
876
|
+
if (tr) trust = tr.replace(/#.*/, "").slice(tr.indexOf(":") + 1).trim().replace(/^["']|["']$/g, "");
|
|
877
|
+
break;
|
|
878
|
+
}
|
|
879
|
+
if (!server || trust === "full") return false;
|
|
880
|
+
const need = ["kb_task_update", "kb_inbox"].map((n) => "mcp:" + server + "/" + n);
|
|
881
|
+
const at = lines.findIndex((l) => /^command_allowlist:/.test(l));
|
|
882
|
+
if (at < 0) {
|
|
883
|
+
writeFileSync(f, t.replace(/\n*$/, "\n") + "# OwlMem auto-wake: unattended hermes -z may call these two OwlMem tools to report back\ncommand_allowlist:\n" +
|
|
884
|
+
need.map((r) => " - " + r + "\n").join(""));
|
|
885
|
+
return true;
|
|
886
|
+
}
|
|
887
|
+
if (!/^command_allowlist:\s*(#.*)?$/.test(lines[at])) return "manual"; // [a, b] on one line, or a string: leave it to the person
|
|
888
|
+
let last = at, pad = null;
|
|
889
|
+
for (let i = at + 1; i < lines.length && (!live(lines[i]) || ind(lines[i]) > 0 || lines[i].startsWith("- ")); i++) {
|
|
890
|
+
if (!live(lines[i])) continue;
|
|
891
|
+
if (!/^\s*- /.test(lines[i])) return "manual";
|
|
892
|
+
pad ??= lines[i].match(/^\s*/)[0];
|
|
893
|
+
last = i;
|
|
894
|
+
}
|
|
895
|
+
const have = lines.slice(at + 1, last + 1).map((l) => l.replace(/\s#.*/, "").trim().replace(/^-\s*/, "").replace(/^["']|["']$/g, ""));
|
|
896
|
+
const add = need.filter((r) => !have.includes(r));
|
|
897
|
+
if (!add.length) return false;
|
|
898
|
+
lines.splice(last + 1, 0, ...add.map((r) => (pad ?? " ") + "- " + r));
|
|
899
|
+
writeFileSync(f, lines.join("\n"));
|
|
900
|
+
return true;
|
|
901
|
+
}
|
|
843
902
|
export const WAKE_DEFAULTS = { max_parallel: 2, timeout_min: 30 };
|
|
844
903
|
const WAKE_UNIT = "owlmem-wake.service", WAKE_LABEL = "com.owlmem.wake", WAKE_TASK = "OwlMem Wake", WAKE_RUNKEY = "OwlMemWake";
|
|
845
904
|
const wakeDir = (dir = DIR) => join(dir, "wake");
|
|
@@ -1158,6 +1217,15 @@ async function wakeRun() {
|
|
|
1158
1217
|
if (out === "revoked") process.exit(0);
|
|
1159
1218
|
}
|
|
1160
1219
|
|
|
1220
|
+
// No command line auto-wake can start: tell OwlMem support which AI apps this computer has, so it can add them. Only the
|
|
1221
|
+
// system, CPU architecture, node version and app names (what install detects) — no paths, user name or computer name.
|
|
1222
|
+
export async function wakeNoClients(api, items = null) {
|
|
1223
|
+
let apps = [];
|
|
1224
|
+
try { apps = (items || plan()).filter((r) => r.state !== "missing").map((r) => r.name); } catch {}
|
|
1225
|
+
const r = await api("feedback", { os: platform(), arch: process.arch, node: process.versions.node, apps: [...new Set(apps)] });
|
|
1226
|
+
return r.status === 200;
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1161
1229
|
async function wakeEnable(args = []) {
|
|
1162
1230
|
const boot = args.includes("--boot"), again = args.includes("--again");
|
|
1163
1231
|
const num = (name, lo, hi) => { const i = args.indexOf(name); if (i < 0) return undefined; const n = Number(args[i + 1]); if (!Number.isInteger(n) || n < lo || n > hi) throw new Fail(`${name} takes a whole number from ${lo} to ${hi}.`); return n; };
|
|
@@ -1168,10 +1236,18 @@ async function wakeEnable(args = []) {
|
|
|
1168
1236
|
const bin = !clients[id] && wakeBin(c);
|
|
1169
1237
|
if (bin) clients[id] = { command: [bin, ...c.command.slice(1)], prompt: c.prompt };
|
|
1170
1238
|
}
|
|
1171
|
-
if (!Object.keys(clients).length)
|
|
1172
|
-
|
|
1239
|
+
if (!Object.keys(clients).length) {
|
|
1240
|
+
const told = await wakeNoClients(wakeApi()).catch(() => false);
|
|
1241
|
+
throw new Fail("No AI command line found on this computer that auto-wake can start (Claude Code: claude, Codex: codex, Antigravity: agy, Hermes: hermes). Install one, or add its command to " + wakeFile() + " under \"clients\"." +
|
|
1242
|
+
(told ? "\nOwlMem support has been told; you'll hear from us once your AI app is supported." : ""));
|
|
1243
|
+
}
|
|
1173
1244
|
if (clients.codex && allowCodex()) say("Let Codex report back to OwlMem when nobody is watching (2 tool approvals in ~/.codex/config.toml).");
|
|
1174
1245
|
if (clients.antigravity && allowAntigravity()) say("Let Antigravity's command line report back to OwlMem when nobody is watching (2 rules in ~/.gemini/antigravity-cli/settings.json).");
|
|
1246
|
+
if (clients.hermes) {
|
|
1247
|
+
const h = allowHermes();
|
|
1248
|
+
if (h === true) say("Let Hermes report back to OwlMem when nobody is watching (2 entries under command_allowlist in ~/.hermes/config.yaml).");
|
|
1249
|
+
else if (h === "manual") say("Hermes treats OwlMem as untrusted: add mcp:<server>/kb_task_update and mcp:<server>/kb_inbox to command_allowlist in ~/.hermes/config.yaml, or it can't report back when nobody is watching.");
|
|
1250
|
+
}
|
|
1175
1251
|
const cfg = { v: 1, ...WAKE_DEFAULTS, ...old, machine: process.env.OWLMEM_DEVICE_NAME || hostname(), clients, ...(max ? { max_parallel: max } : {}), ...(timeout ? { timeout_min: timeout } : {}), boot: boot || !!old.boot };
|
|
1176
1252
|
// OwlMem knows these app names; a command you add yourself to wake.json goes under "gemini" or "custom"
|
|
1177
1253
|
const r = await wakeApi()("pair", { clients: Object.keys(clients).filter((c) => WAKE_APP_IDS.includes(c)), force: again });
|
|
@@ -1184,7 +1260,8 @@ async function wakeEnable(args = []) {
|
|
|
1184
1260
|
say("Auto-wake is installed (" + a.kind + ": " + tilde(a.file) + ").");
|
|
1185
1261
|
say("Apps it can start here: " + Object.keys(clients).map((c) => WAKE_CLIENTS[c]?.name || c).join(", ") + ". At most " + cfg.max_parallel + " at once, " + cfg.timeout_min + " minutes per task.");
|
|
1186
1262
|
if (r.data.status === "pending") {
|
|
1187
|
-
say("\nApprove this computer in the OwlMem console and pick which AI each app is:\n\n " + r.data.url + "\n\nCheck that the console shows this code: " + r.data.code + "\nUntil then auto-wake waits and starts nothing."
|
|
1263
|
+
say("\nApprove this computer in the OwlMem console and pick which AI each app is:\n\n " + r.data.url + "\n\nCheck that the console shows this code: " + r.data.code + "\nUntil then auto-wake waits and starts nothing." +
|
|
1264
|
+
"\n(If you're an AI agent running this: send the link and the code to the person who turned auto-wake on, with kb_send.)");
|
|
1188
1265
|
openBrowser(r.data.url);
|
|
1189
1266
|
} else say("This computer is already paired (" + r.data.code + "). Turn auto-wake on or off for each AI in the console (AI agents → Agents).");
|
|
1190
1267
|
}
|
|
@@ -1218,7 +1295,7 @@ async function wakeStatus() {
|
|
|
1218
1295
|
const WAKE_HELP = `Usage: npx -y owlmem wake <enable|disable|status>
|
|
1219
1296
|
|
|
1220
1297
|
Auto-wake starts the AI on this computer when it gets an OwlMem task (Claude Code with \`claude -p\`, Codex with
|
|
1221
|
-
\`codex exec\`), without you opening it. It runs in the background from login, for this user only.
|
|
1298
|
+
\`codex exec\`, Antigravity with \`agy -p\`, Hermes with \`hermes -z\`), without you opening it. It runs in the background from login, for this user only.
|
|
1222
1299
|
|
|
1223
1300
|
enable [--boot] [--max N] [--timeout MIN] [--again]
|
|
1224
1301
|
install it and pair this computer (approve in the console). --boot (Linux): run from boot,
|
|
@@ -1262,12 +1339,12 @@ export async function main(argv) {
|
|
|
1262
1339
|
if (cmd === "uninstall") { await install(["--uninstall", ...argv.slice(1)]); return; }
|
|
1263
1340
|
if (cmd === "mcp") { await mcpServe(); return; }
|
|
1264
1341
|
if (cmd === "wake") { await wakeCmd(sub, rest); return; }
|
|
1265
|
-
if (cmd === "device") { //
|
|
1342
|
+
if (cmd === "device") { // show this machine's fingerprint (the console lists approved computers with it)
|
|
1266
1343
|
const r = await call("POST", "device", {});
|
|
1267
1344
|
if (r.status >= 400) throw new Fail(r.data.detail || r.data.error || "HTTP " + r.status);
|
|
1268
1345
|
say("This machine: " + (process.env.OWLMEM_DEVICE_NAME || hostname()));
|
|
1269
1346
|
console.log(r.data.device);
|
|
1270
|
-
say("
|
|
1347
|
+
say("This computer's fingerprint for " + r.data.agent + ". With a standing grant on, the first use here asks once; approving remembers this computer.");
|
|
1271
1348
|
return;
|
|
1272
1349
|
}
|
|
1273
1350
|
if (cmd === "login") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "owlmem",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.6",
|
|
4
4
|
"description": "OwlMem command line: connect your AI apps to OwlMem in one command (npx -y owlmem install), an end-to-end encrypted vault for your AI agents, and a local MCP server.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": { "owlmem": "owlmem.mjs" },
|