ccprofiles-core 0.8.1 → 0.9.0
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/dist/adopt.d.ts +4 -0
- package/dist/adopt.js +7 -0
- package/dist/plugins.d.ts +24 -0
- package/dist/plugins.js +72 -1
- package/package.json +1 -1
package/dist/adopt.d.ts
CHANGED
|
@@ -8,5 +8,9 @@ export declare function buildManifest(live: LiveProfile[], platform: Platform, s
|
|
|
8
8
|
* re-leak a previously migrated token as plaintext. For each profile that existed before,
|
|
9
9
|
* if the old manifest had a secret:// ref whose resolved value matches the newly-discovered
|
|
10
10
|
* plaintext value, restore the ref instead of the plaintext.
|
|
11
|
+
*
|
|
12
|
+
* Also carries forward declared-only fields (skipPermissions, launcher env) that live
|
|
13
|
+
* state cannot represent — buildManifest resets them to defaults, so without this every
|
|
14
|
+
* snapshot/adopt would silently turn them off.
|
|
11
15
|
*/
|
|
12
16
|
export declare function preserveSecretRefs(newM: Manifest, oldM: Manifest, getSecret: (name: string) => Promise<string | null>): Promise<void>;
|
package/dist/adopt.js
CHANGED
|
@@ -67,12 +67,19 @@ export function buildManifest(live, platform, sharedRoot = join(platform.home, '
|
|
|
67
67
|
* re-leak a previously migrated token as plaintext. For each profile that existed before,
|
|
68
68
|
* if the old manifest had a secret:// ref whose resolved value matches the newly-discovered
|
|
69
69
|
* plaintext value, restore the ref instead of the plaintext.
|
|
70
|
+
*
|
|
71
|
+
* Also carries forward declared-only fields (skipPermissions, launcher env) that live
|
|
72
|
+
* state cannot represent — buildManifest resets them to defaults, so without this every
|
|
73
|
+
* snapshot/adopt would silently turn them off.
|
|
70
74
|
*/
|
|
71
75
|
export async function preserveSecretRefs(newM, oldM, getSecret) {
|
|
72
76
|
for (const pr of newM.profiles) {
|
|
73
77
|
const old = oldM.profiles.find(p => p.name === pr.name);
|
|
74
78
|
if (!old)
|
|
75
79
|
continue;
|
|
80
|
+
pr.skipPermissions = old.skipPermissions;
|
|
81
|
+
if (Object.keys(pr.env).length === 0)
|
|
82
|
+
pr.env = old.env;
|
|
76
83
|
for (const [k, v] of Object.entries(old.settingsEnv ?? {})) {
|
|
77
84
|
if (!v.startsWith('secret://'))
|
|
78
85
|
continue;
|
package/dist/plugins.d.ts
CHANGED
|
@@ -50,5 +50,29 @@ export declare function reconcileProfilePlugins(opts: {
|
|
|
50
50
|
/** ids drifting across profiles — update them here so every profile lands on the same latest. */
|
|
51
51
|
updateIds?: string[];
|
|
52
52
|
}): Promise<string[]>;
|
|
53
|
+
/**
|
|
54
|
+
* Existing claude-mem cache dirs under a profile (`plugins/cache/<owner>/claude-mem`, any owner).
|
|
55
|
+
* Disk is the ground truth on purpose: a PROJECT-scope claude-mem install is invisible to
|
|
56
|
+
* installed_plugins.json's user-scope view (discovery's installedPlugins misses it), yet it still
|
|
57
|
+
* leaks the same way — `.claude-staffinc` was exactly this case on 2026-07-23. Gating the prune on
|
|
58
|
+
* install bookkeeping would skip the one profile that needed it most.
|
|
59
|
+
*/
|
|
60
|
+
export declare function claudeMemCacheDirs(configDir: string): Promise<string[]>;
|
|
61
|
+
/**
|
|
62
|
+
* Keep only the highest-semver version dir under `cacheDir`, delete the rest. Returns removed names.
|
|
63
|
+
*
|
|
64
|
+
* Why this is the real fix for the claude-mem leak: the plugin's session-init hook resolves which
|
|
65
|
+
* worker to launch with `ls -dt <cache>/[0-9]*` — the newest dir by MTIME — and ignores the pinned
|
|
66
|
+
* version in installed_plugins.json entirely. So a leftover OLD version dir with a fresher mtime is
|
|
67
|
+
* launched instead of the pinned latest, reviving the shared-`~/.claude-mem`-worker fight even when
|
|
68
|
+
* every profile's pin is already correct. On 2026-07-23 this ran `.claude-data-plb` (pinned 13.12.1)
|
|
69
|
+
* up to 187 procs / 20GB swap, all from a stale 13.11.0 dir. Version *parity* (planPluginVersionDrift
|
|
70
|
+
* + `clp fix`) is necessary but not sufficient; the ambiguity only goes away when one dir remains.
|
|
71
|
+
*
|
|
72
|
+
* "Keep highest semver" == keep the intended active version, because `claude plugin update` only ever
|
|
73
|
+
* moves to the marketplace's latest — there is no way to pin an older one. Non-numeric dirs
|
|
74
|
+
* (git-sha/"unknown" caches of versionless plugins) are left untouched.
|
|
75
|
+
*/
|
|
76
|
+
export declare function pruneStaleVersionDirs(cacheDir: string): Promise<string[]>;
|
|
53
77
|
/** If a profile's plugins/ is a legacy symlink into the old shared pool, restore it to a real dir. */
|
|
54
78
|
export declare function restoreLegacyPluginSymlink(pluginsDir: string): Promise<boolean>;
|
package/dist/plugins.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { cp, lstat, mkdir, readlink, unlink } from 'node:fs/promises';
|
|
1
|
+
import { cp, lstat, mkdir, readdir, readlink, rm, unlink } from 'node:fs/promises';
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
3
4
|
export function marketplaceOf(id) {
|
|
4
5
|
const at = id.lastIndexOf('@');
|
|
5
6
|
return at > 0 ? id.slice(at + 1) : null;
|
|
@@ -72,6 +73,76 @@ export async function reconcileProfilePlugins(opts) {
|
|
|
72
73
|
}
|
|
73
74
|
return log;
|
|
74
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Existing claude-mem cache dirs under a profile (`plugins/cache/<owner>/claude-mem`, any owner).
|
|
78
|
+
* Disk is the ground truth on purpose: a PROJECT-scope claude-mem install is invisible to
|
|
79
|
+
* installed_plugins.json's user-scope view (discovery's installedPlugins misses it), yet it still
|
|
80
|
+
* leaks the same way — `.claude-staffinc` was exactly this case on 2026-07-23. Gating the prune on
|
|
81
|
+
* install bookkeeping would skip the one profile that needed it most.
|
|
82
|
+
*/
|
|
83
|
+
export async function claudeMemCacheDirs(configDir) {
|
|
84
|
+
const cacheRoot = join(configDir, 'plugins', 'cache');
|
|
85
|
+
let owners;
|
|
86
|
+
try {
|
|
87
|
+
owners = await readdir(cacheRoot);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
const out = [];
|
|
93
|
+
for (const owner of owners) {
|
|
94
|
+
const d = join(cacheRoot, owner, 'claude-mem');
|
|
95
|
+
if (existsSync(d))
|
|
96
|
+
out.push(d);
|
|
97
|
+
}
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
100
|
+
/** Compare dotted numeric version names ("13.9.2" < "13.11.0"). Missing parts count as 0. */
|
|
101
|
+
function compareVersionDir(a, b) {
|
|
102
|
+
const pa = a.split('.'), pb = b.split('.');
|
|
103
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
104
|
+
const x = Number(pa[i] ?? 0), y = Number(pb[i] ?? 0);
|
|
105
|
+
if (x !== y)
|
|
106
|
+
return x - y;
|
|
107
|
+
}
|
|
108
|
+
return 0;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Keep only the highest-semver version dir under `cacheDir`, delete the rest. Returns removed names.
|
|
112
|
+
*
|
|
113
|
+
* Why this is the real fix for the claude-mem leak: the plugin's session-init hook resolves which
|
|
114
|
+
* worker to launch with `ls -dt <cache>/[0-9]*` — the newest dir by MTIME — and ignores the pinned
|
|
115
|
+
* version in installed_plugins.json entirely. So a leftover OLD version dir with a fresher mtime is
|
|
116
|
+
* launched instead of the pinned latest, reviving the shared-`~/.claude-mem`-worker fight even when
|
|
117
|
+
* every profile's pin is already correct. On 2026-07-23 this ran `.claude-data-plb` (pinned 13.12.1)
|
|
118
|
+
* up to 187 procs / 20GB swap, all from a stale 13.11.0 dir. Version *parity* (planPluginVersionDrift
|
|
119
|
+
* + `clp fix`) is necessary but not sufficient; the ambiguity only goes away when one dir remains.
|
|
120
|
+
*
|
|
121
|
+
* "Keep highest semver" == keep the intended active version, because `claude plugin update` only ever
|
|
122
|
+
* moves to the marketplace's latest — there is no way to pin an older one. Non-numeric dirs
|
|
123
|
+
* (git-sha/"unknown" caches of versionless plugins) are left untouched.
|
|
124
|
+
*/
|
|
125
|
+
export async function pruneStaleVersionDirs(cacheDir) {
|
|
126
|
+
let names;
|
|
127
|
+
try {
|
|
128
|
+
names = await readdir(cacheDir);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return [];
|
|
132
|
+
}
|
|
133
|
+
const versions = names.filter(n => /^\d/.test(n)).sort(compareVersionDir);
|
|
134
|
+
if (versions.length <= 1)
|
|
135
|
+
return [];
|
|
136
|
+
const keep = versions[versions.length - 1];
|
|
137
|
+
const removed = [];
|
|
138
|
+
for (const v of versions) {
|
|
139
|
+
if (v === keep)
|
|
140
|
+
continue;
|
|
141
|
+
await rm(join(cacheDir, v), { recursive: true, force: true });
|
|
142
|
+
removed.push(v);
|
|
143
|
+
}
|
|
144
|
+
return removed;
|
|
145
|
+
}
|
|
75
146
|
/** If a profile's plugins/ is a legacy symlink into the old shared pool, restore it to a real dir. */
|
|
76
147
|
export async function restoreLegacyPluginSymlink(pluginsDir) {
|
|
77
148
|
let st = null;
|
package/package.json
CHANGED