Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,6 @@
[submodule "sysBenches/365Bench"]
path = sysBenches/365Bench
url = git@github.com:opensoft/365Bench.git
[submodule "devBenches/rustBench"]
path = devBenches/rustBench
url = git@github.com:opensoft/rustBench.git
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ The TUI Tools column includes editor, terminal, and local agent tooling:
| Visual Studio Code | Windows/WSL winget, Linux/manual fallback | Dev Containers and WSL extension checks |
| Warp Terminal | Windows/WSL winget, Linux/manual fallback | Windows terminal |
| Wave Terminal | Windows/WSL winget, Linux/manual fallback | AI terminal |
| Pi Terminal | Windows npm from WSL, WSL/Linux npm fallback | `npm install -g --ignore-scripts @earendil-works/pi-coding-agent`; run `pi` then `/login` |
| Pi Terminal | Windows and WSL/Linux npm | `npm install -g --ignore-scripts @earendil-works/pi-coding-agent`; use isolated `ppi PROFILE` or standard `pi` |
| AmneziaVPN | Windows/WSL winget | Amnezia/AmneziaWG client access |
| 0dcloud VPN | local installer/manual + local patch | 0dcloud detection and MTU fix |

Expand Down
21 changes: 16 additions & 5 deletions apps/credential-manager/credential_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class Account:
status: str = "active"
auth_mode: str = "browser"
secret_env: str = ""
profile_path: str = ""


PROVIDER_ALIASES = {
Expand Down Expand Up @@ -93,6 +94,7 @@ def load_accounts(repo: Path) -> list[Account]:
status=item.get("status", "active"),
auth_mode=item.get("authMode", "browser"),
secret_env=item.get("secretEnv", ""),
profile_path=item.get("profilePath", ""),
)
accounts.append(account)
seen.add((account.provider, account.name))
Expand Down Expand Up @@ -124,6 +126,7 @@ def load_accounts(repo: Path) -> list[Account]:
status=item.get("status", "active"),
auth_mode=item.get("authMode", "browser"),
secret_env=item.get("secretEnv", ""),
profile_path=item.get("profilePath", ""),
)
accounts.append(account)
seen.add(identity)
Expand All @@ -132,17 +135,25 @@ def load_accounts(repo: Path) -> list[Account]:

def profile_home(account: Account) -> Path | None:
if account.provider == "claude":
return HOME / ".claude-profiles/profiles" / account.name
return HOME / ".claude-profiles/profiles" / (account.profile_path or account.name)
if account.provider == "chatgpt":
current = HOME / ".chatgpt-profiles/profiles" / account.name
current = HOME / ".chatgpt-profiles/profiles" / (
account.profile_path or account.name
)
legacy = HOME / ".openai-profiles/profiles" / account.name
return legacy if legacy.exists() and not current.exists() else current
if account.provider == "grok":
return HOME / ".grok-profiles/profiles" / account.name
return HOME / ".grok-profiles/profiles" / (
account.profile_path or account.name
)
if account.provider == "gemini":
return HOME / ".gemini-profiles/profiles" / account.name
return HOME / ".gemini-profiles/profiles" / (
account.profile_path or account.name
)
if account.provider == "glm":
return HOME / ".glm-profiles/profiles" / account.name
return HOME / ".glm-profiles/profiles" / (
account.profile_path or account.name
)
return None


Expand Down
112 changes: 112 additions & 0 deletions apps/credential-manager/test_claude_nested_profiles.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import json
import os
import pathlib
import subprocess
import tempfile
import unittest


REPO = pathlib.Path(__file__).parents[2]


class ClaudeNestedProfilesTest(unittest.TestCase):
def test_setup_copies_flat_profile_and_launcher_uses_nested_path(self):
with tempfile.TemporaryDirectory(dir="/tmp") as temporary:
root = pathlib.Path(temporary)
home = root / "home"
config = home / ".config/workbenches"
config.mkdir(parents=True)
manifest = config / "claude-profiles.json"
manifest.write_text(
json.dumps(
{
"version": 1,
"profiles": [
{
"name": "team-001",
"profilePath": "example-company/team/team-001",
"email": "team-001@example.com",
"family": "example-company",
"aliases": ["team001"],
}
],
}
)
)

flat = home / ".claude-profiles/profiles/team-001"
flat.mkdir(parents=True)
credential = {"testCredential": "preserved"}
(flat / ".credentials.json").write_text(json.dumps(credential))
(flat / ".credentials.json").chmod(0o600)

env = {
**os.environ,
"HOME": str(home),
"XDG_CONFIG_HOME": str(home / ".config"),
"CLAUDE_PROFILES_HOME": str(home / ".claude-profiles"),
}
subprocess.run(
[str(REPO / "scripts/setup-claude-profiles.sh"), "--manifest", str(manifest)],
env=env,
check=True,
capture_output=True,
text=True,
)

nested = home / ".claude-profiles/profiles/example-company/team/team-001"
self.assertTrue(flat.is_dir())
self.assertTrue(nested.is_dir())
self.assertEqual(json.loads((nested / ".credentials.json").read_text()), credential)
self.assertEqual(
json.loads((nested / ".profile.json").read_text())["profilePath"],
"example-company/team/team-001",
)
self.assertEqual(
(nested / "projects").resolve(),
home / ".claude-profiles/state/example-company/projects",
)
for category in ("team", "max", "xfactor"):
self.assertTrue(
(home / f".claude-profiles/profiles/example-company/{category}").is_dir()
)

retained_group = home / ".claude-profiles/profiles/team/team-001"
retained_group.mkdir(parents=True)
(retained_group / ".profile.json").write_text(
json.dumps(
{
"name": "team-001",
"profilePath": "team/team-001",
"email": "team-001@example.com",
"family": "example-company",
"aliases": ["team001"],
}
)
)

capture = root / "capture"
fake_claude = root / "claude"
fake_claude.write_text(
'#!/bin/sh\nprintf "%s" "$CLAUDE_CONFIG_DIR" > "$CAPTURE"\n'
)
fake_claude.chmod(0o755)
env.update({"CLAUDE_BIN": str(fake_claude), "CAPTURE": str(capture)})
subprocess.run(
[str(REPO / "base-image/files/claude-profile"), "status", "team001"],
env=env,
check=True,
)
self.assertEqual(capture.read_text(), str(nested))

env["CLAUDE_PROFILES_MANIFEST"] = str(root / "missing-manifest.json")
subprocess.run(
[str(REPO / "base-image/files/claude-profile"), "status", "team001"],
env=env,
check=True,
)
self.assertEqual(capture.read_text(), str(nested))


if __name__ == "__main__":
unittest.main()
43 changes: 37 additions & 6 deletions apps/credential-manager/test_generic_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,34 @@ def test_legacy_provider_names_are_normalized(self):
self.assertEqual(MANAGER.canonical_provider("gemini"), "gemini")
self.assertEqual(MANAGER.canonical_provider("zai"), "glm")

def test_profile_home_uses_nested_manifest_path_for_managed_providers(self):
original_home = MANAGER.HOME
try:
with tempfile.TemporaryDirectory() as directory:
MANAGER.HOME = Path(directory)
cases = {
"claude": ".claude-profiles",
"chatgpt": ".chatgpt-profiles",
"grok": ".grok-profiles",
"gemini": ".gemini-profiles",
"glm": ".glm-profiles",
}
for provider, root in cases.items():
account = MANAGER.Account(
provider=provider,
name="team-001",
email="user@company.example",
profile_path="opensoft/team/team-001",
)
self.assertEqual(
MANAGER.profile_home(account),
Path(directory)
/ root
/ "profiles/opensoft/team/team-001",
)
finally:
MANAGER.HOME = original_home

def test_direct_workstation_config_directory_is_supported(self):
with tempfile.TemporaryDirectory() as directory:
config = Path(directory)
Expand Down Expand Up @@ -84,15 +112,15 @@ def test_claude_example_provides_work_and_personal_profiles(self):
(REPO / "config/claude-profiles.example.json").read_text()
)
profiles = {(profile["name"], profile["family"]) for profile in data["profiles"]}
self.assertIn(("work", "work"), profiles)
self.assertIn(("work", "company"), profiles)
self.assertIn(("personal", "personal"), profiles)

def test_codex_example_provides_work_and_personal_profiles(self):
data = json.loads(
(REPO / "config/openai-profiles.example.json").read_text()
)
profiles = {(profile["name"], profile["family"]) for profile in data["profiles"]}
self.assertIn(("work-chatgpt-1", "work"), profiles)
self.assertIn(("work-chatgpt-1", "company"), profiles)
self.assertIn(("personal-chatgpt-1", "personal"), profiles)

def test_codex_profile_setup_and_alias_isolate_codex_home(self):
Expand All @@ -113,7 +141,8 @@ def test_codex_profile_setup_and_alias_isolate_codex_home(self):
"version": 1,
"profiles": [{
"name": "work-chatgpt-1",
"family": "work",
"profilePath": "example-company/team/work-chatgpt-1",
"family": "example-company",
"email": "user@company.example",
"aliases": ["work1"],
}],
Expand Down Expand Up @@ -149,7 +178,7 @@ def test_codex_profile_setup_and_alias_isolate_codex_home(self):
text=True,
)

profile = home / ".chatgpt-profiles/profiles/work-chatgpt-1"
profile = home / ".chatgpt-profiles/profiles/example-company/team/work-chatgpt-1"
profile_config = (profile / "config.toml").read_text()
self.assertEqual((capture / "home").read_text().strip(), str(profile))
self.assertIn('forced_login_method = "chatgpt"', profile_config)
Expand All @@ -160,6 +189,10 @@ def test_codex_profile_setup_and_alias_isolate_codex_home(self):
)
self.assertEqual(profile.stat().st_mode & 0o777, 0o700)
self.assertEqual((profile / ".profile.json").stat().st_mode & 0o777, 0o600)
for category in ("team", "max", "xfactor"):
self.assertTrue(
(home / f".chatgpt-profiles/profiles/example-company/{category}").is_dir()
)

def test_public_account_surface_has_no_private_identifiers(self):
targets = [
Expand All @@ -179,9 +212,7 @@ def test_public_account_surface_has_no_private_identifiers(self):
]
forbidden = (
"br" + "ett",
"open" + "soft",
"far" + "heap",
"med" + "x",
)
violations: list[str] = []
for target in targets:
Expand Down
Loading