memgineering 0.16.0 → 0.17.0

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/CHANGELOG.md CHANGED
@@ -11,6 +11,28 @@ language the reader wants. The bilingual rule the monorepo applies to
11
11
 
12
12
  ## [Unreleased]
13
13
 
14
+ ## [0.17.0] — 2026-08-30
15
+
16
+ ### Added
17
+
18
+ - **One sign-in, both halves.** `memgineering login` now links your registry
19
+ account automatically — same Google account, no extra step — and stores a
20
+ registry session next to the brain token. `memgineering whoami` shows both
21
+ identities (brain name and registry username); `memgineering logout` ends
22
+ both. If the registry half is ever unreachable during login, the brain
23
+ sign-in still completes and whoami says how to link later.
24
+ - **Approving a login is one click now.** The browser approval screen keys off
25
+ your memgineering.com sign-in: signed in, you see who is asking and press
26
+ 승인 — no separate Google round trip. Signed out, the same page signs you in
27
+ first (Google in a popup) and then asks. Consent is requested exactly once,
28
+ when an account is first created.
29
+
30
+ ### Agent guidance
31
+
32
+ - `memgineering-setup` explains the unified sign-in: one login carries brain
33
+ and registry credentials together, and `whoami` reports both. Restart agent
34
+ sessions after upgrading so the refreshed guidance loads.
35
+
14
36
  ## [0.16.0] — 2026-08-30
15
37
 
16
38
  ### Added
@@ -2,7 +2,7 @@
2
2
  name: memgineering
3
3
  description: Use whenever the user refers to something they told you before, asks what was decided, tells you something worth keeping, or settles something that should hold next time. The memory lives in their own folder and outlives this session; check it before answering from guesswork, and write to it when you learn something durable.
4
4
  type: skill
5
- version: 0.16.0
5
+ version: 0.17.0
6
6
  ---
7
7
 
8
8
  # memgineering
@@ -45,6 +45,18 @@ opening nothing. Show the user the code — they approve it in their browser whi
45
45
  you keep working, and the next command picks the token up. Nothing exists on the
46
46
  server until they approve.
47
47
 
48
+ **One sign-in covers both halves (0.17.0+).** The approval also links the
49
+ user's registry account (same Google account, automatic), so the claim stores a
50
+ registry session next to the brain token. `memgineering whoami` reports both —
51
+ the brain identity and a `registry:` line with the username. If the registry
52
+ line says "not linked", the link simply did not happen at sign-in time (older
53
+ server, registry briefly down); a fresh `logout` + `login` links it. Never
54
+ treat a missing registry line as a failed brain sign-in — the brain token
55
+ stands on its own. On the approval page itself, a user already signed in to
56
+ memgineering.com approves with one click; a signed-out user signs in right
57
+ there first (Google in a popup). Consent is asked exactly once, when an
58
+ account is first created.
59
+
48
60
  `--no-login` is how someone says "this machine only". Offer it, do not assume it:
49
61
  a brain that lives in a folder here cannot follow them to their phone or their
50
62
  next laptop, and that is discovered weeks later, by which time the notes are in
package/dist/index.js CHANGED
@@ -4074,6 +4074,11 @@ var init_src = __esm({
4074
4074
  import { mkdir as mkdir3, readFile as readFile3, unlink, writeFile as writeFile2 } from "fs/promises";
4075
4075
  import { join as join5 } from "path";
4076
4076
  import { z as z6 } from "zod";
4077
+ function registryFromClaim(raw) {
4078
+ const parsed = RegistryCredentialsSchema.omit({ linked_at: true }).safeParse(raw);
4079
+ if (!parsed.success) return void 0;
4080
+ return { ...parsed.data, linked_at: (/* @__PURE__ */ new Date()).toISOString() };
4081
+ }
4077
4082
  function credentialsPath() {
4078
4083
  return join5(brandHome(), "credentials.json");
4079
4084
  }
@@ -4098,18 +4103,33 @@ async function saveCredentials(credentials) {
4098
4103
  async function clearCredentials() {
4099
4104
  await unlink(credentialsPath()).catch(() => void 0);
4100
4105
  }
4101
- var CredentialsSchema;
4106
+ var RegistryCredentialsSchema, CredentialsSchema;
4102
4107
  var init_credentials = __esm({
4103
4108
  "src/lib/credentials.ts"() {
4104
4109
  "use strict";
4105
4110
  init_brand();
4111
+ RegistryCredentialsSchema = z6.object({
4112
+ api_url: z6.string().min(1),
4113
+ refresh_token: z6.string().min(1),
4114
+ session_id: z6.string().min(1),
4115
+ username: z6.string().min(1),
4116
+ user_id: z6.string().min(1),
4117
+ linked_at: z6.string().min(1)
4118
+ });
4106
4119
  CredentialsSchema = z6.object({
4107
4120
  /** The server that issued this token. Compared before the token is ever sent. */
4108
4121
  api_url: z6.string().min(1),
4109
4122
  token: z6.string().min(1),
4110
4123
  account_id: z6.string().min(1),
4111
4124
  display_name: z6.string().nullable().default(null),
4112
- signed_in_at: z6.string().min(1)
4125
+ signed_in_at: z6.string().min(1),
4126
+ /**
4127
+ * Absent on pre-federation files and after an old CLI resaves this file
4128
+ * (strip mode drops keys it does not know). `catch` matters more than
4129
+ * `optional`: a mangled registry block must degrade to "not linked", never
4130
+ * to "not signed in" — the brain token above is still perfectly good.
4131
+ */
4132
+ registry: RegistryCredentialsSchema.optional().catch(void 0)
4113
4133
  });
4114
4134
  }
4115
4135
  });
@@ -4322,12 +4342,14 @@ async function claimOnce(pending, baseUrl = apiUrl()) {
4322
4342
  );
4323
4343
  }
4324
4344
  const me = await apiRequest({ method: "GET", path: "/v1/me", token, baseUrl });
4345
+ const registry = registryFromClaim(res.body?.registry);
4325
4346
  const credentials = {
4326
4347
  api_url: baseUrl,
4327
4348
  token,
4328
4349
  account_id: me.body?.account?.id ?? "",
4329
4350
  display_name: me.body?.account?.display_name ?? null,
4330
- signed_in_at: (/* @__PURE__ */ new Date()).toISOString()
4351
+ signed_in_at: (/* @__PURE__ */ new Date()).toISOString(),
4352
+ ...registry !== void 0 ? { registry } : {}
4331
4353
  };
4332
4354
  await saveCredentials(credentials);
4333
4355
  await clearPendingLogin();
@@ -6152,7 +6174,8 @@ async function waitForApproval(pending, base) {
6152
6174
  json: {
6153
6175
  signed_in: true,
6154
6176
  account: { id: claimed.account_id, display_name: claimed.display_name },
6155
- api_url: base
6177
+ api_url: base,
6178
+ registry: claimed.registry ? { username: claimed.registry.username, api_url: claimed.registry.api_url } : null
6156
6179
  },
6157
6180
  human: () => {
6158
6181
  printHuman(
@@ -6160,6 +6183,13 @@ async function waitForApproval(pending, base) {
6160
6183
  Signed in${claimed.display_name ? ` as ${c.bold(oneLine(claimed.display_name, WIDTH.title))}` : ""}.`
6161
6184
  );
6162
6185
  printHuman(c.gray(" The token is stored on this machine only, readable by you alone."));
6186
+ if (claimed.registry) {
6187
+ printHuman(
6188
+ c.gray(
6189
+ ` Registry linked: ${oneLine(claimed.registry.username, WIDTH.handle)} \u2014 one sign-in, both halves.`
6190
+ )
6191
+ );
6192
+ }
6163
6193
  }
6164
6194
  });
6165
6195
  return;
@@ -15702,10 +15732,12 @@ function logoutCommand() {
15702
15732
  }) : err;
15703
15733
  }
15704
15734
  }
15735
+ const registryRevoked = await revokeRegistrySession(credentials.registry);
15705
15736
  await clearCredentials();
15706
15737
  printDual({
15707
15738
  json: {
15708
15739
  signed_out: true,
15740
+ registry: credentials.registry === void 0 ? "not-linked" : registryRevoked ? "revoked" : "revoke-failed-expires-on-its-own",
15709
15741
  revoked_on_server: true,
15710
15742
  revoked,
15711
15743
  all: opts.all === true,
@@ -15727,6 +15759,28 @@ function logoutCommand() {
15727
15759
  });
15728
15760
  });
15729
15761
  }
15762
+ async function revokeRegistrySession(reg) {
15763
+ if (!reg) return false;
15764
+ try {
15765
+ const refreshed = await fetch(`${reg.api_url}/auth/sessions/refresh`, {
15766
+ method: "POST",
15767
+ headers: { "content-type": "application/json" },
15768
+ body: JSON.stringify({ refresh_token: reg.refresh_token }),
15769
+ signal: AbortSignal.timeout(3e3)
15770
+ });
15771
+ if (!refreshed.ok) return false;
15772
+ const pair = await refreshed.json();
15773
+ if (typeof pair.access_token !== "string" || typeof pair.session_id !== "string") return false;
15774
+ const del = await fetch(`${reg.api_url}/auth/sessions/${pair.session_id}`, {
15775
+ method: "DELETE",
15776
+ headers: { authorization: `Bearer ${pair.access_token}` },
15777
+ signal: AbortSignal.timeout(3e3)
15778
+ });
15779
+ return del.ok;
15780
+ } catch {
15781
+ return false;
15782
+ }
15783
+ }
15730
15784
 
15731
15785
  // src/commands/onboard.ts
15732
15786
  init_src();
@@ -18092,7 +18146,12 @@ function whoamiCommand() {
18092
18146
  },
18093
18147
  tokens: { live },
18094
18148
  api_url: base,
18095
- signed_in_at: credentials.signed_in_at
18149
+ signed_in_at: credentials.signed_in_at,
18150
+ registry: credentials.registry ? {
18151
+ username: credentials.registry.username,
18152
+ api_url: credentials.registry.api_url,
18153
+ linked_at: credentials.registry.linked_at
18154
+ } : null
18096
18155
  },
18097
18156
  human: () => {
18098
18157
  printHuman(
@@ -18102,6 +18161,19 @@ function whoamiCommand() {
18102
18161
  );
18103
18162
  if (account.email) printHuman(c.gray(` ${oneLine(account.email, WIDTH.title)}`));
18104
18163
  printHuman(c.gray(` on ${base}`));
18164
+ if (credentials.registry) {
18165
+ printHuman(
18166
+ c.gray(
18167
+ ` registry: ${oneLine(credentials.registry.username, WIDTH.handle)} on ${credentials.registry.api_url}`
18168
+ )
18169
+ );
18170
+ } else {
18171
+ printHuman(
18172
+ c.gray(
18173
+ " registry: not linked \u2014 sign in again (`memgineering logout`, then `login`) to link it."
18174
+ )
18175
+ );
18176
+ }
18105
18177
  if (live !== null) {
18106
18178
  printHuman(
18107
18179
  c.gray(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memgineering",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "private": false,
5
5
  "description": "One memory for the AI you connect. Recall, remember, and revise a brain your agents share \u2014 stored in your own folder.",
6
6
  "license": "Apache-2.0",