personal memory agent
1# SPDX-License-Identifier: AGPL-3.0-only
2# Copyright (c) 2026 sol pbc
3
4"""Guard user-level sol alias ownership."""
5
6from __future__ import annotations
7
8import os
9import sys
10from enum import Enum
11from pathlib import Path
12
13
14class AliasState(Enum):
15 WORKTREE = "worktree"
16 ABSENT = "absent"
17 OWNED = "owned"
18 CROSS_REPO = "cross_repo"
19 DANGLING = "dangling"
20 NOT_SYMLINK = "not_symlink"
21
22
23def alias_path() -> Path:
24 return Path.home() / ".local" / "bin" / "sol"
25
26
27def expected_target(curdir: Path) -> Path:
28 return curdir / ".venv" / "bin" / "sol"
29
30
31def check_alias(curdir: Path) -> tuple[AliasState, Path | None]:
32 if (curdir / ".git").is_file():
33 return AliasState.WORKTREE, None
34
35 alias = alias_path()
36 if not alias.exists() and not alias.is_symlink():
37 return AliasState.ABSENT, None
38
39 if alias.is_symlink():
40 target = Path(os.readlink(alias))
41 if not target.is_absolute():
42 target = alias.parent / target
43 target = target.resolve()
44 if not target.exists():
45 return AliasState.DANGLING, target
46 if target == expected_target(curdir).resolve():
47 return AliasState.OWNED, target
48 return AliasState.CROSS_REPO, target
49
50 return AliasState.NOT_SYMLINK, None
51
52
53def format_error(
54 state: AliasState,
55 curdir: Path,
56 _alias: Path,
57 other_target: Path | None,
58) -> str:
59 if state is AliasState.WORKTREE:
60 return (
61 f"ERROR: refusing to run from a git worktree ({curdir}). "
62 "Run from the primary clone."
63 )
64
65 if state is AliasState.CROSS_REPO:
66 installed = f" installed: {other_target}"
67 elif state is AliasState.DANGLING:
68 installed = f" installed: dangling: {other_target} does not exist"
69 else:
70 installed = " installed: not a symlink"
71
72 return "\n".join(
73 [
74 "ERROR: Another solstone install owns ~/.local/bin/sol.",
75 f" this repo: {curdir}",
76 installed,
77 "Run 'make uninstall-service' from the installed repo first,",
78 "or remove ~/.local/bin/sol manually if that repo is gone. No --force available.",
79 ]
80 )
81
82
83def _print_error(
84 state: AliasState,
85 curdir: Path,
86 alias: Path,
87 other_target: Path | None,
88) -> None:
89 sys.stderr.write(format_error(state, curdir, alias, other_target) + "\n")
90
91
92def cmd_check(curdir: Path) -> int:
93 alias = alias_path()
94 state, other_target = check_alias(curdir)
95
96 if state is AliasState.ABSENT:
97 print("fresh")
98 return 0
99 if state is AliasState.OWNED:
100 print("upgrade")
101 return 0
102
103 print(state.value)
104 _print_error(state, curdir, alias, other_target)
105 return 1
106
107
108def cmd_install(curdir: Path) -> int:
109 alias = alias_path()
110 state, other_target = check_alias(curdir)
111
112 if state is AliasState.WORKTREE:
113 _print_error(state, curdir, alias, other_target)
114 return 1
115 if state is AliasState.ABSENT:
116 alias.parent.mkdir(parents=True, exist_ok=True)
117 alias.symlink_to(expected_target(curdir))
118 print("installed")
119 return 0
120 if state is AliasState.OWNED:
121 alias.unlink()
122 alias.symlink_to(expected_target(curdir))
123 print("installed")
124 return 0
125
126 _print_error(state, curdir, alias, other_target)
127 return 1
128
129
130def cmd_uninstall(curdir: Path) -> int:
131 alias = alias_path()
132 state, other_target = check_alias(curdir)
133
134 if state is AliasState.WORKTREE:
135 _print_error(state, curdir, alias, other_target)
136 return 1
137 if state is AliasState.ABSENT:
138 print("absent")
139 return 0
140 if state is AliasState.OWNED:
141 alias.unlink()
142 print("removed")
143 return 0
144
145 _print_error(state, curdir, alias, other_target)
146 return 1
147
148
149def main(argv: list[str] | None = None) -> int:
150 if argv is None:
151 argv = sys.argv[1:]
152 if len(argv) != 1 or argv[0] not in {"check", "install", "uninstall"}:
153 sys.stderr.write(
154 "usage: python -m think.install_guard <check|install|uninstall>\n"
155 )
156 return 2
157
158 curdir = Path.cwd().resolve()
159 if argv[0] == "check":
160 return cmd_check(curdir)
161 if argv[0] == "install":
162 return cmd_install(curdir)
163 return cmd_uninstall(curdir)
164
165
166if __name__ == "__main__":
167 sys.exit(main())