create-egregore 0.21.2 → 0.21.3

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.2
2
+ // egregore-launcher-version: 0.21.3
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.2";
38
+ const VERSION = "0.21.3";
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.2",
3
+ "version": "0.21.3",
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": {
@@ -153,51 +153,30 @@ On the first session where telemetry events are emitted, if `telemetry_noticed`
153
153
 
154
154
  Then set `telemetry_noticed: true` in the state file. Never repeat this notice.
155
155
 
156
- ## Session Reports
157
-
158
- Separate from telemetry. Users can optionally share session reports during `/wrap` or via `/issue egregore:`. Reports are **opt-in per session** — the user is asked each time and must explicitly agree.
159
-
160
- ### How it works
161
-
162
- `bin/session-report.sh` handles report submission. It POSTs directly to Supabase via the anon key (no API server needed). The agent generates a structured report from the session context inline — no separate LLM call.
163
-
164
- ```bash
165
- # Submit a report (reads JSON from stdin)
166
- echo '{"topic":"...","summary":"..."}' | bash bin/session-report.sh submit
167
-
168
- # Check reporting status
169
- bash bin/session-report.sh status
170
- ```
171
-
172
- ### What is sent
173
-
174
- - AI-analyzed topic + summary (same as what's written to memory)
175
- - Gap analysis: `{type, detail}` where type is `missing_skill`, `missing_tool`, `repeated_failure`, `wrong_info`, or `confusing_ux`
176
- - User's own description (if provided)
177
- - System info: mode, platform, shell, framework version
178
- - Session duration and message count
179
-
180
- ### What is NEVER sent
181
-
182
- - Code, file contents, or file paths
183
- - Conversation transcript or user prompts
184
- - Environment variables or secrets
185
- - Org-specific data (sanitized before sending — org names, person names, tokens replaced)
186
-
187
- ### Opt-out
188
-
189
- - `EGREGORE_NO_REPORTS=1` in `.env`
190
- - `.egregore-state.json` → `"session_reports": false`
191
- - Simply answer "No thanks" when prompted during `/wrap`
192
- - `DO_NOT_TRACK=1` also disables reports
193
-
194
- ### Transport
195
-
196
- - Direct to Supabase via anon key (INSERT-only RLS policy)
197
- - If network fails: saved locally to `~/.egregore/reports/` as fallback
198
- - Telegram notification to maintainers fires server-side via Supabase DB webhook
199
- - Optional: GitHub issue creation on `egregore-labs/egregore` (user chooses each time)
200
-
201
- ### Configuration
202
-
203
- Reports require `report_url` and `report_key` in `egregore.json`. New OSS installations get these automatically via `create-egregore`. If missing, the report prompt is skipped entirely.
156
+ ## Framework support and legacy session feedback
157
+
158
+ For an Egregore bug, including the historical `/issue egregore:` wording, use
159
+ the [issue workflow](../skills/issue/SKILL.md). Connected instances prepare an
160
+ exact private support preview and submit only after confirmation. Local users
161
+ can explicitly choose a public upstream report. An internal issue stays within
162
+ its organization's memory or configured tracker; it does not automatically
163
+ reach Egregore support. `/wrap` does not collect or submit session feedback.
164
+
165
+ The older `bin/session-report.sh` is a feedback-storage compatibility helper,
166
+ not a support-reporting route. It inserts selected feedback into `session_reports`
167
+ when `report_url` and `report_key` are configured. A `submitted` receipt means
168
+ only that storage accepted the feedback: no GitHub issue was created and no
169
+ Telegram message was sent. The authenticated database callback acknowledges
170
+ storage without planning or dispatching a notification to a named teammate.
171
+ Transport failure saves feedback locally under `~/.egregore/reports/`; that
172
+ does not establish remote storage or delivery. Missing configuration skips
173
+ submission.
174
+
175
+ Never invoke legacy feedback automatically, generate it from a transcript, or
176
+ use it as a fallback when private support fails. Only explicitly selected and
177
+ reviewed content may be shared. The helper adds legacy user and organization
178
+ labels; it does not guarantee anonymization or scrub arbitrary input.
179
+
180
+ Legacy feedback can be disabled with `EGREGORE_NO_REPORTS=1` or
181
+ `DO_NOT_TRACK=1` in the process environment, or `"session_reports": false` in
182
+ `.egregore-state.json`. These switches do not grant consent to submit content.
@@ -67,6 +67,12 @@ its default recipient is **Egregore support, private**. Local/OSS users may
67
67
  explicitly choose `upstream` for a **public** report. Connected users can also
68
68
  choose public upstream explicitly. Never fall back from private to public.
69
69
 
70
+ Treat the historical `egregore:` prefix as framework-support intent and use
71
+ this reviewed reporting flow. Never route a framework bug through the
72
+ legacy session-feedback helper: it only stores feedback and does not
73
+ file a support issue or send a Telegram notification. Being Connected does not
74
+ turn an internal issue in the customer's memory into a report to Egregore.
75
+
70
76
  Use `'{title}'` for the concise title derived from the user's report and
71
77
  `'{report}'` for the report text the user supplied or reviewed.
72
78
  Write an embedded single quote as `'\''` in each substituted value.
@@ -119,6 +125,13 @@ review the private issue in its Project/Archive. Keep a failed/uncertain report'
119
125
  request ID; no backend failure authorizes public publication or a fresh blind
120
126
  submission.
121
127
 
128
+ Report the confirmed outcome precisely: a preview is not submitted; a local
129
+ issue is saved within this organization; only a matching private receipt with
130
+ `submission_state: filed` means filed with Egregore support. Other states need
131
+ recovery using the same request ID. A filed issue does not prove Telegram
132
+ delivery: `notification_state: unverified` must never be described as sent or
133
+ received. Only a separate delivery receipt can establish that.
134
+
122
135
  Public reports use the reporter's GitHub authentication and return a public
123
136
  issue URL. Destination comes only from HTTPS GitHub `upstream_url`; absent means
124
137
  `egregore-labs/egregore`, `none` means no upstream. Do not infer it from remotes.
@@ -21,6 +21,26 @@ from .policy import scope_allows_path
21
21
  KINDS = {"bug", "improvement", "question"}
22
22
  AREAS = {"runtime", "onboarding", "connectors", "site", "other"}
23
23
  SEVERITIES = {"critical", "high", "normal"}
24
+ SUPPORT_STATES = {"filed", "failed", "submitting", "uncertain"}
25
+
26
+
27
+ def support_receipt(result, identifier):
28
+ """Accept only a receipt for this request; filing never proves notification."""
29
+ if (not isinstance(result, dict) or result.get('request_id') != identifier
30
+ or not isinstance(result.get('submission_state'), str)
31
+ or result['submission_state'] not in SUPPORT_STATES):
32
+ raise ValueError(f"private support returned an invalid receipt; keep request {identifier} and use report-status")
33
+ state = result['submission_state']
34
+ notices = {
35
+ 'filed': 'Filed with Egregore support. Telegram delivery has not been verified.',
36
+ 'failed': 'Report preserved; support filing failed. Retry this same request.',
37
+ 'submitting': 'Report preserved; support filing is not confirmed. Keep this request ID and check report-status.',
38
+ 'uncertain': 'Report preserved; support filing needs reconciliation. Keep this request ID and check report-status.',
39
+ }
40
+ return {'request_id': identifier, 'submission_state': state,
41
+ 'retryable': state == 'failed',
42
+ 'reconciliation_needed': state in {'submitting', 'uncertain'},
43
+ 'notification_state': 'unverified', 'notice': notices[state]}
24
44
 
25
45
 
26
46
  def _scrub(value, *, public=False):
@@ -207,18 +227,29 @@ class FrameworkReporting:
207
227
  return {'request_id':identifier,'status':result['status'],'github_url':result['github_url']}
208
228
  credential, _ = self._verified_credential(state['destination'],state['authenticated_org'])
209
229
  state['status'] = 'submitting'; GitHubIssues._save(path,state)
210
- code, result = self.api(self.root,state['destination'],'/api/v1/support-reports',
211
- {'request_id':identifier, **state['payload']},credential=credential)
212
- if code >= 400:
230
+ try:
231
+ code, result = self.api(self.root,state['destination'],'/api/v1/support-reports',
232
+ {'request_id':identifier, **state['payload']},credential=credential)
233
+ except OSError:
234
+ state['status'] = 'reconciliation_needed'; GitHubIssues._save(path,state)
235
+ raise OSError(f"private support response unavailable; keep request {identifier} and use report-status or confirm this same preview") from None
236
+ if not 200 <= code < 300:
213
237
  state['status'] = 'reconciliation_needed'; GitHubIssues._save(path,state)
214
238
  raise ValueError(f"private support request {identifier} needs recovery (HTTP {code}); use report-status or confirm this same preview")
215
- state['status'] = result.get('submission_state','unknown'); GitHubIssues._save(path,state)
216
- return result
239
+ try:
240
+ receipt = support_receipt(result, identifier)
241
+ except ValueError:
242
+ state['status'] = 'reconciliation_needed'; GitHubIssues._save(path,state)
243
+ raise
244
+ state['status'] = receipt['submission_state']; GitHubIssues._save(path,state)
245
+ if state['status'] != 'filed':
246
+ raise ValueError(f"private support request {identifier} needs recovery ({state['status']}); use report-status or confirm this same preview")
247
+ return receipt
217
248
 
218
249
  def status(self, identifier):
219
250
  path, state = self._load('report',identifier,write=False)
220
251
  credential, _ = self._verified_credential(state['destination'],state['authenticated_org'])
221
252
  code, result = self.api(self.root,state['destination'],f'/api/v1/support-reports/{identifier}',credential=credential)
222
- if code >= 400:
253
+ if not 200 <= code < 300:
223
254
  raise ValueError(f"private support status unavailable (HTTP {code}); keep request {identifier}")
224
- return result
255
+ return support_receipt(result, identifier)
@@ -21,6 +21,26 @@ from .policy import scope_allows_path
21
21
  KINDS = {"bug", "improvement", "question"}
22
22
  AREAS = {"runtime", "onboarding", "connectors", "site", "other"}
23
23
  SEVERITIES = {"critical", "high", "normal"}
24
+ SUPPORT_STATES = {"filed", "failed", "submitting", "uncertain"}
25
+
26
+
27
+ def support_receipt(result, identifier):
28
+ """Accept only a receipt for this request; filing never proves notification."""
29
+ if (not isinstance(result, dict) or result.get('request_id') != identifier
30
+ or not isinstance(result.get('submission_state'), str)
31
+ or result['submission_state'] not in SUPPORT_STATES):
32
+ raise ValueError(f"private support returned an invalid receipt; keep request {identifier} and use report-status")
33
+ state = result['submission_state']
34
+ notices = {
35
+ 'filed': 'Filed with Egregore support. Telegram delivery has not been verified.',
36
+ 'failed': 'Report preserved; support filing failed. Retry this same request.',
37
+ 'submitting': 'Report preserved; support filing is not confirmed. Keep this request ID and check report-status.',
38
+ 'uncertain': 'Report preserved; support filing needs reconciliation. Keep this request ID and check report-status.',
39
+ }
40
+ return {'request_id': identifier, 'submission_state': state,
41
+ 'retryable': state == 'failed',
42
+ 'reconciliation_needed': state in {'submitting', 'uncertain'},
43
+ 'notification_state': 'unverified', 'notice': notices[state]}
24
44
 
25
45
 
26
46
  def _scrub(value, *, public=False):
@@ -207,18 +227,29 @@ class FrameworkReporting:
207
227
  return {'request_id':identifier,'status':result['status'],'github_url':result['github_url']}
208
228
  credential, _ = self._verified_credential(state['destination'],state['authenticated_org'])
209
229
  state['status'] = 'submitting'; GitHubIssues._save(path,state)
210
- code, result = self.api(self.root,state['destination'],'/api/v1/support-reports',
211
- {'request_id':identifier, **state['payload']},credential=credential)
212
- if code >= 400:
230
+ try:
231
+ code, result = self.api(self.root,state['destination'],'/api/v1/support-reports',
232
+ {'request_id':identifier, **state['payload']},credential=credential)
233
+ except OSError:
234
+ state['status'] = 'reconciliation_needed'; GitHubIssues._save(path,state)
235
+ raise OSError(f"private support response unavailable; keep request {identifier} and use report-status or confirm this same preview") from None
236
+ if not 200 <= code < 300:
213
237
  state['status'] = 'reconciliation_needed'; GitHubIssues._save(path,state)
214
238
  raise ValueError(f"private support request {identifier} needs recovery (HTTP {code}); use report-status or confirm this same preview")
215
- state['status'] = result.get('submission_state','unknown'); GitHubIssues._save(path,state)
216
- return result
239
+ try:
240
+ receipt = support_receipt(result, identifier)
241
+ except ValueError:
242
+ state['status'] = 'reconciliation_needed'; GitHubIssues._save(path,state)
243
+ raise
244
+ state['status'] = receipt['submission_state']; GitHubIssues._save(path,state)
245
+ if state['status'] != 'filed':
246
+ raise ValueError(f"private support request {identifier} needs recovery ({state['status']}); use report-status or confirm this same preview")
247
+ return receipt
217
248
 
218
249
  def status(self, identifier):
219
250
  path, state = self._load('report',identifier,write=False)
220
251
  credential, _ = self._verified_credential(state['destination'],state['authenticated_org'])
221
252
  code, result = self.api(self.root,state['destination'],f'/api/v1/support-reports/{identifier}',credential=credential)
222
- if code >= 400:
253
+ if not 200 <= code < 300:
223
254
  raise ValueError(f"private support status unavailable (HTTP {code}); keep request {identifier}")
224
- return result
255
+ return support_receipt(result, identifier)
@@ -21,6 +21,26 @@ from .policy import scope_allows_path
21
21
  KINDS = {"bug", "improvement", "question"}
22
22
  AREAS = {"runtime", "onboarding", "connectors", "site", "other"}
23
23
  SEVERITIES = {"critical", "high", "normal"}
24
+ SUPPORT_STATES = {"filed", "failed", "submitting", "uncertain"}
25
+
26
+
27
+ def support_receipt(result, identifier):
28
+ """Accept only a receipt for this request; filing never proves notification."""
29
+ if (not isinstance(result, dict) or result.get('request_id') != identifier
30
+ or not isinstance(result.get('submission_state'), str)
31
+ or result['submission_state'] not in SUPPORT_STATES):
32
+ raise ValueError(f"private support returned an invalid receipt; keep request {identifier} and use report-status")
33
+ state = result['submission_state']
34
+ notices = {
35
+ 'filed': 'Filed with Egregore support. Telegram delivery has not been verified.',
36
+ 'failed': 'Report preserved; support filing failed. Retry this same request.',
37
+ 'submitting': 'Report preserved; support filing is not confirmed. Keep this request ID and check report-status.',
38
+ 'uncertain': 'Report preserved; support filing needs reconciliation. Keep this request ID and check report-status.',
39
+ }
40
+ return {'request_id': identifier, 'submission_state': state,
41
+ 'retryable': state == 'failed',
42
+ 'reconciliation_needed': state in {'submitting', 'uncertain'},
43
+ 'notification_state': 'unverified', 'notice': notices[state]}
24
44
 
25
45
 
26
46
  def _scrub(value, *, public=False):
@@ -207,18 +227,29 @@ class FrameworkReporting:
207
227
  return {'request_id':identifier,'status':result['status'],'github_url':result['github_url']}
208
228
  credential, _ = self._verified_credential(state['destination'],state['authenticated_org'])
209
229
  state['status'] = 'submitting'; GitHubIssues._save(path,state)
210
- code, result = self.api(self.root,state['destination'],'/api/v1/support-reports',
211
- {'request_id':identifier, **state['payload']},credential=credential)
212
- if code >= 400:
230
+ try:
231
+ code, result = self.api(self.root,state['destination'],'/api/v1/support-reports',
232
+ {'request_id':identifier, **state['payload']},credential=credential)
233
+ except OSError:
234
+ state['status'] = 'reconciliation_needed'; GitHubIssues._save(path,state)
235
+ raise OSError(f"private support response unavailable; keep request {identifier} and use report-status or confirm this same preview") from None
236
+ if not 200 <= code < 300:
213
237
  state['status'] = 'reconciliation_needed'; GitHubIssues._save(path,state)
214
238
  raise ValueError(f"private support request {identifier} needs recovery (HTTP {code}); use report-status or confirm this same preview")
215
- state['status'] = result.get('submission_state','unknown'); GitHubIssues._save(path,state)
216
- return result
239
+ try:
240
+ receipt = support_receipt(result, identifier)
241
+ except ValueError:
242
+ state['status'] = 'reconciliation_needed'; GitHubIssues._save(path,state)
243
+ raise
244
+ state['status'] = receipt['submission_state']; GitHubIssues._save(path,state)
245
+ if state['status'] != 'filed':
246
+ raise ValueError(f"private support request {identifier} needs recovery ({state['status']}); use report-status or confirm this same preview")
247
+ return receipt
217
248
 
218
249
  def status(self, identifier):
219
250
  path, state = self._load('report',identifier,write=False)
220
251
  credential, _ = self._verified_credential(state['destination'],state['authenticated_org'])
221
252
  code, result = self.api(self.root,state['destination'],f'/api/v1/support-reports/{identifier}',credential=credential)
222
- if code >= 400:
253
+ if not 200 <= code < 300:
223
254
  raise ValueError(f"private support status unavailable (HTTP {code}); keep request {identifier}")
224
- return result
255
+ return support_receipt(result, identifier)
@@ -21,6 +21,26 @@ from .policy import scope_allows_path
21
21
  KINDS = {"bug", "improvement", "question"}
22
22
  AREAS = {"runtime", "onboarding", "connectors", "site", "other"}
23
23
  SEVERITIES = {"critical", "high", "normal"}
24
+ SUPPORT_STATES = {"filed", "failed", "submitting", "uncertain"}
25
+
26
+
27
+ def support_receipt(result, identifier):
28
+ """Accept only a receipt for this request; filing never proves notification."""
29
+ if (not isinstance(result, dict) or result.get('request_id') != identifier
30
+ or not isinstance(result.get('submission_state'), str)
31
+ or result['submission_state'] not in SUPPORT_STATES):
32
+ raise ValueError(f"private support returned an invalid receipt; keep request {identifier} and use report-status")
33
+ state = result['submission_state']
34
+ notices = {
35
+ 'filed': 'Filed with Egregore support. Telegram delivery has not been verified.',
36
+ 'failed': 'Report preserved; support filing failed. Retry this same request.',
37
+ 'submitting': 'Report preserved; support filing is not confirmed. Keep this request ID and check report-status.',
38
+ 'uncertain': 'Report preserved; support filing needs reconciliation. Keep this request ID and check report-status.',
39
+ }
40
+ return {'request_id': identifier, 'submission_state': state,
41
+ 'retryable': state == 'failed',
42
+ 'reconciliation_needed': state in {'submitting', 'uncertain'},
43
+ 'notification_state': 'unverified', 'notice': notices[state]}
24
44
 
25
45
 
26
46
  def _scrub(value, *, public=False):
@@ -207,18 +227,29 @@ class FrameworkReporting:
207
227
  return {'request_id':identifier,'status':result['status'],'github_url':result['github_url']}
208
228
  credential, _ = self._verified_credential(state['destination'],state['authenticated_org'])
209
229
  state['status'] = 'submitting'; GitHubIssues._save(path,state)
210
- code, result = self.api(self.root,state['destination'],'/api/v1/support-reports',
211
- {'request_id':identifier, **state['payload']},credential=credential)
212
- if code >= 400:
230
+ try:
231
+ code, result = self.api(self.root,state['destination'],'/api/v1/support-reports',
232
+ {'request_id':identifier, **state['payload']},credential=credential)
233
+ except OSError:
234
+ state['status'] = 'reconciliation_needed'; GitHubIssues._save(path,state)
235
+ raise OSError(f"private support response unavailable; keep request {identifier} and use report-status or confirm this same preview") from None
236
+ if not 200 <= code < 300:
213
237
  state['status'] = 'reconciliation_needed'; GitHubIssues._save(path,state)
214
238
  raise ValueError(f"private support request {identifier} needs recovery (HTTP {code}); use report-status or confirm this same preview")
215
- state['status'] = result.get('submission_state','unknown'); GitHubIssues._save(path,state)
216
- return result
239
+ try:
240
+ receipt = support_receipt(result, identifier)
241
+ except ValueError:
242
+ state['status'] = 'reconciliation_needed'; GitHubIssues._save(path,state)
243
+ raise
244
+ state['status'] = receipt['submission_state']; GitHubIssues._save(path,state)
245
+ if state['status'] != 'filed':
246
+ raise ValueError(f"private support request {identifier} needs recovery ({state['status']}); use report-status or confirm this same preview")
247
+ return receipt
217
248
 
218
249
  def status(self, identifier):
219
250
  path, state = self._load('report',identifier,write=False)
220
251
  credential, _ = self._verified_credential(state['destination'],state['authenticated_org'])
221
252
  code, result = self.api(self.root,state['destination'],f'/api/v1/support-reports/{identifier}',credential=credential)
222
- if code >= 400:
253
+ if not 200 <= code < 300:
223
254
  raise ValueError(f"private support status unavailable (HTTP {code}); keep request {identifier}")
224
- return result
255
+ return support_receipt(result, identifier)