@bordoni/claudep 0.4.0 → 0.4.1
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/CHANGELOG.md +17 -1
- package/README.md +5 -1
- package/claudep.ts +85 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,21 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.4.1] - 2026-10-01
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- `claudep doctor` warns when your Claude Code asks for approval on every auto-memory write in a profile. Since 2.1.280 a write through the shared `projects/` symlink is judged at `~/.claude/projects`, which auto mode cannot approve ([anthropics/claude-code#98044](https://github.com/anthropics/claude-code/issues/98044)). The README explains it.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- Caught up with Claude Code 2.1.280, whose list of per-instance files grew. `state/`, `policy-limits.json.stamp.json`, the `.signature.json` files next to `policy-limits.json` and `remote-settings.json`, `daemon.lock`, `shares/`, `storage-v2/` and about twenty-five caches and locks are known-private, so `doctor` stops calling them unclassified.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- `loop.md`, which Claude Code keeps per instance, is no longer shared by the rule that links every top-level `*.md`. `claudep doctor` names any `loop.md` symlink an older claudep left in a profile and tells you to remove it.
|
|
22
|
+
- `claudep doctor` on Linux and Windows no longer calls a profile "not logged in yet" when it has no `.credentials.json` but Claude Code reports it logged in. Claude Code can keep the login elsewhere, such as Windows Credential Manager behind a feature flag.
|
|
23
|
+
|
|
9
24
|
## [0.4.0] - 2026-09-14
|
|
10
25
|
|
|
11
26
|
### Added
|
|
@@ -86,7 +101,8 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
|
|
|
86
101
|
- Keychain isolation per config dir verified against Claude Code 2.1.259: the service name is `Claude Code-credentials-<sha256(dir)[0:8]>`.
|
|
87
102
|
- Test suite with `bun test`, a sandboxed `$HOME`, a fake `claude` and `security` on PATH, and a real-shell test for the hook. CI runs typecheck, lint and tests on macOS and Linux.
|
|
88
103
|
|
|
89
|
-
[Unreleased]: https://github.com/bordoni/claudep/compare/0.4.
|
|
104
|
+
[Unreleased]: https://github.com/bordoni/claudep/compare/0.4.1...HEAD
|
|
105
|
+
[0.4.1]: https://github.com/bordoni/claudep/compare/0.4.0...0.4.1
|
|
90
106
|
[0.4.0]: https://github.com/bordoni/claudep/compare/0.3.0...0.4.0
|
|
91
107
|
[0.3.0]: https://github.com/bordoni/claudep/compare/0.2.1...0.3.0
|
|
92
108
|
[0.2.1]: https://github.com/bordoni/claudep/compare/0.2.0...0.2.1
|
package/README.md
CHANGED
|
@@ -155,7 +155,11 @@ The shared list is an allowlist, so an account-specific file cannot leak across
|
|
|
155
155
|
|
|
156
156
|
- The first time you open a repository under a new profile you will re-accept folder trust, and claude.ai connectors need their OAuth redone in that profile.
|
|
157
157
|
- Two profiles running at the same time write the same `settings.json` and `plugins/`. That is the same situation as two terminals today.
|
|
158
|
-
- Background sessions and the daemon are tied to `~/.claude`. As of Claude Code 2.1.
|
|
158
|
+
- Background sessions and the daemon are tied to `~/.claude`. As of Claude Code 2.1.280, `claude daemon install` refuses to run with `CLAUDE_CONFIG_DIR` set, and the launcher skips the daemon for `claude --bg` under a profile. Anthropic's docs describe a separate background supervisor per config dir; I have not verified that here.
|
|
159
|
+
- Since Claude Code 2.1.280, every auto-memory write in a profile asks for approval, and auto mode cannot give it. The memory directory sits under the shared `projects/` symlink, and Claude Code now checks a write at the path it lands on, `~/.claude/projects/...`, which it treats as sensitive. This is [anthropics/claude-code#98044](https://github.com/anthropics/claude-code/issues/98044), open upstream. `claudep doctor` warns while your Claude Code is affected. The default profile is not.
|
|
160
|
+
- Skills and plugins synced from claude.ai land in the shared `skills/synced/` and `plugins/synced/`, in one folder per account, so profiles do not mix them. The `syncClaudeAiSkills` and `syncClaudeAiPlugins` switches live in the shared `settings.json` and apply to every profile.
|
|
161
|
+
- Console sign-ins made without an API key live in `~/.config/anthropic`, outside every config dir, so all profiles see the same ones. `ANTHROPIC_PROFILE` picks which one Claude Code uses.
|
|
162
|
+
- In VS Code, `CLAUDE_CONFIG_DIR` in `claudeCode.environmentVariables` must be an absolute path. The session list ignores it when `claudeProcessWrapper` is set.
|
|
159
163
|
- Set `CLAUDE_PROFILES_DIR` to move the profiles root. Keep it out of iCloud or Dropbox; `.claude.json` is rewritten constantly and sync tools create conflict copies.
|
|
160
164
|
- Never put `CLAUDE_CONFIG_DIR` in a `settings.json` `env` block. Claude Code detects that mismatch and disables features; `claudep doctor` reports it.
|
|
161
165
|
- `ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN` and `CLAUDE_CODE_OAUTH_TOKEN` override the login in any config dir, always in `-p` mode. `claudep <name>` and `claudep doctor` warn when one is set and leave it alone.
|
package/claudep.ts
CHANGED
|
@@ -250,8 +250,9 @@ export const SHARED_DIRS = [
|
|
|
250
250
|
"workflows",
|
|
251
251
|
] as const;
|
|
252
252
|
|
|
253
|
-
/** Per-profile state. First block is Claude Code's own runtime-state list
|
|
254
|
-
*
|
|
253
|
+
/** Per-profile state. First block is Claude Code's own runtime-state list as of
|
|
254
|
+
* 2.1.263, then observed extras, then what 2.1.280 added to that list. Never
|
|
255
|
+
* shared, and a name here is never shared by the top-level *.md rule either. */
|
|
255
256
|
export const KNOWN_PRIVATE = new Set<string>([
|
|
256
257
|
".claude.json",
|
|
257
258
|
".claude.json.backup",
|
|
@@ -301,6 +302,42 @@ export const KNOWN_PRIVATE = new Set<string>([
|
|
|
301
302
|
"launch.json",
|
|
302
303
|
"scheduled_tasks.json",
|
|
303
304
|
"seed-admin",
|
|
305
|
+
// Added to Claude Code's runtime-state list by 2.1.280 (checked 2026-09-30).
|
|
306
|
+
"state",
|
|
307
|
+
"policy-limits.json.stamp.json",
|
|
308
|
+
"policy-limits.json.signature.json",
|
|
309
|
+
"policy-limits.json.signature-iat.json",
|
|
310
|
+
"remote-settings.json.signature.json",
|
|
311
|
+
"remote-settings.json.signature-iat.json",
|
|
312
|
+
"remote-settings-consent.json",
|
|
313
|
+
"remote-settings-helper-consent",
|
|
314
|
+
"hfi-auth.json",
|
|
315
|
+
"shares",
|
|
316
|
+
"storage-v2",
|
|
317
|
+
"daemon.lock",
|
|
318
|
+
"gh-pr-status-cache.json",
|
|
319
|
+
"active-time.json",
|
|
320
|
+
"server-sessions.json",
|
|
321
|
+
"image-cache",
|
|
322
|
+
"file-transfers",
|
|
323
|
+
"computer-use.lock",
|
|
324
|
+
"server.lock",
|
|
325
|
+
"downloads",
|
|
326
|
+
"scratch",
|
|
327
|
+
"traces",
|
|
328
|
+
"startup-perf",
|
|
329
|
+
"feedback-bundles",
|
|
330
|
+
"ccr",
|
|
331
|
+
"bridge-spawn",
|
|
332
|
+
"local-settings",
|
|
333
|
+
"project-settings",
|
|
334
|
+
"remote",
|
|
335
|
+
"systemd",
|
|
336
|
+
"api-dumps",
|
|
337
|
+
"dump-prompts",
|
|
338
|
+
"antproto.json",
|
|
339
|
+
".cc-writes",
|
|
340
|
+
"loop.md",
|
|
304
341
|
]);
|
|
305
342
|
|
|
306
343
|
/** Keys copied from the base .claude.json into a fresh profile so first-run
|
|
@@ -462,7 +499,8 @@ export function sharedItems(base: string): SharedItem[] {
|
|
|
462
499
|
for (const f of SHARED_FILES) push(f, "file");
|
|
463
500
|
if (existsSync(base)) {
|
|
464
501
|
for (const entry of readdirSync(base, { withFileTypes: true })) {
|
|
465
|
-
if (entry.name.endsWith(".md") && !entry.isDirectory()
|
|
502
|
+
if (entry.name.endsWith(".md") && !entry.isDirectory() && !KNOWN_PRIVATE.has(entry.name))
|
|
503
|
+
push(entry.name, "file");
|
|
466
504
|
}
|
|
467
505
|
}
|
|
468
506
|
for (const d of SHARED_DIRS) push(d, "dir");
|
|
@@ -557,6 +595,18 @@ export function linkState(
|
|
|
557
595
|
return existsSync(dest) ? "ok" : "broken";
|
|
558
596
|
}
|
|
559
597
|
|
|
598
|
+
/** Known-private names in a profile that are still symlinks into the base.
|
|
599
|
+
* An older claudep linked every top-level *.md, which once included loop.md;
|
|
600
|
+
* link() never deletes, so doctor names them for the user to remove. */
|
|
601
|
+
export function staleSharedLinks(base: string, dir: string, platform: NodeJS.Platform = process.platform): string[] {
|
|
602
|
+
if (!existsSync(dir)) return [];
|
|
603
|
+
return readdirSync(dir, { withFileTypes: true })
|
|
604
|
+
.filter((e) => e.isSymbolicLink() && KNOWN_PRIVATE.has(e.name))
|
|
605
|
+
.filter((e) => samePath(readlinkSync(join(dir, e.name)), join(base, e.name), platform))
|
|
606
|
+
.map((e) => e.name)
|
|
607
|
+
.sort();
|
|
608
|
+
}
|
|
609
|
+
|
|
560
610
|
/** What to tell the user when the OS refused to create a symlink. */
|
|
561
611
|
export function symlinkDeniedHint(platform: NodeJS.Platform, name: string): string {
|
|
562
612
|
if (platform === "win32")
|
|
@@ -598,8 +648,23 @@ export function authEnvOverrides(env: Env = process.env, platform: NodeJS.Platfo
|
|
|
598
648
|
* dir. Before it every config dir shared one login. */
|
|
599
649
|
export const MIN_CLAUDE_VERSION = "2.1.144";
|
|
600
650
|
|
|
651
|
+
/** From this Claude Code on, a write through a symlink is also judged at the
|
|
652
|
+
* path it lands on. Auto memory in a profile whose projects/ is shared lands
|
|
653
|
+
* in ~/.claude/projects and asks for approval on every write, which auto mode
|
|
654
|
+
* cannot give (anthropics/claude-code#98044). No fixed version yet. */
|
|
655
|
+
export const MEMORY_SYMLINK_PROMPT_FROM = "2.1.280";
|
|
656
|
+
export const MEMORY_SYMLINK_ISSUE = "https://github.com/anthropics/claude-code/issues/98044";
|
|
657
|
+
|
|
601
658
|
export type Version = [number, number, number];
|
|
602
659
|
|
|
660
|
+
/** True when this Claude Code prompts on every auto-memory write through the
|
|
661
|
+
* shared projects/ symlink. An unknown version is not reported. */
|
|
662
|
+
export function memoryWritesPrompt(v: Version | undefined, shared: readonly SharedItem[]): boolean {
|
|
663
|
+
const from = parseVersion(MEMORY_SYMLINK_PROMPT_FROM);
|
|
664
|
+
if (!v || !from || versionBelow(v, from)) return false;
|
|
665
|
+
return shared.some((s) => s.name === "projects");
|
|
666
|
+
}
|
|
667
|
+
|
|
603
668
|
/** The first x.y.z in `claude --version` output. */
|
|
604
669
|
export function parseVersion(text: string): Version | undefined {
|
|
605
670
|
const m = /(\d+)\.(\d+)\.(\d+)/.exec(text);
|
|
@@ -1672,6 +1737,7 @@ function msysConfigDirWarning(L: Layout, env: Env = process.env): string | undef
|
|
|
1672
1737
|
async function cmdDoctor(L: Layout, args: string[]): Promise<void> {
|
|
1673
1738
|
const names = args[0] ? [args[0]] : listProfileNames(L);
|
|
1674
1739
|
let problems = 0;
|
|
1740
|
+
let v: Version | undefined;
|
|
1675
1741
|
const launch = findClaude();
|
|
1676
1742
|
if (launch && launch.kind !== "ps1") {
|
|
1677
1743
|
const { out } = await captureClaude(L, undefined, ["--version"]);
|
|
@@ -1680,7 +1746,7 @@ async function cmdDoctor(L: Layout, args: string[]): Promise<void> {
|
|
|
1680
1746
|
console.log(
|
|
1681
1747
|
`${c.dim("·")} claude is the npm cmd shim and runs through cmd.exe; the native installer's claude.exe avoids that hop`,
|
|
1682
1748
|
);
|
|
1683
|
-
|
|
1749
|
+
v = parseVersion(out);
|
|
1684
1750
|
const floor = parseVersion(MIN_CLAUDE_VERSION);
|
|
1685
1751
|
if (L.platform === "darwin" && v && floor && versionBelow(v, floor)) {
|
|
1686
1752
|
bad(
|
|
@@ -1715,6 +1781,10 @@ async function cmdDoctor(L: Layout, args: string[]): Promise<void> {
|
|
|
1715
1781
|
if (unclassified.length) {
|
|
1716
1782
|
warn(`base items neither shared nor known-private (they stay per-profile): ${unclassified.join(", ")}`);
|
|
1717
1783
|
}
|
|
1784
|
+
if (memoryWritesPrompt(v, shared))
|
|
1785
|
+
warn(
|
|
1786
|
+
`Claude Code ${v?.join(".")} asks for approval on every auto-memory write in a profile, because projects/ is shared through a symlink; auto mode cannot approve it. Known upstream bug: ${MEMORY_SYMLINK_ISSUE}`,
|
|
1787
|
+
);
|
|
1718
1788
|
|
|
1719
1789
|
for (const name of names) {
|
|
1720
1790
|
const dir = profileDir(L, name);
|
|
@@ -1744,13 +1814,23 @@ async function cmdDoctor(L: Layout, args: string[]): Promise<void> {
|
|
|
1744
1814
|
ok(`${shared.length} shared item(s) checked`);
|
|
1745
1815
|
const strays = readdirSync(dir).filter((n) => !sharedNames.has(n) && !KNOWN_PRIVATE.has(n) && !n.endsWith(".md"));
|
|
1746
1816
|
if (strays.length) warn(`unexpected private items: ${strays.join(", ")}`);
|
|
1817
|
+
for (const n of staleSharedLinks(L.base, dir, L.platform)) {
|
|
1818
|
+
bad(
|
|
1819
|
+
`${n}: linked to the base by an older claudep, but it is per-profile state now. Remove the symlink: ${join(dir, n)}`,
|
|
1820
|
+
);
|
|
1821
|
+
problems++;
|
|
1822
|
+
}
|
|
1823
|
+
const s = await authStatus(L, dir);
|
|
1747
1824
|
if (L.platform === "darwin") {
|
|
1748
1825
|
const svc = keychainService(dir);
|
|
1749
1826
|
if (await keychainHas(svc)) ok(`keychain item "${svc}" present`);
|
|
1750
1827
|
else warn(`no keychain item "${svc}". Not logged in yet (claudep ${name} auth login)`);
|
|
1751
1828
|
} else if (credentialsFileHas(dir)) ok(".credentials.json present");
|
|
1829
|
+
else if (s.loggedIn)
|
|
1830
|
+
console.log(
|
|
1831
|
+
`${c.dim("·")} no .credentials.json in the profile; Claude Code keeps this login elsewhere (on Windows, possibly Credential Manager)`,
|
|
1832
|
+
);
|
|
1752
1833
|
else warn(`no .credentials.json in the profile. Not logged in yet (claudep ${name} auth login)`);
|
|
1753
|
-
const s = await authStatus(L, dir);
|
|
1754
1834
|
if (s.loggedIn) ok(`logged in as ${s.email ?? "?"} (${s.orgName ?? "?"}, ${s.subscriptionType ?? "?"})`);
|
|
1755
1835
|
else warn("not logged in");
|
|
1756
1836
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bordoni/claudep",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Unofficial, not affiliated with Anthropic. Run Claude Code under separate accounts on one machine (work + personal) with isolated logins and shared config.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|