create-egregore 0.19.19 → 0.19.20
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 +15 -8
- package/package.json +2 -2
- package/runtime/codex/bin/agent.sh +2 -0
- package/runtime/codex/bin/autosave.sh +86 -0
- package/runtime/codex/bin/codex-session-start.sh +15 -4
- package/runtime/codex/bin/handoff-save-egregore.sh +8 -2
- package/runtime/codex/bin/lib/greeting.sh +16 -4
- package/runtime/codex/bin/session-autosave.sh +26 -1
- package/runtime/pi/bin/agent.sh +2 -0
- package/runtime/pi/bin/autosave.sh +86 -0
- package/runtime/pi/bin/codex-session-start.sh +15 -4
- package/runtime/pi/bin/handoff-save-egregore.sh +8 -2
- package/runtime/pi/bin/lib/greeting.sh +16 -4
- package/runtime/pi/bin/session-autosave.sh +26 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// egregore-launcher-version: 0.19.
|
|
2
|
+
// egregore-launcher-version: 0.19.20
|
|
3
3
|
//
|
|
4
4
|
// egregore — launch and browse Egregore instances from your terminal.
|
|
5
5
|
// Installed by create-egregore. Reads ~/.egregore/instances.json.
|
|
@@ -33,7 +33,7 @@ const crypto = require("node:crypto");
|
|
|
33
33
|
const { spawnSync, spawn } = require("node:child_process");
|
|
34
34
|
const readline = require("node:readline");
|
|
35
35
|
|
|
36
|
-
const VERSION = "0.19.
|
|
36
|
+
const VERSION = "0.19.20";
|
|
37
37
|
const REGISTRY = path.join(os.homedir(), ".egregore", "instances.json");
|
|
38
38
|
const DEFAULT_FILE = path.join(os.homedir(), ".egregore", "default");
|
|
39
39
|
const UPDATE_CACHE_FILE = path.join(os.homedir(), ".egregore", "update-check.json");
|
|
@@ -749,6 +749,9 @@ async function launch(inst, runtimeOverride) {
|
|
|
749
749
|
}
|
|
750
750
|
const runtime = await chooseRuntimeForLaunch(inst, runtimeOverride);
|
|
751
751
|
if (!runtime) return;
|
|
752
|
+
if (runtime === "claude-code") {
|
|
753
|
+
normalizeClaudeDenyRules(inst.path);
|
|
754
|
+
}
|
|
752
755
|
const binary = runtimeBinary(runtime);
|
|
753
756
|
if (!commandExists(binary)) {
|
|
754
757
|
die(`${runtimeLabel(runtime)} is not installed.\n Install it with:\n ${assistantInstallCommand(runtime)}\n Then run: egregore`);
|
|
@@ -1213,15 +1216,19 @@ function writeJsonFileAtomic(file, value) {
|
|
|
1213
1216
|
atomicWrite(file, JSON.stringify(value, null, 2) + "\n", 0o644);
|
|
1214
1217
|
}
|
|
1215
1218
|
|
|
1216
|
-
function
|
|
1217
|
-
const stale = "Write(~/.egregore/instances.json)";
|
|
1218
|
-
const supported = "Edit(~/.egregore/instances.json)";
|
|
1219
|
+
function normalizeClaudeDenyRules(instancePath) {
|
|
1219
1220
|
for (const relative of [".claude/settings.json", ".claude/settings.local.json"]) {
|
|
1220
1221
|
const file = path.join(instancePath, relative);
|
|
1221
1222
|
const settings = readJsonFile(file);
|
|
1222
1223
|
const deny = settings?.permissions?.deny;
|
|
1223
|
-
if (!Array.isArray(deny)
|
|
1224
|
-
|
|
1224
|
+
if (!Array.isArray(deny)) continue;
|
|
1225
|
+
const normalized = deny.map((rule) => (
|
|
1226
|
+
typeof rule === "string" && /^Write\(.+\)$/.test(rule)
|
|
1227
|
+
? `Edit${rule.slice("Write".length)}`
|
|
1228
|
+
: rule
|
|
1229
|
+
));
|
|
1230
|
+
if (normalized.every((rule, index) => rule === deny[index])) continue;
|
|
1231
|
+
settings.permissions.deny = [...new Set(normalized)];
|
|
1225
1232
|
writeJsonFileAtomic(file, settings);
|
|
1226
1233
|
}
|
|
1227
1234
|
}
|
|
@@ -1454,7 +1461,7 @@ async function cmdConnect(args) {
|
|
|
1454
1461
|
api_url: claim.data.api_url,
|
|
1455
1462
|
});
|
|
1456
1463
|
writeEnvValueAtomic(envFile, "EGREGORE_API_KEY", claim.data.api_key);
|
|
1457
|
-
|
|
1464
|
+
normalizeClaudeDenyRules(inst.path);
|
|
1458
1465
|
persistConnectState(inst, {
|
|
1459
1466
|
status: "backfilling",
|
|
1460
1467
|
activation_protocol: claim.data.activation_protocol,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-egregore",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.20",
|
|
4
4
|
"description": "Set up Egregore for your team in one command — installs the egregore launcher and your choice of Claude Code, Codex, or Pi.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"scripts": {
|
|
13
13
|
"build:runtime": "node scripts/build-runtime-bundle.mjs && node scripts/build-pi-runtime-bundle.mjs",
|
|
14
14
|
"build:pi-runtime": "node scripts/build-pi-runtime-bundle.mjs",
|
|
15
|
-
"test": "node scripts/build-runtime-bundle.mjs --check && node scripts/build-pi-runtime-bundle.mjs --check && node --test test/context.test.js test/parse-join-target.test.js test/skip-repos.test.js test/onboarding-shortcircuit.test.js test/welcome-screen.test.js test/runtime-selection.test.js test/command-generation.test.js test/auth-gh.test.js test/gauntlet-fixes.test.js test/codex-runtime.test.js test/pi-runtime.test.js test/pi-product-workflows.test.js test/installer-runtime-source.test.js test/scroll-repro.test.js test/launcher-refocus-redraw.test.js test/multiselect-remote-search.test.js test/back-navigation.test.js test/manifest-form.test.js test/launcher-delete.test.js test/launcher-connect.test.js test/launcher-update.test.js test/connected-config.test.js",
|
|
15
|
+
"test": "node scripts/build-runtime-bundle.mjs --check && node scripts/build-pi-runtime-bundle.mjs --check && node --test test/context.test.js test/parse-join-target.test.js test/skip-repos.test.js test/onboarding-shortcircuit.test.js test/welcome-screen.test.js test/runtime-selection.test.js test/command-generation.test.js test/auth-gh.test.js test/gauntlet-fixes.test.js test/codex-runtime.test.js test/pi-runtime.test.js test/pi-product-workflows.test.js test/installer-runtime-source.test.js test/scroll-repro.test.js test/launcher-refocus-redraw.test.js test/multiselect-remote-search.test.js test/back-navigation.test.js test/manifest-form.test.js test/launcher-delete.test.js test/launcher-connect.test.js test/launcher-permissions.test.js test/launcher-update.test.js test/connected-config.test.js",
|
|
16
16
|
"test:e2e": "node --test test/e2e-local.test.js",
|
|
17
17
|
"test:all": "node scripts/build-runtime-bundle.mjs --check && node scripts/build-pi-runtime-bundle.mjs --check && node --test test/"
|
|
18
18
|
},
|
|
@@ -36,6 +36,7 @@ Runtime-neutral commands:
|
|
|
36
36
|
Commit and push current repo + memory changes;
|
|
37
37
|
PR body follows .claude/context/pr-format.md
|
|
38
38
|
(auto-generated skeleton if not provided)
|
|
39
|
+
autosave [status|on|off|scope|publish|merge] Personal ambient-save settings + consent
|
|
39
40
|
wrap --topic T --summary TEXT Write a session wrap to memory/wraps
|
|
40
41
|
handoff --from A --to B --topic T [--intent action|feedback|fyi]
|
|
41
42
|
[--body TEXT|--body-file PATH] [--no-push] [--no-publish] [--no-notify] [--json]
|
|
@@ -732,6 +733,7 @@ case "${1:-}" in
|
|
|
732
733
|
people) shift; cmd_people "$@" ;;
|
|
733
734
|
branch) shift; cmd_branch "$@" ;;
|
|
734
735
|
save) shift; cmd_save "$@" ;;
|
|
736
|
+
autosave) shift; bash "$SCRIPT_DIR/bin/autosave.sh" "$@" ;;
|
|
735
737
|
wrap) shift; cmd_wrap "$@" ;;
|
|
736
738
|
handoff) shift; cmd_handoff "$@" ;;
|
|
737
739
|
ask) shift; cmd_ask "$@" ;;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# autosave.sh — per-user autosave settings. The consent surface for ambient
|
|
3
|
+
# session capture (see bin/session-autosave.sh, docs/autosave.md).
|
|
4
|
+
#
|
|
5
|
+
# Settings live in .egregore-state.json (gitignored, personal — same pattern
|
|
6
|
+
# as the transcript_sharing consent flag):
|
|
7
|
+
# autosave_enabled true|false ambient capture on/off (default FALSE — opt-in experiment)
|
|
8
|
+
# autosave_scope all|handoffs what it touches (default all)
|
|
9
|
+
# all = memory repo + core-repo non-coding work
|
|
10
|
+
# handoffs = memory repo only; core repo never touched
|
|
11
|
+
# ambiently
|
|
12
|
+
# autosave_publish gate|auto what happens to the PR (default gate)
|
|
13
|
+
# gate = PR opened, merge waits for your explicit word
|
|
14
|
+
# auto = PR merged to develop automatically
|
|
15
|
+
#
|
|
16
|
+
# Usage:
|
|
17
|
+
# autosave.sh status
|
|
18
|
+
# autosave.sh on | off
|
|
19
|
+
# autosave.sh scope all|handoffs
|
|
20
|
+
# autosave.sh publish gate|auto
|
|
21
|
+
# autosave.sh merge # the consent act: merge YOUR staged auto-save PRs
|
|
22
|
+
#
|
|
23
|
+
# Exposed to Claude via the /autosave skill and to Codex/Pi via
|
|
24
|
+
# `bin/agent.sh autosave ...`.
|
|
25
|
+
|
|
26
|
+
set -uo pipefail
|
|
27
|
+
|
|
28
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
29
|
+
STATE_FILE="$SCRIPT_DIR/.egregore-state.json"
|
|
30
|
+
|
|
31
|
+
[ -f "$STATE_FILE" ] || echo '{}' > "$STATE_FILE"
|
|
32
|
+
|
|
33
|
+
# NB: jq's `//` swallows false — status would show "on" for enabled:false.
|
|
34
|
+
# Test key presence instead.
|
|
35
|
+
get() {
|
|
36
|
+
local v
|
|
37
|
+
v=$(jq -r "if ($1 != null) then ($1|tostring) else empty end" "$STATE_FILE" 2>/dev/null)
|
|
38
|
+
[ -n "$v" ] && echo "$v" || echo "$2"
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
set_key() { # key value(json-encoded)
|
|
42
|
+
jq ".$1 = $2" "$STATE_FILE" > "$STATE_FILE.tmp" && mv "$STATE_FILE.tmp" "$STATE_FILE"
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
status() {
|
|
46
|
+
local enabled scope publish
|
|
47
|
+
enabled=$(get '.autosave_enabled' 'false')
|
|
48
|
+
scope=$(get '.autosave_scope' 'all')
|
|
49
|
+
publish=$(get '.autosave_publish' 'gate')
|
|
50
|
+
echo "autosave: $([ "$enabled" = "true" ] && echo on || echo "off (opt-in — 'autosave on' to join the experiment)")"
|
|
51
|
+
echo "scope: $scope (all = handoffs + artifacts + docs · handoffs = memory repo only)"
|
|
52
|
+
echo "publish: $publish (gate = PRs wait for your merge · auto = merge to develop automatically)"
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
case "${1:-status}" in
|
|
56
|
+
status) status ;;
|
|
57
|
+
on) set_key autosave_enabled true && echo "autosave: on" ;;
|
|
58
|
+
off) set_key autosave_enabled false && echo "autosave: off — nothing is captured ambiently; /save and /handoff still work" ;;
|
|
59
|
+
scope)
|
|
60
|
+
case "${2:-}" in
|
|
61
|
+
all|handoffs) set_key autosave_scope "\"$2\"" && echo "scope: $2" ;;
|
|
62
|
+
*) echo "usage: autosave.sh scope all|handoffs" >&2; exit 2 ;;
|
|
63
|
+
esac ;;
|
|
64
|
+
publish)
|
|
65
|
+
case "${2:-}" in
|
|
66
|
+
gate|auto) set_key autosave_publish "\"$2\"" && echo "publish: $2" ;;
|
|
67
|
+
*) echo "usage: autosave.sh publish gate|auto" >&2; exit 2 ;;
|
|
68
|
+
esac ;;
|
|
69
|
+
merge)
|
|
70
|
+
# The consent act under publish=gate: merge this user's own staged
|
|
71
|
+
# Auto-save PRs. Never touches anyone else's.
|
|
72
|
+
command -v gh >/dev/null 2>&1 || { echo "gh not available" >&2; exit 1; }
|
|
73
|
+
GH_SELF=$(get '.github_username' '')
|
|
74
|
+
[ -n "$GH_SELF" ] || { echo "no github_username in .egregore-state.json" >&2; exit 1; }
|
|
75
|
+
PRS=$(gh pr list --state open --author "$GH_SELF" --limit 20 \
|
|
76
|
+
--json number,title --jq '.[] | select(.title | startswith("Auto-save:")) | .number' 2>/dev/null)
|
|
77
|
+
[ -n "$PRS" ] || { echo "no auto-saves waiting"; exit 0; }
|
|
78
|
+
for n in $PRS; do
|
|
79
|
+
if gh pr merge "$n" --merge >/dev/null 2>&1; then
|
|
80
|
+
echo "✓ merged auto-save PR #$n → develop"
|
|
81
|
+
else
|
|
82
|
+
echo "✗ PR #$n did not merge (conflict with develop?) — it stays open"
|
|
83
|
+
fi
|
|
84
|
+
done ;;
|
|
85
|
+
*) echo "usage: autosave.sh [status|on|off|scope all|handoffs|publish gate|auto|merge]" >&2; exit 2 ;;
|
|
86
|
+
esac
|
|
@@ -183,15 +183,26 @@ render_card() {
|
|
|
183
183
|
if [ "$total_as" -gt "$seen_as" ]; then
|
|
184
184
|
new_as=$((total_as - seen_as))
|
|
185
185
|
merged_as=$(grep '|autosave|' "$autosave_log" 2>/dev/null | tail -n "$new_as" | grep -c '|merged|' || echo 0)
|
|
186
|
-
if [ "$merged_as" -
|
|
187
|
-
printf ' ⟲ auto-saved %s non-coding save(s) → develop while you were away\n' "$
|
|
188
|
-
else
|
|
189
|
-
printf ' ⟲ auto-saved %s non-coding save(s) — %s merged, rest in open PRs\n' "$new_as" "$merged_as"
|
|
186
|
+
if [ "$merged_as" -gt 0 ]; then
|
|
187
|
+
printf ' ⟲ auto-saved %s non-coding save(s) → develop while you were away\n' "$merged_as"
|
|
190
188
|
fi
|
|
191
189
|
fi
|
|
192
190
|
printf '%s' "$total_as" > "$autosave_seen" 2>/dev/null || true
|
|
193
191
|
fi
|
|
194
192
|
|
|
193
|
+
# Staged auto-saves awaiting the user's merge (the autosave consent gate).
|
|
194
|
+
# Live gh query so the line persists until the PRs actually merge.
|
|
195
|
+
if command -v gh >/dev/null 2>&1; then
|
|
196
|
+
gh_self=$(jq -r '.github_username // empty' "$STATE_FILE" 2>/dev/null)
|
|
197
|
+
if [ -n "$gh_self" ]; then
|
|
198
|
+
waiting_as=$(gh pr list --state open --author "$gh_self" --limit 20 \
|
|
199
|
+
--json number,title --jq '[.[] | select(.title | startswith("Auto-save:")) | .number] | join(", #")' 2>/dev/null || true)
|
|
200
|
+
if [ -n "$waiting_as" ]; then
|
|
201
|
+
printf ' ⟲ auto-saves awaiting your merge: PR #%s — \$autosave merge (or bin/agent.sh autosave merge)\n' "$waiting_as"
|
|
202
|
+
fi
|
|
203
|
+
fi
|
|
204
|
+
fi
|
|
205
|
+
|
|
195
206
|
# Greeting links. Org config `pinned_links` in egregore.json replaces the
|
|
196
207
|
# default board link — same contract as bin/lib/greeting.sh.
|
|
197
208
|
pinned_links=$(jq -c '.pinned_links // []' "$CONFIG" 2>/dev/null || echo "[]")
|
|
@@ -33,11 +33,13 @@ shift 2 2>/dev/null || true
|
|
|
33
33
|
REPO_DIR=""
|
|
34
34
|
KIND="handoff"
|
|
35
35
|
BRANCH_SUFFIX=""
|
|
36
|
+
PUBLISH="auto"
|
|
36
37
|
while [ $# -gt 0 ]; do
|
|
37
38
|
case "$1" in
|
|
38
39
|
--repo-dir) REPO_DIR="${2:-}"; shift 2 ;;
|
|
39
40
|
--kind) KIND="${2:-handoff}"; shift 2 ;;
|
|
40
41
|
--branch-suffix) BRANCH_SUFFIX="${2:-}"; shift 2 ;;
|
|
42
|
+
--publish) PUBLISH="${2:-auto}"; shift 2 ;;
|
|
41
43
|
*) shift ;;
|
|
42
44
|
esac
|
|
43
45
|
done
|
|
@@ -158,14 +160,18 @@ fi
|
|
|
158
160
|
|
|
159
161
|
[ -n "$PR_NUMBER" ] || { ledger_write "-" "pushed"; exit 0; }
|
|
160
162
|
|
|
161
|
-
# ---
|
|
163
|
+
# --- Merge decision -------------------------------------------------------
|
|
164
|
+
# Non-coding + publish=auto → merge now. publish=gate → the PR is staged and
|
|
165
|
+
# the merge waits for the user's explicit word (the /autosave consent gate).
|
|
162
166
|
# `--auto` only works when the repo allows auto-merge AND required checks are
|
|
163
167
|
# pending; on a repo with no branch protection it always errors. Fall back to
|
|
164
168
|
# an immediate merge so non-coding PRs never sit open.
|
|
165
169
|
OUTCOME="open"
|
|
166
170
|
NON_CODE_BLOCKER=$(noncode_blockers origin/develop .)
|
|
167
171
|
if [ -z "$NON_CODE_BLOCKER" ]; then
|
|
168
|
-
if
|
|
172
|
+
if [ "$PUBLISH" = "gate" ]; then
|
|
173
|
+
OUTCOME="staged"
|
|
174
|
+
elif gh pr merge "$PR_NUMBER" --auto --merge >/dev/null 2>&1 \
|
|
169
175
|
|| gh pr merge "$PR_NUMBER" --merge >/dev/null 2>&1; then
|
|
170
176
|
OUTCOME="merged"
|
|
171
177
|
fi
|
|
@@ -164,15 +164,27 @@ if [ -f "$AUTOSAVE_LOG" ]; then
|
|
|
164
164
|
if [ "$TOTAL_AS" -gt "$SEEN_AS" ]; then
|
|
165
165
|
NEW_AS=$((TOTAL_AS - SEEN_AS))
|
|
166
166
|
MERGED_AS=$(grep '|autosave|' "$AUTOSAVE_LOG" 2>/dev/null | tail -n "$NEW_AS" | grep -c '|merged|' || echo 0)
|
|
167
|
-
if [ "$MERGED_AS" -
|
|
168
|
-
printf " ⟲ auto-saved %s non-coding save(s) → develop while you were away\n" "$
|
|
169
|
-
else
|
|
170
|
-
printf " ⟲ auto-saved %s non-coding save(s) — %s merged, rest in open PRs\n" "$NEW_AS" "$MERGED_AS"
|
|
167
|
+
if [ "$MERGED_AS" -gt 0 ]; then
|
|
168
|
+
printf " ⟲ auto-saved %s non-coding save(s) → develop while you were away\n" "$MERGED_AS"
|
|
171
169
|
fi
|
|
172
170
|
fi
|
|
173
171
|
printf '%s' "$TOTAL_AS" > "$AUTOSAVE_SEEN" 2>/dev/null || true
|
|
174
172
|
fi
|
|
175
173
|
|
|
174
|
+
# Staged auto-saves awaiting the user's merge (the /autosave consent gate).
|
|
175
|
+
# Live gh query so the line persists until the PRs actually merge — the
|
|
176
|
+
# ledger cursor above only reports once. Fail-soft when gh/network is out.
|
|
177
|
+
if command -v gh >/dev/null 2>&1; then
|
|
178
|
+
GH_SELF=$(jq -r '.github_username // empty' "$STATE_FILE" 2>/dev/null)
|
|
179
|
+
if [ -n "$GH_SELF" ]; then
|
|
180
|
+
WAITING_AS=$(gh pr list --state open --author "$GH_SELF" --limit 20 \
|
|
181
|
+
--json number,title --jq '[.[] | select(.title | startswith("Auto-save:")) | .number] | join(", #")' 2>/dev/null || true)
|
|
182
|
+
if [ -n "$WAITING_AS" ]; then
|
|
183
|
+
printf " ⟲ auto-saves awaiting your merge: PR #%s — say \"merge my auto-saves\"\n" "$WAITING_AS"
|
|
184
|
+
fi
|
|
185
|
+
fi
|
|
186
|
+
fi
|
|
187
|
+
|
|
176
188
|
# Greeting links. Org config `pinned_links` in egregore.json replaces the
|
|
177
189
|
# default dashboard + board links. Entries are strings or {url, label}.
|
|
178
190
|
# (OSC 8 hyperlink — clickable text in supported terminals)
|
|
@@ -87,6 +87,26 @@ touch "$STAMP" 2>/dev/null || true
|
|
|
87
87
|
# shellcheck source=bin/lib/noncode.sh
|
|
88
88
|
. "$SCRIPT_ROOT/bin/lib/noncode.sh" 2>/dev/null || exit 0
|
|
89
89
|
|
|
90
|
+
# --- Per-user consent settings (bin/autosave.sh / the /autosave skill) ----
|
|
91
|
+
# enabled=false → fully off, nothing captured ambiently
|
|
92
|
+
# scope=handoffs → memory repo only; the core repo is never touched ambiently
|
|
93
|
+
# publish=gate → PR is opened but NEVER merged without the user's word
|
|
94
|
+
# NB: jq's `//` swallows false (falsy) — `.k // empty` on k:false yields the
|
|
95
|
+
# default, silently re-enabling autosave. Test key presence instead (this is
|
|
96
|
+
# the same trap that broke auto_update:false).
|
|
97
|
+
_as_conf() { # key default
|
|
98
|
+
local v
|
|
99
|
+
v=$(jq -r "if (.$1 != null) then (.$1|tostring) else empty end" "$DIR/.egregore-state.json" 2>/dev/null)
|
|
100
|
+
[ -n "$v" ] || v=$(jq -r "if (.$1 != null) then (.$1|tostring) else empty end" "$SCRIPT_ROOT/.egregore-state.json" 2>/dev/null)
|
|
101
|
+
[ -n "$v" ] && echo "$v" || echo "$2"
|
|
102
|
+
}
|
|
103
|
+
# Autosave is an opt-in experiment: enabled defaults to FALSE. Nothing is
|
|
104
|
+
# captured ambiently until the user runs `/autosave on` (or autosave.sh on).
|
|
105
|
+
AS_ENABLED=$(_as_conf autosave_enabled false)
|
|
106
|
+
AS_SCOPE=$(_as_conf autosave_scope all)
|
|
107
|
+
AS_PUBLISH=$(_as_conf autosave_publish gate)
|
|
108
|
+
[ "$AS_ENABLED" = "true" ] || exit 0
|
|
109
|
+
|
|
90
110
|
if [ -z "$AUTHOR" ]; then
|
|
91
111
|
AUTHOR=$(jq -r '.github_username // .name // empty' "$DIR/.egregore-state.json" 2>/dev/null || true)
|
|
92
112
|
[ -n "$AUTHOR" ] || AUTHOR=$(jq -r '.github_username // .name // empty' "$SCRIPT_ROOT/.egregore-state.json" 2>/dev/null || true)
|
|
@@ -111,6 +131,10 @@ if git -C "$DIR/memory" rev-parse --git-dir >/dev/null 2>&1; then
|
|
|
111
131
|
fi
|
|
112
132
|
fi
|
|
113
133
|
|
|
134
|
+
# scope=handoffs: memory (above) is the handoff channel; stop before the
|
|
135
|
+
# core repo — it is never captured ambiently under this scope.
|
|
136
|
+
[ "$AS_SCOPE" = "handoffs" ] && exit 0
|
|
137
|
+
|
|
114
138
|
# --- Core repo gate: everything pending must be non-coding ---------------
|
|
115
139
|
# -uall expands untracked dirs to individual files: a collapsed "docs/" entry
|
|
116
140
|
# would defeat both the classifier (code nested under a content dir) and the
|
|
@@ -144,7 +168,7 @@ ORIG_BRANCH=$(git branch --show-current 2>/dev/null || echo "")
|
|
|
144
168
|
|
|
145
169
|
bash "$SCRIPT_ROOT/bin/handoff-save-egregore.sh" "$AUTHOR" \
|
|
146
170
|
"session content $(date +%Y-%m-%d)" --kind autosave --repo-dir "$DIR" \
|
|
147
|
-
--branch-suffix "$DIR_HASH" \
|
|
171
|
+
--branch-suffix "$DIR_HASH" --publish "$AS_PUBLISH" \
|
|
148
172
|
>/dev/null 2>&1 || true
|
|
149
173
|
|
|
150
174
|
# Tell the user what just happened. Under the Stop hook this line surfaces in
|
|
@@ -162,6 +186,7 @@ if [ -n "$LAST" ]; then
|
|
|
162
186
|
if [ $(( NOW_EPOCH - TS_EPOCH )) -lt 120 ]; then
|
|
163
187
|
case "$RESULT" in
|
|
164
188
|
merged) echo "⟲ auto-saved: $NF non-coding file(s) → develop (PR #$PR merged)" ;;
|
|
189
|
+
staged) echo "⟲ auto-saved: $NF non-coding file(s) → PR #$PR — yours to merge (say \"merge my auto-saves\" or /autosave)" ;;
|
|
165
190
|
open) echo "⟲ auto-saved: $NF non-coding file(s) → PR #$PR (merge pending)" ;;
|
|
166
191
|
*) echo "⟲ auto-saved: $NF non-coding file(s) → branch pushed (PR pending)" ;;
|
|
167
192
|
esac
|
package/runtime/pi/bin/agent.sh
CHANGED
|
@@ -36,6 +36,7 @@ Runtime-neutral commands:
|
|
|
36
36
|
Commit and push current repo + memory changes;
|
|
37
37
|
PR body follows .claude/context/pr-format.md
|
|
38
38
|
(auto-generated skeleton if not provided)
|
|
39
|
+
autosave [status|on|off|scope|publish|merge] Personal ambient-save settings + consent
|
|
39
40
|
wrap --topic T --summary TEXT Write a session wrap to memory/wraps
|
|
40
41
|
handoff --from A --to B --topic T [--intent action|feedback|fyi]
|
|
41
42
|
[--body TEXT|--body-file PATH] [--no-push] [--no-publish] [--no-notify] [--json]
|
|
@@ -732,6 +733,7 @@ case "${1:-}" in
|
|
|
732
733
|
people) shift; cmd_people "$@" ;;
|
|
733
734
|
branch) shift; cmd_branch "$@" ;;
|
|
734
735
|
save) shift; cmd_save "$@" ;;
|
|
736
|
+
autosave) shift; bash "$SCRIPT_DIR/bin/autosave.sh" "$@" ;;
|
|
735
737
|
wrap) shift; cmd_wrap "$@" ;;
|
|
736
738
|
handoff) shift; cmd_handoff "$@" ;;
|
|
737
739
|
ask) shift; cmd_ask "$@" ;;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# autosave.sh — per-user autosave settings. The consent surface for ambient
|
|
3
|
+
# session capture (see bin/session-autosave.sh, docs/autosave.md).
|
|
4
|
+
#
|
|
5
|
+
# Settings live in .egregore-state.json (gitignored, personal — same pattern
|
|
6
|
+
# as the transcript_sharing consent flag):
|
|
7
|
+
# autosave_enabled true|false ambient capture on/off (default FALSE — opt-in experiment)
|
|
8
|
+
# autosave_scope all|handoffs what it touches (default all)
|
|
9
|
+
# all = memory repo + core-repo non-coding work
|
|
10
|
+
# handoffs = memory repo only; core repo never touched
|
|
11
|
+
# ambiently
|
|
12
|
+
# autosave_publish gate|auto what happens to the PR (default gate)
|
|
13
|
+
# gate = PR opened, merge waits for your explicit word
|
|
14
|
+
# auto = PR merged to develop automatically
|
|
15
|
+
#
|
|
16
|
+
# Usage:
|
|
17
|
+
# autosave.sh status
|
|
18
|
+
# autosave.sh on | off
|
|
19
|
+
# autosave.sh scope all|handoffs
|
|
20
|
+
# autosave.sh publish gate|auto
|
|
21
|
+
# autosave.sh merge # the consent act: merge YOUR staged auto-save PRs
|
|
22
|
+
#
|
|
23
|
+
# Exposed to Claude via the /autosave skill and to Codex/Pi via
|
|
24
|
+
# `bin/agent.sh autosave ...`.
|
|
25
|
+
|
|
26
|
+
set -uo pipefail
|
|
27
|
+
|
|
28
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
29
|
+
STATE_FILE="$SCRIPT_DIR/.egregore-state.json"
|
|
30
|
+
|
|
31
|
+
[ -f "$STATE_FILE" ] || echo '{}' > "$STATE_FILE"
|
|
32
|
+
|
|
33
|
+
# NB: jq's `//` swallows false — status would show "on" for enabled:false.
|
|
34
|
+
# Test key presence instead.
|
|
35
|
+
get() {
|
|
36
|
+
local v
|
|
37
|
+
v=$(jq -r "if ($1 != null) then ($1|tostring) else empty end" "$STATE_FILE" 2>/dev/null)
|
|
38
|
+
[ -n "$v" ] && echo "$v" || echo "$2"
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
set_key() { # key value(json-encoded)
|
|
42
|
+
jq ".$1 = $2" "$STATE_FILE" > "$STATE_FILE.tmp" && mv "$STATE_FILE.tmp" "$STATE_FILE"
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
status() {
|
|
46
|
+
local enabled scope publish
|
|
47
|
+
enabled=$(get '.autosave_enabled' 'false')
|
|
48
|
+
scope=$(get '.autosave_scope' 'all')
|
|
49
|
+
publish=$(get '.autosave_publish' 'gate')
|
|
50
|
+
echo "autosave: $([ "$enabled" = "true" ] && echo on || echo "off (opt-in — 'autosave on' to join the experiment)")"
|
|
51
|
+
echo "scope: $scope (all = handoffs + artifacts + docs · handoffs = memory repo only)"
|
|
52
|
+
echo "publish: $publish (gate = PRs wait for your merge · auto = merge to develop automatically)"
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
case "${1:-status}" in
|
|
56
|
+
status) status ;;
|
|
57
|
+
on) set_key autosave_enabled true && echo "autosave: on" ;;
|
|
58
|
+
off) set_key autosave_enabled false && echo "autosave: off — nothing is captured ambiently; /save and /handoff still work" ;;
|
|
59
|
+
scope)
|
|
60
|
+
case "${2:-}" in
|
|
61
|
+
all|handoffs) set_key autosave_scope "\"$2\"" && echo "scope: $2" ;;
|
|
62
|
+
*) echo "usage: autosave.sh scope all|handoffs" >&2; exit 2 ;;
|
|
63
|
+
esac ;;
|
|
64
|
+
publish)
|
|
65
|
+
case "${2:-}" in
|
|
66
|
+
gate|auto) set_key autosave_publish "\"$2\"" && echo "publish: $2" ;;
|
|
67
|
+
*) echo "usage: autosave.sh publish gate|auto" >&2; exit 2 ;;
|
|
68
|
+
esac ;;
|
|
69
|
+
merge)
|
|
70
|
+
# The consent act under publish=gate: merge this user's own staged
|
|
71
|
+
# Auto-save PRs. Never touches anyone else's.
|
|
72
|
+
command -v gh >/dev/null 2>&1 || { echo "gh not available" >&2; exit 1; }
|
|
73
|
+
GH_SELF=$(get '.github_username' '')
|
|
74
|
+
[ -n "$GH_SELF" ] || { echo "no github_username in .egregore-state.json" >&2; exit 1; }
|
|
75
|
+
PRS=$(gh pr list --state open --author "$GH_SELF" --limit 20 \
|
|
76
|
+
--json number,title --jq '.[] | select(.title | startswith("Auto-save:")) | .number' 2>/dev/null)
|
|
77
|
+
[ -n "$PRS" ] || { echo "no auto-saves waiting"; exit 0; }
|
|
78
|
+
for n in $PRS; do
|
|
79
|
+
if gh pr merge "$n" --merge >/dev/null 2>&1; then
|
|
80
|
+
echo "✓ merged auto-save PR #$n → develop"
|
|
81
|
+
else
|
|
82
|
+
echo "✗ PR #$n did not merge (conflict with develop?) — it stays open"
|
|
83
|
+
fi
|
|
84
|
+
done ;;
|
|
85
|
+
*) echo "usage: autosave.sh [status|on|off|scope all|handoffs|publish gate|auto|merge]" >&2; exit 2 ;;
|
|
86
|
+
esac
|
|
@@ -183,15 +183,26 @@ render_card() {
|
|
|
183
183
|
if [ "$total_as" -gt "$seen_as" ]; then
|
|
184
184
|
new_as=$((total_as - seen_as))
|
|
185
185
|
merged_as=$(grep '|autosave|' "$autosave_log" 2>/dev/null | tail -n "$new_as" | grep -c '|merged|' || echo 0)
|
|
186
|
-
if [ "$merged_as" -
|
|
187
|
-
printf ' ⟲ auto-saved %s non-coding save(s) → develop while you were away\n' "$
|
|
188
|
-
else
|
|
189
|
-
printf ' ⟲ auto-saved %s non-coding save(s) — %s merged, rest in open PRs\n' "$new_as" "$merged_as"
|
|
186
|
+
if [ "$merged_as" -gt 0 ]; then
|
|
187
|
+
printf ' ⟲ auto-saved %s non-coding save(s) → develop while you were away\n' "$merged_as"
|
|
190
188
|
fi
|
|
191
189
|
fi
|
|
192
190
|
printf '%s' "$total_as" > "$autosave_seen" 2>/dev/null || true
|
|
193
191
|
fi
|
|
194
192
|
|
|
193
|
+
# Staged auto-saves awaiting the user's merge (the autosave consent gate).
|
|
194
|
+
# Live gh query so the line persists until the PRs actually merge.
|
|
195
|
+
if command -v gh >/dev/null 2>&1; then
|
|
196
|
+
gh_self=$(jq -r '.github_username // empty' "$STATE_FILE" 2>/dev/null)
|
|
197
|
+
if [ -n "$gh_self" ]; then
|
|
198
|
+
waiting_as=$(gh pr list --state open --author "$gh_self" --limit 20 \
|
|
199
|
+
--json number,title --jq '[.[] | select(.title | startswith("Auto-save:")) | .number] | join(", #")' 2>/dev/null || true)
|
|
200
|
+
if [ -n "$waiting_as" ]; then
|
|
201
|
+
printf ' ⟲ auto-saves awaiting your merge: PR #%s — \$autosave merge (or bin/agent.sh autosave merge)\n' "$waiting_as"
|
|
202
|
+
fi
|
|
203
|
+
fi
|
|
204
|
+
fi
|
|
205
|
+
|
|
195
206
|
# Greeting links. Org config `pinned_links` in egregore.json replaces the
|
|
196
207
|
# default board link — same contract as bin/lib/greeting.sh.
|
|
197
208
|
pinned_links=$(jq -c '.pinned_links // []' "$CONFIG" 2>/dev/null || echo "[]")
|
|
@@ -33,11 +33,13 @@ shift 2 2>/dev/null || true
|
|
|
33
33
|
REPO_DIR=""
|
|
34
34
|
KIND="handoff"
|
|
35
35
|
BRANCH_SUFFIX=""
|
|
36
|
+
PUBLISH="auto"
|
|
36
37
|
while [ $# -gt 0 ]; do
|
|
37
38
|
case "$1" in
|
|
38
39
|
--repo-dir) REPO_DIR="${2:-}"; shift 2 ;;
|
|
39
40
|
--kind) KIND="${2:-handoff}"; shift 2 ;;
|
|
40
41
|
--branch-suffix) BRANCH_SUFFIX="${2:-}"; shift 2 ;;
|
|
42
|
+
--publish) PUBLISH="${2:-auto}"; shift 2 ;;
|
|
41
43
|
*) shift ;;
|
|
42
44
|
esac
|
|
43
45
|
done
|
|
@@ -158,14 +160,18 @@ fi
|
|
|
158
160
|
|
|
159
161
|
[ -n "$PR_NUMBER" ] || { ledger_write "-" "pushed"; exit 0; }
|
|
160
162
|
|
|
161
|
-
# ---
|
|
163
|
+
# --- Merge decision -------------------------------------------------------
|
|
164
|
+
# Non-coding + publish=auto → merge now. publish=gate → the PR is staged and
|
|
165
|
+
# the merge waits for the user's explicit word (the /autosave consent gate).
|
|
162
166
|
# `--auto` only works when the repo allows auto-merge AND required checks are
|
|
163
167
|
# pending; on a repo with no branch protection it always errors. Fall back to
|
|
164
168
|
# an immediate merge so non-coding PRs never sit open.
|
|
165
169
|
OUTCOME="open"
|
|
166
170
|
NON_CODE_BLOCKER=$(noncode_blockers origin/develop .)
|
|
167
171
|
if [ -z "$NON_CODE_BLOCKER" ]; then
|
|
168
|
-
if
|
|
172
|
+
if [ "$PUBLISH" = "gate" ]; then
|
|
173
|
+
OUTCOME="staged"
|
|
174
|
+
elif gh pr merge "$PR_NUMBER" --auto --merge >/dev/null 2>&1 \
|
|
169
175
|
|| gh pr merge "$PR_NUMBER" --merge >/dev/null 2>&1; then
|
|
170
176
|
OUTCOME="merged"
|
|
171
177
|
fi
|
|
@@ -164,15 +164,27 @@ if [ -f "$AUTOSAVE_LOG" ]; then
|
|
|
164
164
|
if [ "$TOTAL_AS" -gt "$SEEN_AS" ]; then
|
|
165
165
|
NEW_AS=$((TOTAL_AS - SEEN_AS))
|
|
166
166
|
MERGED_AS=$(grep '|autosave|' "$AUTOSAVE_LOG" 2>/dev/null | tail -n "$NEW_AS" | grep -c '|merged|' || echo 0)
|
|
167
|
-
if [ "$MERGED_AS" -
|
|
168
|
-
printf " ⟲ auto-saved %s non-coding save(s) → develop while you were away\n" "$
|
|
169
|
-
else
|
|
170
|
-
printf " ⟲ auto-saved %s non-coding save(s) — %s merged, rest in open PRs\n" "$NEW_AS" "$MERGED_AS"
|
|
167
|
+
if [ "$MERGED_AS" -gt 0 ]; then
|
|
168
|
+
printf " ⟲ auto-saved %s non-coding save(s) → develop while you were away\n" "$MERGED_AS"
|
|
171
169
|
fi
|
|
172
170
|
fi
|
|
173
171
|
printf '%s' "$TOTAL_AS" > "$AUTOSAVE_SEEN" 2>/dev/null || true
|
|
174
172
|
fi
|
|
175
173
|
|
|
174
|
+
# Staged auto-saves awaiting the user's merge (the /autosave consent gate).
|
|
175
|
+
# Live gh query so the line persists until the PRs actually merge — the
|
|
176
|
+
# ledger cursor above only reports once. Fail-soft when gh/network is out.
|
|
177
|
+
if command -v gh >/dev/null 2>&1; then
|
|
178
|
+
GH_SELF=$(jq -r '.github_username // empty' "$STATE_FILE" 2>/dev/null)
|
|
179
|
+
if [ -n "$GH_SELF" ]; then
|
|
180
|
+
WAITING_AS=$(gh pr list --state open --author "$GH_SELF" --limit 20 \
|
|
181
|
+
--json number,title --jq '[.[] | select(.title | startswith("Auto-save:")) | .number] | join(", #")' 2>/dev/null || true)
|
|
182
|
+
if [ -n "$WAITING_AS" ]; then
|
|
183
|
+
printf " ⟲ auto-saves awaiting your merge: PR #%s — say \"merge my auto-saves\"\n" "$WAITING_AS"
|
|
184
|
+
fi
|
|
185
|
+
fi
|
|
186
|
+
fi
|
|
187
|
+
|
|
176
188
|
# Greeting links. Org config `pinned_links` in egregore.json replaces the
|
|
177
189
|
# default dashboard + board links. Entries are strings or {url, label}.
|
|
178
190
|
# (OSC 8 hyperlink — clickable text in supported terminals)
|
|
@@ -87,6 +87,26 @@ touch "$STAMP" 2>/dev/null || true
|
|
|
87
87
|
# shellcheck source=bin/lib/noncode.sh
|
|
88
88
|
. "$SCRIPT_ROOT/bin/lib/noncode.sh" 2>/dev/null || exit 0
|
|
89
89
|
|
|
90
|
+
# --- Per-user consent settings (bin/autosave.sh / the /autosave skill) ----
|
|
91
|
+
# enabled=false → fully off, nothing captured ambiently
|
|
92
|
+
# scope=handoffs → memory repo only; the core repo is never touched ambiently
|
|
93
|
+
# publish=gate → PR is opened but NEVER merged without the user's word
|
|
94
|
+
# NB: jq's `//` swallows false (falsy) — `.k // empty` on k:false yields the
|
|
95
|
+
# default, silently re-enabling autosave. Test key presence instead (this is
|
|
96
|
+
# the same trap that broke auto_update:false).
|
|
97
|
+
_as_conf() { # key default
|
|
98
|
+
local v
|
|
99
|
+
v=$(jq -r "if (.$1 != null) then (.$1|tostring) else empty end" "$DIR/.egregore-state.json" 2>/dev/null)
|
|
100
|
+
[ -n "$v" ] || v=$(jq -r "if (.$1 != null) then (.$1|tostring) else empty end" "$SCRIPT_ROOT/.egregore-state.json" 2>/dev/null)
|
|
101
|
+
[ -n "$v" ] && echo "$v" || echo "$2"
|
|
102
|
+
}
|
|
103
|
+
# Autosave is an opt-in experiment: enabled defaults to FALSE. Nothing is
|
|
104
|
+
# captured ambiently until the user runs `/autosave on` (or autosave.sh on).
|
|
105
|
+
AS_ENABLED=$(_as_conf autosave_enabled false)
|
|
106
|
+
AS_SCOPE=$(_as_conf autosave_scope all)
|
|
107
|
+
AS_PUBLISH=$(_as_conf autosave_publish gate)
|
|
108
|
+
[ "$AS_ENABLED" = "true" ] || exit 0
|
|
109
|
+
|
|
90
110
|
if [ -z "$AUTHOR" ]; then
|
|
91
111
|
AUTHOR=$(jq -r '.github_username // .name // empty' "$DIR/.egregore-state.json" 2>/dev/null || true)
|
|
92
112
|
[ -n "$AUTHOR" ] || AUTHOR=$(jq -r '.github_username // .name // empty' "$SCRIPT_ROOT/.egregore-state.json" 2>/dev/null || true)
|
|
@@ -111,6 +131,10 @@ if git -C "$DIR/memory" rev-parse --git-dir >/dev/null 2>&1; then
|
|
|
111
131
|
fi
|
|
112
132
|
fi
|
|
113
133
|
|
|
134
|
+
# scope=handoffs: memory (above) is the handoff channel; stop before the
|
|
135
|
+
# core repo — it is never captured ambiently under this scope.
|
|
136
|
+
[ "$AS_SCOPE" = "handoffs" ] && exit 0
|
|
137
|
+
|
|
114
138
|
# --- Core repo gate: everything pending must be non-coding ---------------
|
|
115
139
|
# -uall expands untracked dirs to individual files: a collapsed "docs/" entry
|
|
116
140
|
# would defeat both the classifier (code nested under a content dir) and the
|
|
@@ -144,7 +168,7 @@ ORIG_BRANCH=$(git branch --show-current 2>/dev/null || echo "")
|
|
|
144
168
|
|
|
145
169
|
bash "$SCRIPT_ROOT/bin/handoff-save-egregore.sh" "$AUTHOR" \
|
|
146
170
|
"session content $(date +%Y-%m-%d)" --kind autosave --repo-dir "$DIR" \
|
|
147
|
-
--branch-suffix "$DIR_HASH" \
|
|
171
|
+
--branch-suffix "$DIR_HASH" --publish "$AS_PUBLISH" \
|
|
148
172
|
>/dev/null 2>&1 || true
|
|
149
173
|
|
|
150
174
|
# Tell the user what just happened. Under the Stop hook this line surfaces in
|
|
@@ -162,6 +186,7 @@ if [ -n "$LAST" ]; then
|
|
|
162
186
|
if [ $(( NOW_EPOCH - TS_EPOCH )) -lt 120 ]; then
|
|
163
187
|
case "$RESULT" in
|
|
164
188
|
merged) echo "⟲ auto-saved: $NF non-coding file(s) → develop (PR #$PR merged)" ;;
|
|
189
|
+
staged) echo "⟲ auto-saved: $NF non-coding file(s) → PR #$PR — yours to merge (say \"merge my auto-saves\" or /autosave)" ;;
|
|
165
190
|
open) echo "⟲ auto-saved: $NF non-coding file(s) → PR #$PR (merge pending)" ;;
|
|
166
191
|
*) echo "⟲ auto-saved: $NF non-coding file(s) → branch pushed (PR pending)" ;;
|
|
167
192
|
esac
|