create-egregore 0.19.30 → 0.19.32
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/assets/egregore-launcher.js +2 -2
- package/package.json +1 -1
- package/runtime/codex/.codex/skills/create-skill/SKILL.md +29 -0
- package/runtime/codex/.codex/skills/slack-connect/SKILL.md +29 -0
- package/runtime/codex/.codex/spec-manifest.json +3 -3
- package/runtime/codex/AGENTS.md +5 -3
- package/runtime/codex/bin/ingest-surface.html +66 -0
- package/runtime/codex/bin/ingest.py +85 -19
- package/runtime/codex/bin/ingest.sh +49 -1
- package/runtime/codex/bin/ingest_graph.py +81 -1
- package/runtime/codex/bin/ingest_surface.py +81 -1
- package/runtime/codex/bin/notify.sh +113 -23
- package/runtime/codex/manifest.json +5 -1
- package/runtime/pi/.codex/skills/create-skill/SKILL.md +29 -0
- package/runtime/pi/.codex/skills/slack-connect/SKILL.md +29 -0
- package/runtime/pi/.codex/spec-manifest.json +3 -3
- package/runtime/pi/.pi/APPEND_SYSTEM.md +1 -1
- package/runtime/pi/.pi/spec-manifest.json +2 -2
- package/runtime/pi/AGENTS.md +5 -3
- package/runtime/pi/bin/ingest-surface.html +66 -0
- package/runtime/pi/bin/ingest.py +85 -19
- package/runtime/pi/bin/ingest.sh +49 -1
- package/runtime/pi/bin/ingest_graph.py +81 -1
- package/runtime/pi/bin/ingest_surface.py +81 -1
- package/runtime/pi/bin/notify.sh +113 -23
- package/runtime/pi/manifest.json +5 -1
|
@@ -232,6 +232,39 @@ class SelectionState:
|
|
|
232
232
|
self.done.set()
|
|
233
233
|
return self.result
|
|
234
234
|
|
|
235
|
+
def choose_connector(self, payload: dict) -> dict:
|
|
236
|
+
# A connector choice ends the local-selection flow. When the CLI
|
|
237
|
+
# pre-started an OAuth listener, the browser response carries its
|
|
238
|
+
# consent URL so THIS tab continues straight into Google's sign-in;
|
|
239
|
+
# otherwise the agent guides authorization from the terminal. No bytes
|
|
240
|
+
# are staged and no source contract is collected here.
|
|
241
|
+
with self.lock:
|
|
242
|
+
if self.done.is_set():
|
|
243
|
+
raise ValueError("selection already finished")
|
|
244
|
+
provider = str(payload.get("provider", "")).strip().lower()
|
|
245
|
+
if provider not in available_connectors():
|
|
246
|
+
raise ValueError(f"unknown connector: {provider or '(empty)'}")
|
|
247
|
+
redirect = connector_auth_urls().get(provider, "")
|
|
248
|
+
account = connector_accounts().get(provider, "")
|
|
249
|
+
self.result = {
|
|
250
|
+
"connector": provider,
|
|
251
|
+
"receipt_url": self.receipt_url,
|
|
252
|
+
}
|
|
253
|
+
if redirect:
|
|
254
|
+
self.result["auth"] = "in-browser"
|
|
255
|
+
elif account:
|
|
256
|
+
self.result["account"] = account
|
|
257
|
+
self.receipt = {
|
|
258
|
+
**self.result,
|
|
259
|
+
"status": "processing",
|
|
260
|
+
"message": "The agent is walking you through connecting this source in the terminal.",
|
|
261
|
+
}
|
|
262
|
+
self.done.set()
|
|
263
|
+
response = dict(self.result)
|
|
264
|
+
if redirect:
|
|
265
|
+
response["redirect"] = redirect
|
|
266
|
+
return response
|
|
267
|
+
|
|
235
268
|
def finish_receipt(self, payload: dict) -> dict:
|
|
236
269
|
with self.lock:
|
|
237
270
|
if not self.done.is_set() or self.cancelled:
|
|
@@ -252,10 +285,53 @@ class SelectionState:
|
|
|
252
285
|
shutil.rmtree(self.session_root, ignore_errors=True)
|
|
253
286
|
|
|
254
287
|
|
|
288
|
+
def available_connectors() -> list[str]:
|
|
289
|
+
# A connector is offered when its runtime shim ships with this instance.
|
|
290
|
+
# Authorization state is NOT checked here — the button's job is to route
|
|
291
|
+
# the user into the guided setup, which handles both fresh and connected.
|
|
292
|
+
here = Path(__file__).parent
|
|
293
|
+
providers = []
|
|
294
|
+
if (here / "connector-google.sh").exists():
|
|
295
|
+
providers.append("google")
|
|
296
|
+
if (here / "connector-notion.sh").exists():
|
|
297
|
+
providers.append("notion")
|
|
298
|
+
return providers
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def _env_json_map(name: str) -> dict[str, str]:
|
|
302
|
+
try:
|
|
303
|
+
value = json.loads(os.environ.get(name, "") or "{}")
|
|
304
|
+
except json.JSONDecodeError:
|
|
305
|
+
return {}
|
|
306
|
+
if not isinstance(value, dict):
|
|
307
|
+
return {}
|
|
308
|
+
return {str(k): str(v) for k, v in value.items() if isinstance(v, (str, int))}
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def connector_auth_urls() -> dict[str, str]:
|
|
312
|
+
# Consent URLs pre-fetched by the CLI (listener already running) so a
|
|
313
|
+
# connector click can redirect this same tab straight into sign-in.
|
|
314
|
+
# Only loopback/HTTPS URLs are accepted.
|
|
315
|
+
urls = {}
|
|
316
|
+
for provider, url in _env_json_map("EGREGORE_CONNECTOR_AUTH_URLS").items():
|
|
317
|
+
if url.startswith("https://") or url.startswith("http://127.0.0.1") or url.startswith("http://localhost"):
|
|
318
|
+
urls[provider] = url
|
|
319
|
+
return urls
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def connector_accounts() -> dict[str, str]:
|
|
323
|
+
# Already-connected account per provider, for honest completion copy.
|
|
324
|
+
return _env_json_map("EGREGORE_CONNECTOR_ACCOUNTS")
|
|
325
|
+
|
|
326
|
+
|
|
255
327
|
def handler_class(state: SelectionState, token: str, expected_origin: str, preference_file: Path | None = None):
|
|
256
328
|
html_template = HTML_PATH.read_text(encoding="utf-8")
|
|
257
329
|
design_css = DESIGN_CSS_PATH.read_text(encoding="utf-8")
|
|
258
|
-
capabilities = {
|
|
330
|
+
capabilities = {
|
|
331
|
+
"pdftotext": bool(shutil.which("pdftotext")),
|
|
332
|
+
"connectors": available_connectors(),
|
|
333
|
+
"connector_accounts": connector_accounts(),
|
|
334
|
+
}
|
|
259
335
|
preferences = load_preferences(preference_file or preferences_path())
|
|
260
336
|
page = (
|
|
261
337
|
html_template.replace("__EGREGORE_DESIGN_CSS__", design_css)
|
|
@@ -349,6 +425,10 @@ def handler_class(state: SelectionState, token: str, expected_origin: str, prefe
|
|
|
349
425
|
result = state.confirm(self.read_json())
|
|
350
426
|
self.send_json(200, result)
|
|
351
427
|
return
|
|
428
|
+
if path == "/api/connector":
|
|
429
|
+
result = state.choose_connector(self.read_json())
|
|
430
|
+
self.send_json(200, result)
|
|
431
|
+
return
|
|
352
432
|
if path == "/api/cancel":
|
|
353
433
|
state.cancel()
|
|
354
434
|
self.send_json(200, {"cancelled": True})
|
|
@@ -35,6 +35,7 @@ chmod 700 "$STATE_DIR" 2>/dev/null || true
|
|
|
35
35
|
if [ -f "$SCRIPT_DIR/.env" ]; then
|
|
36
36
|
EGREGORE_API_URL="${EGREGORE_API_URL:-$(grep '^EGREGORE_API_URL=' "$SCRIPT_DIR/.env" 2>/dev/null | cut -d'=' -f2- || true)}"
|
|
37
37
|
EGREGORE_API_KEY="${EGREGORE_API_KEY:-$(grep '^EGREGORE_API_KEY=' "$SCRIPT_DIR/.env" 2>/dev/null | cut -d'=' -f2- || true)}"
|
|
38
|
+
SLACK_BOT_TOKEN="${SLACK_BOT_TOKEN:-$(grep '^SLACK_BOT_TOKEN=' "$SCRIPT_DIR/.env" 2>/dev/null | cut -d'=' -f2- || true)}"
|
|
38
39
|
fi
|
|
39
40
|
API_URL="${EGREGORE_API_URL:-$(jq -r '.api_url // empty' "$CONFIG" 2>/dev/null)}"
|
|
40
41
|
API_KEY="${EGREGORE_API_KEY:-}"
|
|
@@ -178,6 +179,8 @@ write_plan() {
|
|
|
178
179
|
}' "$path"
|
|
179
180
|
}
|
|
180
181
|
|
|
182
|
+
# Channel-generic despite the name: reads any field from .egregore-state.json
|
|
183
|
+
# with egregore.json as the legacy fallback (telegram_*, slack_*, …).
|
|
181
184
|
read_local_telegram_config() {
|
|
182
185
|
local field="$1"
|
|
183
186
|
local state_file="$SCRIPT_DIR/.egregore-state.json"
|
|
@@ -220,24 +223,38 @@ plan_local() {
|
|
|
220
223
|
die "direct messages are unavailable in local mode; no group fallback was sent"
|
|
221
224
|
fi
|
|
222
225
|
|
|
223
|
-
local chat_id group_link
|
|
226
|
+
local chat_id group_link slack_channel deliveries
|
|
224
227
|
chat_id="$(read_local_telegram_config telegram_chat_id)"
|
|
225
228
|
group_link="$(read_local_telegram_config telegram_group_link)"
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
+
slack_channel="$(read_local_telegram_config slack_channel_id)"
|
|
230
|
+
|
|
231
|
+
deliveries="[]"
|
|
232
|
+
if [ -n "$chat_id" ] || [ -n "$group_link" ]; then
|
|
233
|
+
deliveries="$(printf '%s' "$deliveries" | jq -c \
|
|
234
|
+
--arg destination "${group_link:-$chat_id}" \
|
|
235
|
+
'. + [{channel:"telegram", destination:$destination, kind:"group"}]')"
|
|
236
|
+
fi
|
|
237
|
+
# Slack is local-direct (the org owns the app and token): plan it only when
|
|
238
|
+
# both halves of the config exist, so a half-configured Slack fails soft.
|
|
239
|
+
if [ -n "$slack_channel" ] && [ -n "${SLACK_BOT_TOKEN:-}" ]; then
|
|
240
|
+
deliveries="$(printf '%s' "$deliveries" | jq -c \
|
|
241
|
+
--arg destination "$slack_channel" \
|
|
242
|
+
'. + [{channel:"slack", destination:$destination, kind:"group"}]')"
|
|
243
|
+
fi
|
|
244
|
+
[ "$deliveries" != "[]" ] ||
|
|
245
|
+
die "no group notification channel is configured"
|
|
229
246
|
|
|
230
247
|
local response
|
|
231
248
|
response="$(jq -nc \
|
|
232
249
|
--arg slug "$ORG_SLUG" \
|
|
233
250
|
--arg org "$ORG_NAME" \
|
|
234
|
-
--
|
|
251
|
+
--argjson deliveries "$deliveries" \
|
|
235
252
|
'{
|
|
236
253
|
status:"planned",
|
|
237
254
|
org_slug:$slug,
|
|
238
255
|
org_name:$org,
|
|
239
|
-
channels:
|
|
240
|
-
deliveries
|
|
256
|
+
channels:($deliveries | map(.channel)),
|
|
257
|
+
deliveries:$deliveries,
|
|
241
258
|
no_fallback:true
|
|
242
259
|
}')"
|
|
243
260
|
write_plan "$response" "$kind" "$recipient" "$message"
|
|
@@ -342,11 +359,12 @@ dispatch_connected() {
|
|
|
342
359
|
printf '%s\n' "$response"
|
|
343
360
|
}
|
|
344
361
|
|
|
345
|
-
|
|
346
|
-
local
|
|
347
|
-
local message
|
|
348
|
-
|
|
349
|
-
|
|
362
|
+
dispatch_local_telegram() {
|
|
363
|
+
local destination="$1"
|
|
364
|
+
local message="$2"
|
|
365
|
+
local plan_slug="$3"
|
|
366
|
+
local plan_org="$4"
|
|
367
|
+
local chat_id group_link
|
|
350
368
|
case "$destination" in
|
|
351
369
|
https://t.me/*)
|
|
352
370
|
chat_id=""
|
|
@@ -357,9 +375,7 @@ dispatch_local() {
|
|
|
357
375
|
group_link=""
|
|
358
376
|
;;
|
|
359
377
|
esac
|
|
360
|
-
|
|
361
|
-
plan_org="$(jq -r '.org_name' "$path")"
|
|
362
|
-
response="$(curl -sS -X POST "${RELAY_URL}/api/notify/relay" \
|
|
378
|
+
curl -sS -X POST "${RELAY_URL}/api/notify/relay" \
|
|
363
379
|
-H "Content-Type: application/json" \
|
|
364
380
|
-d "$(jq -nc \
|
|
365
381
|
--arg chat_id "$chat_id" \
|
|
@@ -368,12 +384,84 @@ dispatch_local() {
|
|
|
368
384
|
--arg slug "$plan_slug" \
|
|
369
385
|
--arg org_name "$plan_org" \
|
|
370
386
|
'{chat_id:$chat_id,group_link:$group_link,message:$message,slug:$slug,org_name:$org_name}')" \
|
|
371
|
-
--max-time 10
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
387
|
+
--max-time 10
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
# Local-direct: the org owns the Slack app and token, so the message goes
|
|
391
|
+
# straight from this machine to Slack — never through the Egregore relay.
|
|
392
|
+
dispatch_local_slack() {
|
|
393
|
+
local destination="$1"
|
|
394
|
+
local message="$2"
|
|
395
|
+
if [ -z "${SLACK_BOT_TOKEN:-}" ]; then
|
|
396
|
+
jq -nc '{status:"error",detail:"SLACK_BOT_TOKEN is missing from .env"}'
|
|
397
|
+
return 0
|
|
398
|
+
fi
|
|
399
|
+
local response
|
|
400
|
+
response="$(curl -sS -X POST "https://slack.com/api/chat.postMessage" \
|
|
401
|
+
-H "Authorization: Bearer $SLACK_BOT_TOKEN" \
|
|
402
|
+
-H "Content-Type: application/json; charset=utf-8" \
|
|
403
|
+
-d "$(jq -nc \
|
|
404
|
+
--arg channel "$destination" \
|
|
405
|
+
--arg text "$message" \
|
|
406
|
+
'{channel:$channel,text:$text,unfurl_links:false}')" \
|
|
407
|
+
--max-time 10)" || {
|
|
408
|
+
jq -nc '{status:"error",detail:"Slack request failed"}'
|
|
409
|
+
return 0
|
|
375
410
|
}
|
|
376
|
-
printf '%s
|
|
411
|
+
if printf '%s' "$response" | jq -e '.ok == true' >/dev/null 2>&1; then
|
|
412
|
+
jq -nc '{status:"sent"}'
|
|
413
|
+
else
|
|
414
|
+
jq -nc \
|
|
415
|
+
--arg detail "$(printf '%s' "$response" | jq -r '.error // "unknown"' 2>/dev/null || echo unknown)" \
|
|
416
|
+
'{status:"error",detail:$detail}'
|
|
417
|
+
fi
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
dispatch_local() {
|
|
421
|
+
local path="$1"
|
|
422
|
+
local message plan_slug plan_org count i channel destination result
|
|
423
|
+
message="$(jq -r '.message' "$path")"
|
|
424
|
+
plan_slug="$(jq -r '.org_slug' "$path")"
|
|
425
|
+
plan_org="$(jq -r '.org_name' "$path")"
|
|
426
|
+
count="$(jq -r '.deliveries | length' "$path")"
|
|
427
|
+
|
|
428
|
+
# One platform failing must not mask another succeeding (mirror of the
|
|
429
|
+
# server-side send_group semantics).
|
|
430
|
+
local results="{}"
|
|
431
|
+
i=0
|
|
432
|
+
while [ "$i" -lt "$count" ]; do
|
|
433
|
+
channel="$(jq -r ".deliveries[$i].channel" "$path")"
|
|
434
|
+
destination="$(jq -r ".deliveries[$i].destination" "$path")"
|
|
435
|
+
case "$channel" in
|
|
436
|
+
telegram)
|
|
437
|
+
result="$(dispatch_local_telegram "$destination" "$message" "$plan_slug" "$plan_org")" ||
|
|
438
|
+
result='{"status":"error","detail":"relay request failed"}'
|
|
439
|
+
printf '%s' "$result" | jq -e . >/dev/null 2>&1 ||
|
|
440
|
+
result='{"status":"error","detail":"relay returned an unreadable response"}'
|
|
441
|
+
;;
|
|
442
|
+
slack)
|
|
443
|
+
result="$(dispatch_local_slack "$destination" "$message")"
|
|
444
|
+
;;
|
|
445
|
+
*)
|
|
446
|
+
result='{"status":"error","detail":"Unsupported channel"}'
|
|
447
|
+
;;
|
|
448
|
+
esac
|
|
449
|
+
results="$(printf '%s' "$results" | jq -c --arg ch "$channel" --argjson r "$result" '. + {($ch): $r}')"
|
|
450
|
+
i=$((i + 1))
|
|
451
|
+
done
|
|
452
|
+
|
|
453
|
+
local sent
|
|
454
|
+
sent="$(printf '%s' "$results" | jq -c '[to_entries[] | select(.value.status == "sent") | .key]')"
|
|
455
|
+
if [ "$sent" = "[]" ]; then
|
|
456
|
+
printf '%s\n' "$results"
|
|
457
|
+
return 1
|
|
458
|
+
fi
|
|
459
|
+
printf '%s' "$results" | jq -c --argjson sent "$sent" \
|
|
460
|
+
'{status:"sent", platforms:$sent}
|
|
461
|
+
+ ([to_entries[] | select(.value.status != "sent")] as $f
|
|
462
|
+
| if ($f | length) > 0
|
|
463
|
+
then {failed: ($f | map({(.key): (.value.detail // "unknown")}) | add)}
|
|
464
|
+
else {} end)'
|
|
377
465
|
}
|
|
378
466
|
|
|
379
467
|
dispatch_plan() {
|
|
@@ -469,12 +557,14 @@ test_connection() {
|
|
|
469
557
|
-H "Authorization: Bearer $API_KEY" \
|
|
470
558
|
--max-time 10
|
|
471
559
|
elif [ "$MODE" = "local" ]; then
|
|
472
|
-
local group_link
|
|
560
|
+
local group_link slack_channel
|
|
473
561
|
group_link="$(read_local_telegram_config telegram_group_link)"
|
|
474
|
-
|
|
562
|
+
slack_channel="$(read_local_telegram_config slack_channel_id)"
|
|
563
|
+
if [ -n "$group_link" ] ||
|
|
564
|
+
{ [ -n "$slack_channel" ] && [ -n "${SLACK_BOT_TOKEN:-}" ]; }; then
|
|
475
565
|
echo '{"status":"configured","mode":"local"}'
|
|
476
566
|
else
|
|
477
|
-
echo '{"status":"offline","reason":"
|
|
567
|
+
echo '{"status":"offline","reason":"no_notification_channel"}'
|
|
478
568
|
fi
|
|
479
569
|
else
|
|
480
570
|
echo '{"status":"offline","reason":"no_api_key"}'
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"source": "egregore-runtime",
|
|
3
3
|
"policy": "available OSS components selected by capability-distribution.json; Connect is an authenticated additive overlay",
|
|
4
|
-
"skillCount":
|
|
4
|
+
"skillCount": 43,
|
|
5
5
|
"skills": [
|
|
6
6
|
"activity",
|
|
7
7
|
"announce",
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"checkup",
|
|
11
11
|
"commit",
|
|
12
12
|
"contribute",
|
|
13
|
+
"create-skill",
|
|
13
14
|
"dark-mode",
|
|
14
15
|
"dashboard",
|
|
15
16
|
"deep-reflect",
|
|
@@ -32,6 +33,7 @@
|
|
|
32
33
|
"reflect",
|
|
33
34
|
"review-pr",
|
|
34
35
|
"save",
|
|
36
|
+
"slack-connect",
|
|
35
37
|
"summon",
|
|
36
38
|
"sync-repos",
|
|
37
39
|
"telegram-connect",
|
|
@@ -60,6 +62,7 @@
|
|
|
60
62
|
".codex/skills/checkup/SKILL.md",
|
|
61
63
|
".codex/skills/commit/SKILL.md",
|
|
62
64
|
".codex/skills/contribute/SKILL.md",
|
|
65
|
+
".codex/skills/create-skill/SKILL.md",
|
|
63
66
|
".codex/skills/dark-mode/SKILL.md",
|
|
64
67
|
".codex/skills/dashboard/SKILL.md",
|
|
65
68
|
".codex/skills/deep-reflect/SKILL.md",
|
|
@@ -83,6 +86,7 @@
|
|
|
83
86
|
".codex/skills/reflect/SKILL.md",
|
|
84
87
|
".codex/skills/review-pr/SKILL.md",
|
|
85
88
|
".codex/skills/save/SKILL.md",
|
|
89
|
+
".codex/skills/slack-connect/SKILL.md",
|
|
86
90
|
".codex/skills/summon/SKILL.md",
|
|
87
91
|
".codex/skills/sync-repos/SKILL.md",
|
|
88
92
|
".codex/skills/telegram-connect/SKILL.md",
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: create-skill
|
|
3
|
+
description: 'Create a skill your org owns — scaffolded in this Egregore, protected from framework updates, shared with teammates via /save.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- generated-by: bin/codex-sync-skills.sh -->
|
|
7
|
+
|
|
8
|
+
# Egregore create-skill Adapter
|
|
9
|
+
|
|
10
|
+
This is fallback coverage for a long-tail Egregore workflow that has not been
|
|
11
|
+
ported to a hand-written Codex-native skill yet.
|
|
12
|
+
|
|
13
|
+
Use the project shell and filesystem directly. Do not invoke Claude Code
|
|
14
|
+
commands. Translate interactive choices to structured Codex question tooling
|
|
15
|
+
when it is available; otherwise render compact numbered choices with an
|
|
16
|
+
`Other:` option and wait for the user.
|
|
17
|
+
|
|
18
|
+
1. Read `.claude/skills/create-skill/SKILL.md` for the workflow details.
|
|
19
|
+
2. Run the referenced `bin/` scripts directly from Codex.
|
|
20
|
+
3. Treat graph and publish steps as best-effort unless that workflow explicitly
|
|
21
|
+
says they are required.
|
|
22
|
+
4. For every external notification, follow
|
|
23
|
+
`.claude/context/notification-consent.md`: plan without sending, then show
|
|
24
|
+
a separate exact Send / Edit / Cancel checkpoint. Never infer notification
|
|
25
|
+
consent from the workflow request or a batch approval.
|
|
26
|
+
5. Keep local-mode behavior filesystem-first and avoid graph or notification
|
|
27
|
+
calls when `egregore.json` declares `"mode": "local"`.
|
|
28
|
+
6. Never call the deprecated `egregore-handoff` CLI for Egregore project
|
|
29
|
+
handoffs.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: slack-connect
|
|
3
|
+
description: 'Fallback Codex adapter for the Egregore slack-connect workflow.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- generated-by: bin/codex-sync-skills.sh -->
|
|
7
|
+
|
|
8
|
+
# Egregore slack-connect Adapter
|
|
9
|
+
|
|
10
|
+
This is fallback coverage for a long-tail Egregore workflow that has not been
|
|
11
|
+
ported to a hand-written Codex-native skill yet.
|
|
12
|
+
|
|
13
|
+
Use the project shell and filesystem directly. Do not invoke Claude Code
|
|
14
|
+
commands. Translate interactive choices to structured Codex question tooling
|
|
15
|
+
when it is available; otherwise render compact numbered choices with an
|
|
16
|
+
`Other:` option and wait for the user.
|
|
17
|
+
|
|
18
|
+
1. Read `.claude/skills/slack-connect/SKILL.md` for the workflow details.
|
|
19
|
+
2. Run the referenced `bin/` scripts directly from Codex.
|
|
20
|
+
3. Treat graph and publish steps as best-effort unless that workflow explicitly
|
|
21
|
+
says they are required.
|
|
22
|
+
4. For every external notification, follow
|
|
23
|
+
`.claude/context/notification-consent.md`: plan without sending, then show
|
|
24
|
+
a separate exact Send / Edit / Cancel checkpoint. Never infer notification
|
|
25
|
+
consent from the workflow request or a batch approval.
|
|
26
|
+
5. Keep local-mode behavior filesystem-first and avoid graph or notification
|
|
27
|
+
calls when `egregore.json` declares `"mode": "local"`.
|
|
28
|
+
6. Never call the deprecated `egregore-handoff` CLI for Egregore project
|
|
29
|
+
handoffs.
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
{
|
|
30
30
|
"section": "On Launch — MANDATORY FIRST ACTION",
|
|
31
31
|
"action": "replace",
|
|
32
|
-
"sourceHash": "
|
|
32
|
+
"sourceHash": "233d160f9ca8",
|
|
33
33
|
"note": "Claude SessionStart hook does not run on Codex; the egregore launcher renders the card via bin/codex-session-start.sh before the session. Greeting-replay instruction dropped (card is already on screen)."
|
|
34
34
|
},
|
|
35
35
|
{
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
{
|
|
42
42
|
"section": "Config Files",
|
|
43
43
|
"action": "keep",
|
|
44
|
-
"sourceHash": "
|
|
44
|
+
"sourceHash": "4ca75fe428ca",
|
|
45
45
|
"note": "Kept verbatim — shell facts, runtime-neutral."
|
|
46
46
|
},
|
|
47
47
|
{
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
{
|
|
102
102
|
"section": "Command Awareness",
|
|
103
103
|
"action": "keep",
|
|
104
|
-
"sourceHash": "
|
|
104
|
+
"sourceHash": "905ad739e18c",
|
|
105
105
|
"note": "Prepended the Codex command surface (skill tokens, native vs adapter split, $save abstraction); disambiguation map kept with /x -> $x token rendering. Tokens with no Codex skill (e.g. /loop, a Claude Code harness feature) are left as-is."
|
|
106
106
|
},
|
|
107
107
|
{
|
|
@@ -142,7 +142,7 @@ If the startup card output contains `onboarding_needed`, invoke the `/onboarding
|
|
|
142
142
|
|
|
143
143
|
## Config Files
|
|
144
144
|
|
|
145
|
-
- **`egregore.json`** — committed. Non-secret org config: `org_name`, `github_org`, `memory_repo`, `slug`, `mode`. `api_url` is connected-mode only. Optional `boundary { posture, read[], locked }` sets the org's isolation posture (see Environment Isolation). **Never put secrets here.**
|
|
145
|
+
- **`egregore.json`** — committed. Non-secret org config: `org_name`, `github_org`, `memory_repo`, `slug`, `mode`. `api_url` is connected-mode only. Optional `boundary { posture, read[], locked }` sets the org's isolation posture (see Environment Isolation). Optional `owned_skills[]` names org-authored skills in `.claude/skills/` that `/update` must never overwrite (created via `/create-skill`). **Never put secrets here.**
|
|
146
146
|
- **`.env`** — gitignored. Personal secrets. Local mode: `GITHUB_TOKEN` only. Connected mode: `GITHUB_TOKEN` + `EGREGORE_API_KEY`. **Never use `source .env`** — use `grep '^KEY=' .env | cut -d'=' -f2-`.
|
|
147
147
|
|
|
148
148
|
In connected mode, infrastructure credentials (Neo4j, Telegram) live on the API server only — `bin/graph.sh` and `bin/notify.sh` route through the API gateway.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"generatedBy": "bin/pi-render-spec.mjs",
|
|
3
3
|
"source": "AGENTS.md generated Codex spec",
|
|
4
|
-
"sourceHash": "
|
|
5
|
-
"outputHash": "
|
|
4
|
+
"sourceHash": "02c4d2d275db",
|
|
5
|
+
"outputHash": "dc9d961b8ce2",
|
|
6
6
|
"adaptations": [
|
|
7
7
|
"Pi project trust and APPEND_SYSTEM delivery",
|
|
8
8
|
"Pi tool_call branch gate",
|
package/runtime/pi/AGENTS.md
CHANGED
|
@@ -216,7 +216,7 @@ If the startup card output contains `onboarding_needed`, invoke the `$onboarding
|
|
|
216
216
|
|
|
217
217
|
## Config Files
|
|
218
218
|
|
|
219
|
-
- **`egregore.json`** — committed. Non-secret org config: `org_name`, `github_org`, `memory_repo`, `slug`, `mode`. `api_url` is connected-mode only. Optional `boundary { posture, read[], locked }` sets the org's isolation posture (see Environment Isolation). **Never put secrets here.**
|
|
219
|
+
- **`egregore.json`** — committed. Non-secret org config: `org_name`, `github_org`, `memory_repo`, `slug`, `mode`. `api_url` is connected-mode only. Optional `boundary { posture, read[], locked }` sets the org's isolation posture (see Environment Isolation). Optional `owned_skills[]` names org-authored skills in `.claude/skills/` that `$update` must never overwrite (created via `$create-skill`). **Never put secrets here.**
|
|
220
220
|
- **`.env`** — gitignored. Personal secrets. Local mode: `GITHUB_TOKEN` only. Connected mode: `GITHUB_TOKEN` + `EGREGORE_API_KEY`. **Never use `source .env`** — use `grep '^KEY=' .env | cut -d'=' -f2-`.
|
|
221
221
|
|
|
222
222
|
In connected mode, infrastructure credentials (Neo4j, Telegram) live on the API server only — `bin/graph.sh` and `bin/notify.sh` route through the API gateway.
|
|
@@ -395,9 +395,10 @@ Invoke commands from user intent — don't wait for the slash. Each command file
|
|
|
395
395
|
**Knowledge** — `$search` `$deep-reflect` `$archive` `$note` `$add` `$meeting` `$ingest` `$scroll` `$mock` `$audit`
|
|
396
396
|
**Identity** — `$me` (view profile or set display name)
|
|
397
397
|
**Coordination** — `$ask` `$quest` `$issue` `$invite` `$delete-user` `$announce`
|
|
398
|
-
**Connectors** — `$telegram-connect` (Telegram group setup) `$teams-connect` (Microsoft Teams channel setup)
|
|
398
|
+
**Connectors** — `$notion-connect` (Notion workspace) `$telegram-connect` (Telegram group setup) `$teams-connect` (Microsoft Teams channel setup)
|
|
399
399
|
**Git** — `$branch` `$commit` `$push` `$pr` `$save` `$review-pr` `$contribute`
|
|
400
400
|
**Spirits** — `$summon` (persistent agent processes)
|
|
401
|
+
**Skills** — `$create-skill` (org-owned skill: scaffold, protect from updates, share)
|
|
401
402
|
**Infra** — `$setup` `$update` `$pull` `$env` `$infra` `$sync-repos` `$release` `$checkup`
|
|
402
403
|
|
|
403
404
|
**Disambiguation:**
|
|
@@ -410,11 +411,12 @@ Invoke commands from user intent — don't wait for the slash. Each command file
|
|
|
410
411
|
- Tasks: `$todo` (personal) · `$quest` (team exploration) · `$issue` (something broken)
|
|
411
412
|
- Questions: `$ask [person]` (async) · just ask (agent answers from context)
|
|
412
413
|
- Ingestion: `$ingest <file-or-folder>` (org-scoped corpus intake) · `$ingest meeting` · `$ingest user-interview` · `$ingest google` · ambiguous → ask which type
|
|
413
|
-
- Connectors: `$telegram-connect` (Telegram group) · `$teams-connect` (MS Teams channel) · `$ingest` (bring content in)
|
|
414
|
+
- Connectors: `$notion-connect` (Notion workspace) · `$telegram-connect` (Telegram group) · `$teams-connect` (MS Teams channel) · `$ingest` (bring content in) · `$ingest notion` (promote Notion docs)
|
|
414
415
|
- Identity: `$me` — "who am I", "call me oz"
|
|
415
416
|
- People: `$invite` (add) · `$delete-user` (remove)
|
|
416
417
|
- PRs: `$pr` (create) · `$review-pr` (review)
|
|
417
418
|
- Contributing: `$contribute` (upstream framework) · `$save` (org repo) · `$issue` (report bug)
|
|
419
|
+
- Skills: `$create-skill` (new org-owned skill) · `$contribute` (change a framework skill for everyone)
|
|
418
420
|
- Agents: `$summon` (design through questions) · `/loop` (quick recurring schedule)
|
|
419
421
|
- Announcements: `$announce` (broadcast to group) · `$handoff` (structured to a person) · `bin/notify.sh send` (DM one person)
|
|
420
422
|
|
|
@@ -143,6 +143,14 @@ __EGREGORE_DESIGN_CSS__
|
|
|
143
143
|
.button { border-radius: 99px; padding: 11px 18px; border: 1px solid var(--border); background: var(--card-bg); color: var(--dark); }
|
|
144
144
|
.button.primary { border-color: var(--terracotta); background: var(--terracotta); color: var(--paper); }
|
|
145
145
|
.button:disabled { cursor: not-allowed; color: var(--warm-gray); border-color: var(--border); background: var(--card-bg); }
|
|
146
|
+
.connectors { margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--border); }
|
|
147
|
+
.connectors-head { display: flex; justify-content: space-between; align-items: baseline; }
|
|
148
|
+
.connectors-head h3 { font-size: 13px; font-weight: 600; color: var(--dark); }
|
|
149
|
+
.connector-row { display: flex; gap: 10px; margin-top: 10px; flex-wrap: wrap; }
|
|
150
|
+
.connector { display: flex; flex-direction: column; gap: 3px; text-align: left; border: 1px solid var(--border); border-radius: 12px; padding: 12px 16px; background: var(--card-bg); color: var(--dark); min-width: 180px; }
|
|
151
|
+
.connector:hover { border-color: var(--terracotta); }
|
|
152
|
+
.connector span { color: var(--muted); font-size: 12px; }
|
|
153
|
+
.connector-note { margin-top: 10px; color: var(--muted); font-size: 12px; line-height: 1.5; }
|
|
146
154
|
.status { border-left: 2px solid var(--terracotta); padding: 10px 12px; color: var(--muted); font-size: 12px; line-height: 1.5; }
|
|
147
155
|
.status.error { border-color: var(--danger); color: var(--danger); }
|
|
148
156
|
.status.done { border-color: var(--success); color: var(--success); }
|
|
@@ -198,6 +206,11 @@ __EGREGORE_DESIGN_CSS__
|
|
|
198
206
|
</div>
|
|
199
207
|
<input id="file-input" type="file" multiple>
|
|
200
208
|
<input id="folder-input" type="file" webkitdirectory multiple>
|
|
209
|
+
<div class="connectors hidden" id="connectors">
|
|
210
|
+
<div class="connectors-head"><h3>Or bring a connected source</h3><span class="step">guided setup</span></div>
|
|
211
|
+
<div class="connector-row" id="connector-row"></div>
|
|
212
|
+
<p class="connector-note">Setup continues in the terminal — the agent walks you through authorization, and your tokens stay on this machine.</p>
|
|
213
|
+
</div>
|
|
201
214
|
</section>
|
|
202
215
|
|
|
203
216
|
<section class="panel hidden" id="review-panel">
|
|
@@ -273,6 +286,59 @@ __EGREGORE_DESIGN_CSS__
|
|
|
273
286
|
document.getElementById('completion').classList.remove('hidden');
|
|
274
287
|
document.title = 'Return to the terminal — Egregore';
|
|
275
288
|
}
|
|
289
|
+
var connectorMeta = {
|
|
290
|
+
google: { label: 'Google Workspace', detail: 'Gmail, Drive, Calendar — multiple accounts' },
|
|
291
|
+
notion: { label: 'Notion', detail: 'Pages and databases' }
|
|
292
|
+
};
|
|
293
|
+
function showConnectorCompletion(provider) {
|
|
294
|
+
var meta = connectorMeta[provider] || { label: provider };
|
|
295
|
+
var account = (capabilities.connector_accounts || {})[provider] || '';
|
|
296
|
+
document.getElementById('completion-kicker').textContent = account
|
|
297
|
+
? 'Already connected'
|
|
298
|
+
: 'Guided setup continues in the terminal';
|
|
299
|
+
document.getElementById('completion-copy').textContent = account
|
|
300
|
+
? meta.label + ' is already connected as ' + account + '. Back in the terminal, your agent can add another account or start bringing material in.'
|
|
301
|
+
: 'Your agent is waiting there to connect ' + meta.label + ' — it walks you through authorization step by step. Tokens stay on this machine.';
|
|
302
|
+
document.getElementById('completion-source').textContent = account || meta.label;
|
|
303
|
+
document.getElementById('completion-files').textContent = account ? 'connected' : 'connected source';
|
|
304
|
+
document.getElementById('completion-outcome').textContent = account ? meta.label : 'guided setup';
|
|
305
|
+
document.querySelector('.shell').classList.add('hidden');
|
|
306
|
+
document.getElementById('completion').classList.remove('hidden');
|
|
307
|
+
document.title = 'Return to the terminal — Egregore';
|
|
308
|
+
}
|
|
309
|
+
function chooseConnector(provider) {
|
|
310
|
+
if (busy) return;
|
|
311
|
+
busy = true;
|
|
312
|
+
post('/api/connector', {
|
|
313
|
+
method: 'POST',
|
|
314
|
+
headers: {'Content-Type': 'application/json'},
|
|
315
|
+
body: JSON.stringify({ provider: provider })
|
|
316
|
+
}).then(function (result) {
|
|
317
|
+
if (result.redirect) {
|
|
318
|
+
// The CLI pre-started the OAuth listener — continue this same tab
|
|
319
|
+
// straight into the sign-in page.
|
|
320
|
+
window.location.href = result.redirect;
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
showConnectorCompletion(provider);
|
|
324
|
+
}).catch(function (err) {
|
|
325
|
+
busy = false;
|
|
326
|
+
setStatus(err.message, 'error');
|
|
327
|
+
statusBox.classList.remove('hidden');
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
(function renderConnectors() {
|
|
331
|
+
var list = (capabilities.connectors || []).filter(function (p) { return connectorMeta[p]; });
|
|
332
|
+
if (!list.length) return;
|
|
333
|
+
var row = document.getElementById('connector-row');
|
|
334
|
+
row.innerHTML = list.map(function (p) {
|
|
335
|
+
return '<button class="connector" type="button" data-provider="' + p + '"><strong>' + connectorMeta[p].label + '</strong><span>' + connectorMeta[p].detail + '</span></button>';
|
|
336
|
+
}).join('');
|
|
337
|
+
row.querySelectorAll('.connector').forEach(function (btn) {
|
|
338
|
+
btn.addEventListener('click', function () { chooseConnector(btn.dataset.provider); });
|
|
339
|
+
});
|
|
340
|
+
document.getElementById('connectors').classList.remove('hidden');
|
|
341
|
+
})();
|
|
276
342
|
function choose(files) {
|
|
277
343
|
var seen = new Set();
|
|
278
344
|
selected = Array.from(files).filter(function (file) {
|