create-egregore 0.21.0 → 0.21.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.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // egregore-launcher-version: 0.21.0
2
+ // egregore-launcher-version: 0.21.1
3
3
  //
4
4
  // egregore — launch and browse Egregore instances from your terminal.
5
5
  // Installed by create-egregore. Reads ~/.egregore/instances.json.
@@ -35,7 +35,7 @@ const crypto = require("node:crypto");
35
35
  const { spawnSync, spawn } = require("node:child_process");
36
36
  const readline = require("node:readline");
37
37
 
38
- const VERSION = "0.21.0";
38
+ const VERSION = "0.21.1";
39
39
  const REGISTRY = path.join(os.homedir(), ".egregore", "instances.json");
40
40
  const DEFAULT_FILE = path.join(os.homedir(), ".egregore", "default");
41
41
  const UPDATE_CACHE_FILE = path.join(os.homedir(), ".egregore", "update-check.json");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-egregore",
3
- "version": "0.21.0",
3
+ "version": "0.21.1",
4
4
  "description": "Set up Egregore for your team in one command \u2014 installs the egregore launcher and your choice of Claude Code, Codex, or Pi.",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -11,6 +11,7 @@ from dataclasses import replace
11
11
  import hashlib
12
12
  import json
13
13
  import os
14
+ import re
14
15
  from pathlib import Path, PurePosixPath
15
16
  import tempfile
16
17
  from typing import Any, Mapping
@@ -101,6 +102,26 @@ def _changed_fields(fields: Mapping[str, Any], source_id: str, destination_id: s
101
102
  return updates
102
103
 
103
104
 
105
+ def _malformed_transfer_candidate(markdown: str, source_id: str) -> bool:
106
+ """Identify an invalid document that could still require ownership transfer.
107
+
108
+ Existing memory can contain old or adversarial Markdown that is not a valid
109
+ Runtime artifact. Identity reconciliation must not fail because an unrelated
110
+ document is malformed, but it must fail closed when that document explicitly
111
+ names the current actor as a transferable owner.
112
+ """
113
+
114
+ if not source_id:
115
+ return False
116
+ actor = re.escape(source_id)
117
+ question = re.search(r"(?m)^type:\s*[\"']?question[\"']?\s*$", markdown)
118
+ recipient = re.search(rf"(?m)^to_actor_id:\s*[\"']?{actor}[\"']?\s*$", markdown)
119
+ thread = re.search(r"(?m)^type:\s*[\"']?thread[\"']?\s*$", markdown)
120
+ steward = re.search(rf"(?m)^steward_id:\s*[\"']?{actor}[\"']?\s*$", markdown)
121
+ stewards = re.search(rf"(?m)^steward_ids:\s*.*\b{actor}\b", markdown)
122
+ return bool((question and recipient) or (thread and (steward or stewards)))
123
+
124
+
104
125
  def _document(before: str, path: str, pair: Mapping[str, Any], transfer_id: str):
105
126
  fields, _ = split_frontmatter(before)
106
127
  updates = _changed_fields(fields, pair["source"]["actor_id"], pair["destination"]["actor_id"])
@@ -148,7 +169,18 @@ def _inventory(memory: Path, runtime, source: ActorContext, destination_id: str)
148
169
  if len(raw) > MAX_DOCUMENT_BYTES or size > MAX_SCAN_BYTES:
149
170
  raise IdentityTransferError("ownership inventory exceeds its byte limit; retain the current identity")
150
171
  before = raw.decode("utf-8")
151
- fields, _ = split_frontmatter(before)
172
+ try:
173
+ fields, _ = split_frontmatter(before)
174
+ except ValueError as error:
175
+ # A malformed legacy or test document outside the transfer
176
+ # contract must not block identity reconciliation. If it names
177
+ # the current owner, fail closed so ownership is never left
178
+ # half-transferred.
179
+ if _malformed_transfer_candidate(before, source.actor.actor_id):
180
+ raise IdentityTransferError(
181
+ f"ownership inventory cannot parse {relative}: {error}"
182
+ ) from error
183
+ continue
152
184
  if (fields.get("org") or fields.get("org_id")) not in {None, source.profile.org_id}:
153
185
  continue
154
186
  if _changed_fields(fields, source.actor.actor_id, destination_id):
@@ -11,6 +11,7 @@ from dataclasses import replace
11
11
  import hashlib
12
12
  import json
13
13
  import os
14
+ import re
14
15
  from pathlib import Path, PurePosixPath
15
16
  import tempfile
16
17
  from typing import Any, Mapping
@@ -101,6 +102,26 @@ def _changed_fields(fields: Mapping[str, Any], source_id: str, destination_id: s
101
102
  return updates
102
103
 
103
104
 
105
+ def _malformed_transfer_candidate(markdown: str, source_id: str) -> bool:
106
+ """Identify an invalid document that could still require ownership transfer.
107
+
108
+ Existing memory can contain old or adversarial Markdown that is not a valid
109
+ Runtime artifact. Identity reconciliation must not fail because an unrelated
110
+ document is malformed, but it must fail closed when that document explicitly
111
+ names the current actor as a transferable owner.
112
+ """
113
+
114
+ if not source_id:
115
+ return False
116
+ actor = re.escape(source_id)
117
+ question = re.search(r"(?m)^type:\s*[\"']?question[\"']?\s*$", markdown)
118
+ recipient = re.search(rf"(?m)^to_actor_id:\s*[\"']?{actor}[\"']?\s*$", markdown)
119
+ thread = re.search(r"(?m)^type:\s*[\"']?thread[\"']?\s*$", markdown)
120
+ steward = re.search(rf"(?m)^steward_id:\s*[\"']?{actor}[\"']?\s*$", markdown)
121
+ stewards = re.search(rf"(?m)^steward_ids:\s*.*\b{actor}\b", markdown)
122
+ return bool((question and recipient) or (thread and (steward or stewards)))
123
+
124
+
104
125
  def _document(before: str, path: str, pair: Mapping[str, Any], transfer_id: str):
105
126
  fields, _ = split_frontmatter(before)
106
127
  updates = _changed_fields(fields, pair["source"]["actor_id"], pair["destination"]["actor_id"])
@@ -148,7 +169,18 @@ def _inventory(memory: Path, runtime, source: ActorContext, destination_id: str)
148
169
  if len(raw) > MAX_DOCUMENT_BYTES or size > MAX_SCAN_BYTES:
149
170
  raise IdentityTransferError("ownership inventory exceeds its byte limit; retain the current identity")
150
171
  before = raw.decode("utf-8")
151
- fields, _ = split_frontmatter(before)
172
+ try:
173
+ fields, _ = split_frontmatter(before)
174
+ except ValueError as error:
175
+ # A malformed legacy or test document outside the transfer
176
+ # contract must not block identity reconciliation. If it names
177
+ # the current owner, fail closed so ownership is never left
178
+ # half-transferred.
179
+ if _malformed_transfer_candidate(before, source.actor.actor_id):
180
+ raise IdentityTransferError(
181
+ f"ownership inventory cannot parse {relative}: {error}"
182
+ ) from error
183
+ continue
152
184
  if (fields.get("org") or fields.get("org_id")) not in {None, source.profile.org_id}:
153
185
  continue
154
186
  if _changed_fields(fields, source.actor.actor_id, destination_id):
@@ -11,6 +11,7 @@ from dataclasses import replace
11
11
  import hashlib
12
12
  import json
13
13
  import os
14
+ import re
14
15
  from pathlib import Path, PurePosixPath
15
16
  import tempfile
16
17
  from typing import Any, Mapping
@@ -101,6 +102,26 @@ def _changed_fields(fields: Mapping[str, Any], source_id: str, destination_id: s
101
102
  return updates
102
103
 
103
104
 
105
+ def _malformed_transfer_candidate(markdown: str, source_id: str) -> bool:
106
+ """Identify an invalid document that could still require ownership transfer.
107
+
108
+ Existing memory can contain old or adversarial Markdown that is not a valid
109
+ Runtime artifact. Identity reconciliation must not fail because an unrelated
110
+ document is malformed, but it must fail closed when that document explicitly
111
+ names the current actor as a transferable owner.
112
+ """
113
+
114
+ if not source_id:
115
+ return False
116
+ actor = re.escape(source_id)
117
+ question = re.search(r"(?m)^type:\s*[\"']?question[\"']?\s*$", markdown)
118
+ recipient = re.search(rf"(?m)^to_actor_id:\s*[\"']?{actor}[\"']?\s*$", markdown)
119
+ thread = re.search(r"(?m)^type:\s*[\"']?thread[\"']?\s*$", markdown)
120
+ steward = re.search(rf"(?m)^steward_id:\s*[\"']?{actor}[\"']?\s*$", markdown)
121
+ stewards = re.search(rf"(?m)^steward_ids:\s*.*\b{actor}\b", markdown)
122
+ return bool((question and recipient) or (thread and (steward or stewards)))
123
+
124
+
104
125
  def _document(before: str, path: str, pair: Mapping[str, Any], transfer_id: str):
105
126
  fields, _ = split_frontmatter(before)
106
127
  updates = _changed_fields(fields, pair["source"]["actor_id"], pair["destination"]["actor_id"])
@@ -148,7 +169,18 @@ def _inventory(memory: Path, runtime, source: ActorContext, destination_id: str)
148
169
  if len(raw) > MAX_DOCUMENT_BYTES or size > MAX_SCAN_BYTES:
149
170
  raise IdentityTransferError("ownership inventory exceeds its byte limit; retain the current identity")
150
171
  before = raw.decode("utf-8")
151
- fields, _ = split_frontmatter(before)
172
+ try:
173
+ fields, _ = split_frontmatter(before)
174
+ except ValueError as error:
175
+ # A malformed legacy or test document outside the transfer
176
+ # contract must not block identity reconciliation. If it names
177
+ # the current owner, fail closed so ownership is never left
178
+ # half-transferred.
179
+ if _malformed_transfer_candidate(before, source.actor.actor_id):
180
+ raise IdentityTransferError(
181
+ f"ownership inventory cannot parse {relative}: {error}"
182
+ ) from error
183
+ continue
152
184
  if (fields.get("org") or fields.get("org_id")) not in {None, source.profile.org_id}:
153
185
  continue
154
186
  if _changed_fields(fields, source.actor.actor_id, destination_id):
@@ -11,6 +11,7 @@ from dataclasses import replace
11
11
  import hashlib
12
12
  import json
13
13
  import os
14
+ import re
14
15
  from pathlib import Path, PurePosixPath
15
16
  import tempfile
16
17
  from typing import Any, Mapping
@@ -101,6 +102,26 @@ def _changed_fields(fields: Mapping[str, Any], source_id: str, destination_id: s
101
102
  return updates
102
103
 
103
104
 
105
+ def _malformed_transfer_candidate(markdown: str, source_id: str) -> bool:
106
+ """Identify an invalid document that could still require ownership transfer.
107
+
108
+ Existing memory can contain old or adversarial Markdown that is not a valid
109
+ Runtime artifact. Identity reconciliation must not fail because an unrelated
110
+ document is malformed, but it must fail closed when that document explicitly
111
+ names the current actor as a transferable owner.
112
+ """
113
+
114
+ if not source_id:
115
+ return False
116
+ actor = re.escape(source_id)
117
+ question = re.search(r"(?m)^type:\s*[\"']?question[\"']?\s*$", markdown)
118
+ recipient = re.search(rf"(?m)^to_actor_id:\s*[\"']?{actor}[\"']?\s*$", markdown)
119
+ thread = re.search(r"(?m)^type:\s*[\"']?thread[\"']?\s*$", markdown)
120
+ steward = re.search(rf"(?m)^steward_id:\s*[\"']?{actor}[\"']?\s*$", markdown)
121
+ stewards = re.search(rf"(?m)^steward_ids:\s*.*\b{actor}\b", markdown)
122
+ return bool((question and recipient) or (thread and (steward or stewards)))
123
+
124
+
104
125
  def _document(before: str, path: str, pair: Mapping[str, Any], transfer_id: str):
105
126
  fields, _ = split_frontmatter(before)
106
127
  updates = _changed_fields(fields, pair["source"]["actor_id"], pair["destination"]["actor_id"])
@@ -148,7 +169,18 @@ def _inventory(memory: Path, runtime, source: ActorContext, destination_id: str)
148
169
  if len(raw) > MAX_DOCUMENT_BYTES or size > MAX_SCAN_BYTES:
149
170
  raise IdentityTransferError("ownership inventory exceeds its byte limit; retain the current identity")
150
171
  before = raw.decode("utf-8")
151
- fields, _ = split_frontmatter(before)
172
+ try:
173
+ fields, _ = split_frontmatter(before)
174
+ except ValueError as error:
175
+ # A malformed legacy or test document outside the transfer
176
+ # contract must not block identity reconciliation. If it names
177
+ # the current owner, fail closed so ownership is never left
178
+ # half-transferred.
179
+ if _malformed_transfer_candidate(before, source.actor.actor_id):
180
+ raise IdentityTransferError(
181
+ f"ownership inventory cannot parse {relative}: {error}"
182
+ ) from error
183
+ continue
152
184
  if (fields.get("org") or fields.get("org_id")) not in {None, source.profile.org_id}:
153
185
  continue
154
186
  if _changed_fields(fields, source.actor.actor_id, destination_id):