claude-multiacc 1.0.13 → 1.0.15

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/install.sh CHANGED
@@ -4,41 +4,128 @@
4
4
  # its install dir, or its update machinery.
5
5
  #
6
6
  # ./install.sh [--server user@host] install or update (data untouched)
7
+ # ./install.sh --no-server install with NO sync target (local-only pool:
8
+ # a panel/runner daemon distributes accounts)
9
+ # ./install.sh --instance NAME label this install's agents (default: derived
10
+ # from the pool root when it is not the default)
7
11
  # ./install.sh --uninstall remove addon (keeps ~/.claude-accounts)
8
12
  # ./install.sh --uninstall --purge-data remove addon + all account data
13
+ #
14
+ # INSTANCE-SCOPED POOLS: export CLAUDE_ACCOUNTS_ROOT / CODEX_ACCOUNTS_ROOT before
15
+ # running this and the whole install serves that pool — its LaunchAgents/cron entries
16
+ # get their own labels and carry the roots in their environment, so several app-robot
17
+ # instances on one machine never touch each other's accounts or agents.
9
18
  set -u
10
19
 
11
20
  REPO_DIR="$(cd "$(dirname "$0")" && pwd -P)"
12
21
  # shellcheck source=lib/common.sh
13
22
  . "$REPO_DIR/lib/common.sh"
14
23
  # common.sh is sourced in claude mode (ACC_ROOT = the claude pool); the codex pool
15
- # root is needed here too for uninstall/purge messaging.
16
- CODEX_ACC_ROOT="${CODEX_ACCOUNTS_DIR:-$HOME/.codex-accounts}"
17
-
18
- MARK_BEGIN="# >>> claude-multiacc >>>"
19
- MARK_END="# <<< claude-multiacc <<<"
20
- CRON_TAG="# claude-multiacc"
21
- PLIST_LIMITS="$HOME/Library/LaunchAgents/com.claude-multiacc.limits.plist"
22
- PLIST_HEALTH="$HOME/Library/LaunchAgents/com.claude-multiacc.health.plist"
23
- PLIST_UPDATE="$HOME/Library/LaunchAgents/com.claude-multiacc.update.plist"
24
- PLIST_CODEX_LIMITS="$HOME/Library/LaunchAgents/com.claude-multiacc.codex-limits.plist"
25
- PLIST_CODEX_HEALTH="$HOME/Library/LaunchAgents/com.claude-multiacc.codex-health.plist"
26
- PROFILED="/etc/profile.d/claude-multiacc.sh"
24
+ # root is needed here too for the codex agents and uninstall/purge messaging.
25
+ CODEX_ACC_ROOT="${CODEX_ACCOUNTS_ROOT:-${CODEX_ACCOUNTS_DIR:-$HOME/.codex-accounts}}"
27
26
 
28
27
  UNINSTALL=0
29
28
  PURGE=0
30
29
  SERVER_OVERRIDE=""
31
30
  NO_SCHEDULE=0
31
+ INSTANCE_OVERRIDE=""
32
32
  while [ $# -gt 0 ]; do
33
33
  case "$1" in
34
34
  --uninstall) UNINSTALL=1; shift ;;
35
35
  --purge-data) PURGE=1; shift ;;
36
36
  --server) SERVER_OVERRIDE="${2:?--server requires a value}"; shift 2 ;;
37
+ --no-server) SERVER_OVERRIDE="none"; shift ;;
38
+ --instance) INSTANCE_OVERRIDE="${2:?--instance requires a name}"; shift 2 ;;
37
39
  --no-schedule) NO_SCHEDULE=1; shift ;;
38
40
  *) echo "unknown flag: $1" >&2; exit 1 ;;
39
41
  esac
40
42
  done
41
43
 
44
+ # ---- instance scoping -----------------------------------------------------------
45
+ # An install whose pool roots are the defaults keeps the historical labels and rc
46
+ # blocks byte-for-byte. Anything else is an INSTANCE: its agents get a suffix (two
47
+ # instances would otherwise overwrite each other's plists) and the shell rc block is
48
+ # left alone, because one interactive PATH cannot point at two pools at once.
49
+ default_pool_roots() {
50
+ [ "$ACC_ROOT" = "$HOME/.claude-accounts" ] && [ "$CODEX_ACC_ROOT" = "$HOME/.codex-accounts" ]
51
+ }
52
+ short_hash() { # stable 8-char digest of $1, on macOS and Linux alike
53
+ if command -v shasum >/dev/null 2>&1; then printf '%s' "$1" | shasum | cut -c1-8
54
+ elif command -v sha1sum >/dev/null 2>&1; then printf '%s' "$1" | sha1sum | cut -c1-8
55
+ else printf '%s' "$1" | cksum | tr -d ' ' | cut -c1-8; fi
56
+ }
57
+ if [ -n "$INSTANCE_OVERRIDE" ]; then
58
+ case "$INSTANCE_OVERRIDE" in
59
+ *[!A-Za-z0-9-]*) echo "--instance must be letters, digits and dashes: $INSTANCE_OVERRIDE" >&2; exit 1 ;;
60
+ esac
61
+ INSTANCE="$INSTANCE_OVERRIDE"
62
+ elif default_pool_roots; then
63
+ INSTANCE=""
64
+ else
65
+ INSTANCE="i$(short_hash "$ACC_ROOT")"
66
+ fi
67
+ # The roots are interpolated into plists and crontab lines; a quote or newline in one
68
+ # would break out of that context, so such a root is refused up front.
69
+ case "$ACC_ROOT$CODEX_ACC_ROOT" in
70
+ *["'\"<>&"]*|*"
71
+ "*) echo "pool root contains a character that cannot be scheduled safely: $ACC_ROOT / $CODEX_ACC_ROOT" >&2; exit 1 ;;
72
+ esac
73
+
74
+ # A pool root under a temp directory is by definition gone tomorrow, but the launchd
75
+ # agent or crontab line naming it is not: it keeps firing forever against a path that
76
+ # no longer exists, one leaked set per run. That is how a Mac collected 57 orphaned
77
+ # agent sets and a server 4 stray cron blocks. EITHER root disqualifies the whole set,
78
+ # because one install writes agents for both providers: the leaked sets above named a
79
+ # temp CLAUDE root and the REAL codex pool, so they went right on polling a live usage
80
+ # endpoint every five minutes. Install the binaries for such a root; never schedule it.
81
+ ephemeral_root() { # $1 = a pool root
82
+ case "$1" in
83
+ /tmp/*|/private/tmp/*|/var/tmp/*|/private/var/tmp/*|/var/folders/*|/private/var/folders/*)
84
+ return 0 ;;
85
+ *) return 1 ;;
86
+ esac
87
+ }
88
+ if [ "$NO_SCHEDULE" != "1" ] && { ephemeral_root "$ACC_ROOT" || ephemeral_root "$CODEX_ACC_ROOT"; }; then
89
+ NO_SCHEDULE=1
90
+ echo " note: a pool root is under a temp directory (claude: $ACC_ROOT, codex: $CODEX_ACC_ROOT)" >&2
91
+ echo " — installing WITHOUT schedulers, which would outlive it." >&2
92
+ echo " Pass --no-schedule to silence this." >&2
93
+ fi
94
+
95
+ LABEL="com.claude-multiacc${INSTANCE:+.$INSTANCE}"
96
+ MARK_BEGIN="# >>> claude-multiacc >>>"
97
+ MARK_END="# <<< claude-multiacc <<<"
98
+ CRON_TAG="# claude-multiacc${INSTANCE:+ $INSTANCE}"
99
+ PLIST_LIMITS="$HOME/Library/LaunchAgents/$LABEL.limits.plist"
100
+ PLIST_HEALTH="$HOME/Library/LaunchAgents/$LABEL.health.plist"
101
+ PLIST_UPDATE="$HOME/Library/LaunchAgents/$LABEL.update.plist"
102
+ PLIST_CODEX_LIMITS="$HOME/Library/LaunchAgents/$LABEL.codex-limits.plist"
103
+ PLIST_CODEX_HEALTH="$HOME/Library/LaunchAgents/$LABEL.codex-health.plist"
104
+ PROFILED="/etc/profile.d/claude-multiacc.sh"
105
+
106
+ # Pool roots (and any sync override) for a scheduled agent, so it refreshes THIS
107
+ # instance's pool. Empty for a default install — nothing changes there.
108
+ plist_env_block() {
109
+ [ -n "$INSTANCE" ] || return 0
110
+ printf ' <key>EnvironmentVariables</key><dict>\n'
111
+ printf ' <key>CLAUDE_ACCOUNTS_ROOT</key><string>%s</string>\n' "$ACC_ROOT"
112
+ printf ' <key>CODEX_ACCOUNTS_ROOT</key><string>%s</string>\n' "$CODEX_ACC_ROOT"
113
+ printf ' </dict>\n'
114
+ }
115
+ cron_env_prefix() {
116
+ [ -n "$INSTANCE" ] || return 0
117
+ printf "CLAUDE_ACCOUNTS_ROOT='%s' CODEX_ACCOUNTS_ROOT='%s' " "$ACC_ROOT" "$CODEX_ACC_ROOT"
118
+ }
119
+
120
+ is_pool_root() { # true when $1 is safe to delete as an account pool
121
+ case "$1" in
122
+ ''|/|"$HOME") return 1 ;;
123
+ */.claude-accounts|*/.codex-accounts) return 0 ;;
124
+ /*) [ -f "$1/accounts.json" ] ;;
125
+ *) return 1 ;;
126
+ esac
127
+ }
128
+
42
129
  strip_block() { # remove our marked block from a file (portable, no sed -i)
43
130
  local f="$1"
44
131
  [ -f "$f" ] || return 0
@@ -78,13 +165,13 @@ mac_schedule_install() {
78
165
  <?xml version="1.0" encoding="UTF-8"?>
79
166
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
80
167
  <plist version="1.0"><dict>
81
- <key>Label</key><string>com.claude-multiacc.limits</string>
168
+ <key>Label</key><string>$LABEL.limits</string>
82
169
  <key>ProgramArguments</key><array>
83
170
  <string>$REPO_DIR/bin/claude-accounts</string>
84
171
  <string>limits</string>
85
172
  <string>--quiet</string>
86
173
  </array>
87
- <key>StartInterval</key><integer>60</integer>
174
+ $(plist_env_block) <key>StartInterval</key><integer>900</integer>
88
175
  <key>RunAtLoad</key><true/>
89
176
  <key>StandardOutPath</key><string>/dev/null</string>
90
177
  <key>StandardErrorPath</key><string>/dev/null</string>
@@ -94,12 +181,12 @@ EOF
94
181
  <?xml version="1.0" encoding="UTF-8"?>
95
182
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
96
183
  <plist version="1.0"><dict>
97
- <key>Label</key><string>com.claude-multiacc.health</string>
184
+ <key>Label</key><string>$LABEL.health</string>
98
185
  <key>ProgramArguments</key><array>
99
186
  <string>$REPO_DIR/bin/claude-accounts</string>
100
187
  <string>health</string>
101
188
  </array>
102
- <key>StartCalendarInterval</key><dict>
189
+ $(plist_env_block) <key>StartCalendarInterval</key><dict>
103
190
  <key>Weekday</key><integer>1</integer>
104
191
  <key>Hour</key><integer>9</integer>
105
192
  <key>Minute</key><integer>17</integer>
@@ -113,13 +200,13 @@ EOF
113
200
  <?xml version="1.0" encoding="UTF-8"?>
114
201
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
115
202
  <plist version="1.0"><dict>
116
- <key>Label</key><string>com.claude-multiacc.update</string>
203
+ <key>Label</key><string>$LABEL.update</string>
117
204
  <key>ProgramArguments</key><array>
118
205
  <string>$REPO_DIR/bin/claude-accounts</string>
119
206
  <string>self-update</string>
120
207
  <string>--quiet</string>
121
208
  </array>
122
- <key>StartCalendarInterval</key><dict>
209
+ $(plist_env_block) <key>StartCalendarInterval</key><dict>
123
210
  <key>Hour</key><integer>4</integer>
124
211
  <key>Minute</key><integer>7</integer>
125
212
  </dict>
@@ -133,13 +220,13 @@ EOF
133
220
  <?xml version="1.0" encoding="UTF-8"?>
134
221
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
135
222
  <plist version="1.0"><dict>
136
- <key>Label</key><string>com.claude-multiacc.codex-limits</string>
223
+ <key>Label</key><string>$LABEL.codex-limits</string>
137
224
  <key>ProgramArguments</key><array>
138
225
  <string>$REPO_DIR/bin/codex-accounts</string>
139
226
  <string>limits</string>
140
227
  <string>--quiet</string>
141
228
  </array>
142
- <key>StartInterval</key><integer>60</integer>
229
+ $(plist_env_block) <key>StartInterval</key><integer>300</integer>
143
230
  <key>RunAtLoad</key><true/>
144
231
  <key>StandardOutPath</key><string>/dev/null</string>
145
232
  <key>StandardErrorPath</key><string>/dev/null</string>
@@ -149,12 +236,12 @@ EOF
149
236
  <?xml version="1.0" encoding="UTF-8"?>
150
237
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
151
238
  <plist version="1.0"><dict>
152
- <key>Label</key><string>com.claude-multiacc.codex-health</string>
239
+ <key>Label</key><string>$LABEL.codex-health</string>
153
240
  <key>ProgramArguments</key><array>
154
241
  <string>$REPO_DIR/bin/codex-accounts</string>
155
242
  <string>health</string>
156
243
  </array>
157
- <key>StartCalendarInterval</key><dict>
244
+ $(plist_env_block) <key>StartCalendarInterval</key><dict>
158
245
  <key>Weekday</key><integer>1</integer>
159
246
  <key>Hour</key><integer>9</integer>
160
247
  <key>Minute</key><integer>37</integer>
@@ -173,7 +260,7 @@ EOF
173
260
  launchctl load -w "$PLIST_CODEX_LIMITS" 2>/dev/null || true
174
261
  launchctl load -w "$PLIST_CODEX_HEALTH" 2>/dev/null || true
175
262
  [ "${CLAUDE_MULTIACC_AUTOUPDATE:-1}" = "1" ] && launchctl load -w "$PLIST_UPDATE" 2>/dev/null || true
176
- echo " launchd: limits refresh every 60s (claude + codex) + weekly health checks + daily auto-update"
263
+ echo " launchd: limits refresh (claude 15m / codex 5m) + weekly health checks + daily auto-update"
177
264
  }
178
265
 
179
266
  mac_schedule_remove() {
@@ -185,60 +272,80 @@ mac_schedule_remove() {
185
272
  rm -f "$PLIST_LIMITS" "$PLIST_HEALTH" "$PLIST_UPDATE" "$PLIST_CODEX_LIMITS" "$PLIST_CODEX_HEALTH"
186
273
  }
187
274
 
275
+ # Only THIS instance's lines are dropped: the tag is matched anchored to end of line,
276
+ # so a default install ("# claude-multiacc") never removes an instance's lines
277
+ # ("# claude-multiacc iXXXXXXXX") or the other way round.
278
+ cron_strip_ours() { grep -v -E "$CRON_TAG\$" || true; }
279
+
188
280
  linux_schedule_install() {
189
- local tmp
281
+ local tmp env
190
282
  tmp="$(mktemp)"
191
- { crontab -l 2>/dev/null | grep -v "$CRON_TAG" || true; } > "$tmp"
283
+ env="$(cron_env_prefix)"
284
+ { crontab -l 2>/dev/null | cron_strip_ours; } > "$tmp"
192
285
  {
193
- echo "* * * * * $REPO_DIR/bin/claude-accounts limits --quiet >/dev/null 2>&1 $CRON_TAG"
194
- echo "* * * * * $REPO_DIR/bin/codex-accounts limits --quiet >/dev/null 2>&1 $CRON_TAG"
195
- echo "17 9 * * 1 $REPO_DIR/bin/claude-accounts health >/dev/null 2>&1 $CRON_TAG"
196
- echo "37 9 * * 1 $REPO_DIR/bin/codex-accounts health >/dev/null 2>&1 $CRON_TAG"
286
+ echo "*/15 * * * * $env$REPO_DIR/bin/claude-accounts limits --quiet >/dev/null 2>&1 $CRON_TAG"
287
+ echo "*/5 * * * * $env$REPO_DIR/bin/codex-accounts limits --quiet >/dev/null 2>&1 $CRON_TAG"
288
+ echo "17 9 * * 1 $env$REPO_DIR/bin/claude-accounts health >/dev/null 2>&1 $CRON_TAG"
289
+ echo "37 9 * * 1 $env$REPO_DIR/bin/codex-accounts health >/dev/null 2>&1 $CRON_TAG"
197
290
  [ "${CLAUDE_MULTIACC_AUTOUPDATE:-1}" = "1" ] && \
198
- echo "7 4 * * * $REPO_DIR/bin/claude-accounts self-update --quiet >/dev/null 2>&1 $CRON_TAG"
291
+ echo "7 4 * * * $env$REPO_DIR/bin/claude-accounts self-update --quiet >/dev/null 2>&1 $CRON_TAG"
199
292
  } >> "$tmp"
200
293
  crontab "$tmp"
201
294
  rm -f "$tmp"
202
- echo " cron: limits refresh every 60s (claude + codex) + weekly health checks + daily auto-update"
295
+ echo " cron: limits refresh (claude 15m / codex 5m) + weekly health checks + daily auto-update"
203
296
  }
204
297
 
205
298
  linux_schedule_remove() {
206
299
  local tmp
207
300
  tmp="$(mktemp)"
208
- { crontab -l 2>/dev/null | grep -v "$CRON_TAG" || true; } > "$tmp"
301
+ { crontab -l 2>/dev/null | cron_strip_ours; } > "$tmp"
209
302
  crontab "$tmp"
210
303
  rm -f "$tmp"
211
304
  }
212
305
 
213
306
  do_uninstall() {
214
307
  echo "claude-multiacc: uninstalling (restoring stock behavior)"
215
- strip_block "$HOME/.zshenv"
216
- strip_block "$HOME/.zprofile"
217
- strip_block "$HOME/.zshrc"
218
- strip_block "$HOME/.bashrc"
219
- strip_block "$HOME/.bash_profile"
220
- strip_block "$HOME/.profile"
308
+ # An instance install never wrote a shell rc block, a /etc/profile.d file or the
309
+ # /usr/local/bin shims — those belong to the DEFAULT install and are shared with it.
310
+ # Removing them here would disable the operator's shim while only one instance was
311
+ # being uninstalled, so an instance removes exactly its own agents.
312
+ if [ -n "$INSTANCE" ]; then
313
+ echo " instance $INSTANCE: removing its agents only (PATH block and shims belong to the default install)"
314
+ else
315
+ strip_block "$HOME/.zshenv"
316
+ strip_block "$HOME/.zprofile"
317
+ strip_block "$HOME/.zshrc"
318
+ strip_block "$HOME/.bashrc"
319
+ strip_block "$HOME/.bash_profile"
320
+ strip_block "$HOME/.profile"
321
+ fi
221
322
  if [ "$(machine_kind)" = "mac" ]; then
222
323
  mac_schedule_remove
223
324
  else
224
325
  linux_schedule_remove
225
- [ -f "$PROFILED" ] && rm -f "$PROFILED"
226
- if [ -L /usr/local/bin/claude ] && [ "$(canon_path /usr/local/bin/claude)" = "$(canon_path "$REPO_DIR/bin/claude")" ]; then
227
- rm -f /usr/local/bin/claude
228
- echo " removed /usr/local/bin/claude shim symlink"
229
- fi
230
- if [ -L /usr/local/bin/codex ] && [ "$(canon_path /usr/local/bin/codex)" = "$(canon_path "$REPO_DIR/bin/codex")" ]; then
231
- rm -f /usr/local/bin/codex
232
- echo " removed /usr/local/bin/codex shim symlink"
326
+ if [ -z "$INSTANCE" ]; then
327
+ [ -f "$PROFILED" ] && rm -f "$PROFILED"
328
+ if [ -L /usr/local/bin/claude ] && [ "$(canon_path /usr/local/bin/claude)" = "$(canon_path "$REPO_DIR/bin/claude")" ]; then
329
+ rm -f /usr/local/bin/claude
330
+ echo " removed /usr/local/bin/claude shim symlink"
331
+ fi
332
+ if [ -L /usr/local/bin/codex ] && [ "$(canon_path /usr/local/bin/codex)" = "$(canon_path "$REPO_DIR/bin/codex")" ]; then
333
+ rm -f /usr/local/bin/codex
334
+ echo " removed /usr/local/bin/codex shim symlink"
335
+ fi
233
336
  fi
234
337
  fi
235
338
  local root
236
339
  for root in "$ACC_ROOT" "$CODEX_ACC_ROOT"; do
237
340
  if [ "$PURGE" = "1" ]; then
238
- case "$root" in
239
- */.claude-accounts|*/.codex-accounts) rm -rf "$root"; echo " purged $root" ;;
240
- *) echo " refusing to purge unusual accounts root: $root" >&2 ;;
241
- esac
341
+ # Either the conventional location, or a directory that is provably a pool (it
342
+ # holds a manifest) an instance root lives anywhere, so the manifest is what
343
+ # makes `rm -rf` safe. Anything else is left alone.
344
+ if is_pool_root "$root"; then
345
+ rm -rf "$root"; echo " purged $root"
346
+ else
347
+ echo " refusing to purge unusual accounts root: $root" >&2
348
+ fi
242
349
  fi
243
350
  done
244
351
  if [ "$PURGE" != "1" ]; then
@@ -251,6 +358,9 @@ do_install() {
251
358
  local kind
252
359
  kind="$(machine_kind)"
253
360
  echo "claude-multiacc: installing (mode: $kind, repo: $REPO_DIR)"
361
+ if [ -n "$INSTANCE" ]; then
362
+ echo " instance: $INSTANCE (agents labelled $LABEL.*, pools $ACC_ROOT + $CODEX_ACC_ROOT)"
363
+ fi
254
364
 
255
365
  [ -x "$REPO_DIR/bin/claude" ] || chmod +x "$REPO_DIR/bin/claude" 2>/dev/null || true
256
366
  [ -x "$REPO_DIR/bin/claude-accounts" ] || chmod +x "$REPO_DIR/bin/claude-accounts" 2>/dev/null || true
@@ -289,7 +399,11 @@ with open(sys.argv[1] + '.tmp', 'w') as f:
289
399
  os.replace(sys.argv[1] + '.tmp', sys.argv[1])
290
400
  PYEOF
291
401
  fi
292
- echo " account pool: $ACC_ROOT (manifest ready)"
402
+ if sync_target_is_local "$(sync_target)"; then
403
+ echo " account pool: $ACC_ROOT (manifest ready; sync target: none — local-only)"
404
+ else
405
+ echo " account pool: $ACC_ROOT (manifest ready; sync target: $(sync_target))"
406
+ fi
293
407
  # The codex pool gets its own skeleton + manifest (same schema, separate root).
294
408
  if ! "$REPO_DIR/bin/codex-accounts" init-pool "${SERVER_OVERRIDE:-}" >/dev/null 2>&1; then
295
409
  echo " WARNING: codex pool init failed (codex-accounts init-pool)" >&2
@@ -297,31 +411,44 @@ PYEOF
297
411
  echo " codex account pool: $CODEX_ACC_ROOT (manifest ready)"
298
412
  fi
299
413
 
414
+ # An INSTANCE install never rewrites the shell rc blocks: one interactive PATH
415
+ # cannot serve two pools, and clobbering the default install's block would point
416
+ # the operator's shell at an instance pool. Its agents (below) carry the roots
417
+ # instead, and the runner daemon invokes the shims by absolute path.
418
+ if [ -n "$INSTANCE" ]; then
419
+ echo " PATH block: skipped (instance install) — for a shell against this pool:"
420
+ echo " export CLAUDE_ACCOUNTS_ROOT='$ACC_ROOT' CODEX_ACCOUNTS_ROOT='$CODEX_ACC_ROOT'"
421
+ echo " export PATH=\"$REPO_DIR/bin:\$PATH\""
422
+ fi
300
423
  if [ "$kind" = "mac" ]; then
301
424
  # .zshenv covers non-interactive zsh; the .zshrc block must be LAST so it wins
302
425
  # over ~/.local/bin re-prepends done earlier in .zshrc/.zprofile.
303
426
  # zsh reads: .zshenv always; .zprofile for login; .zshrc for interactive.
304
427
  # The block must end each file that later re-prepends ~/.local/bin.
305
- touch "$HOME/.zshenv"
306
- append_block "$HOME/.zshenv"
307
- touch "$HOME/.zprofile"
308
- append_block "$HOME/.zprofile"
309
- touch "$HOME/.zshrc"
310
- append_block "$HOME/.zshrc"
311
- [ -f "$HOME/.bash_profile" ] && append_block "$HOME/.bash_profile"
312
- [ -f "$HOME/.bashrc" ] && append_block "$HOME/.bashrc"
313
- echo " PATH block: end of ~/.zshenv, ~/.zprofile, ~/.zshrc (+ bash rc files if present)"
428
+ if [ -z "$INSTANCE" ]; then
429
+ touch "$HOME/.zshenv"
430
+ append_block "$HOME/.zshenv"
431
+ touch "$HOME/.zprofile"
432
+ append_block "$HOME/.zprofile"
433
+ touch "$HOME/.zshrc"
434
+ append_block "$HOME/.zshrc"
435
+ [ -f "$HOME/.bash_profile" ] && append_block "$HOME/.bash_profile"
436
+ [ -f "$HOME/.bashrc" ] && append_block "$HOME/.bashrc"
437
+ echo " PATH block: end of ~/.zshenv, ~/.zprofile, ~/.zshrc (+ bash rc files if present)"
438
+ fi
314
439
  [ "$NO_SCHEDULE" = "1" ] || mac_schedule_install
315
440
  else
316
- touch "$HOME/.bashrc"
317
- append_block "$HOME/.bashrc"
318
- if [ -w /etc/profile.d ] 2>/dev/null || [ "$(id -u)" = "0" ]; then
319
- {
320
- printf '%s\n' "$MARK_BEGIN"
321
- path_block_body
322
- printf '%s\n' "$MARK_END"
323
- } > "$PROFILED"
324
- echo " PATH block: ~/.bashrc + $PROFILED"
441
+ if [ -z "$INSTANCE" ]; then
442
+ touch "$HOME/.bashrc"
443
+ append_block "$HOME/.bashrc"
444
+ if [ -w /etc/profile.d ] 2>/dev/null || [ "$(id -u)" = "0" ]; then
445
+ {
446
+ printf '%s\n' "$MARK_BEGIN"
447
+ path_block_body
448
+ printf '%s\n' "$MARK_END"
449
+ } > "$PROFILED"
450
+ echo " PATH block: ~/.bashrc + $PROFILED"
451
+ fi
325
452
  fi
326
453
  if [ "$(id -u)" = "0" ]; then
327
454
  if [ -e /usr/local/bin/claude ] && [ ! -L /usr/local/bin/claude ]; then