@cprof/cli 0.0.1-alpha.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vedant Nandoskar
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,72 @@
1
+ # cprof
2
+
3
+ [![npm](https://img.shields.io/npm/v/@cprof/cli/alpha?label=npm%40alpha)](https://www.npmjs.com/package/@cprof/cli)
4
+ [![license: MIT](https://img.shields.io/badge/license-MIT-blue)](https://github.com/Vedant1202/claude-prof/blob/main/LICENSE)
5
+
6
+ Snapshot, scrub, and migrate your Claude Code setup as a redacted, portable profile.
7
+
8
+ > **Alpha.** cprof is early and the profile format may still change. Redaction is
9
+ > best-effort — always review a generated profile before sharing it.
10
+
11
+ `cprof` captures your project or global Claude Code configuration — settings, MCP
12
+ servers, `CLAUDE.md` memory, rules, skills, commands, agents, and plugin
13
+ inventory — into a deterministic, schema-valid, **secret-redacted**
14
+ `claude-profile.json`. You can then `diff`, `validate`, and `install` a trusted
15
+ profile onto another machine with a non-destructive deep merge. It runs fully
16
+ offline and never executes hook or plugin code.
17
+
18
+ ## Install
19
+
20
+ ```bash
21
+ npm install -g @cprof/cli@alpha
22
+ # or run it without installing:
23
+ npx @cprof/cli@alpha --help
24
+ ```
25
+
26
+ The npm package is **`@cprof/cli`**; the installed command is **`cprof`**.
27
+ Requires Node.js >= 22.
28
+
29
+ ## Quickstart
30
+
31
+ ```bash
32
+ # 1. Snapshot the current project into claude-profile.json
33
+ cprof init
34
+
35
+ # 2. Review what was captured — and confirm secrets are redacted
36
+ cat claude-profile.json
37
+
38
+ # 3. On another machine, apply it with a non-destructive deep merge
39
+ cprof install claude-profile.json --dry-run # preview the write plan
40
+ cprof install claude-profile.json # apply it
41
+ ```
42
+
43
+ ## Commands
44
+
45
+ | Command | What it does |
46
+ | -------------------------------------------------- | --------------------------------------------------------------- |
47
+ | `cprof init [--global \| --include-global]` | Snapshot the current setup into `claude-profile.json` |
48
+ | `cprof refresh` | Rebuild the profile from its recorded source scope |
49
+ | `cprof install <file> [--dry-run] [--force] [...]` | Apply a trusted profile (deep merge; backs up before overwrite) |
50
+ | `cprof validate <file>` | Validate a profile against the schema |
51
+ | `cprof diff <a.json> <b.json>` | Compare two profiles semantically |
52
+ | `cprof profiles list` | List profiles recorded by local installs |
53
+
54
+ Run `cprof --help` for the full usage.
55
+
56
+ ## Redaction & limits
57
+
58
+ Secrets are redacted on capture: provider keys (via
59
+ [secretlint](https://github.com/secretlint/secretlint)), secret-like key names,
60
+ JWTs, and high-entropy values become `${env:NAME}` placeholders, and the
61
+ generated manifest is re-scanned before it is written. It will **not** catch
62
+ low-entropy secrets stored under non-sensitive keys. Review every profile before
63
+ sharing it.
64
+
65
+ ## Documentation
66
+
67
+ Full docs: **https://vedant1202.github.io/claude-prof/** · Source & issues:
68
+ [github.com/Vedant1202/claude-prof](https://github.com/Vedant1202/claude-prof)
69
+
70
+ ## License
71
+
72
+ MIT
@@ -0,0 +1,12 @@
1
+ import type { CprofProfile } from "@cprof/schema";
2
+ export type CommandWriter = Pick<NodeJS.WriteStream, "write">;
3
+ export type ReadProfileFileResult = {
4
+ readonly ok: true;
5
+ readonly profile: CprofProfile;
6
+ } | {
7
+ readonly ok: false;
8
+ readonly exitCode: 1 | 2;
9
+ readonly errors: readonly string[];
10
+ };
11
+ export declare function readProfileFile(filePath: string): Promise<ReadProfileFileResult>;
12
+ //# sourceMappingURL=command-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command-utils.d.ts","sourceRoot":"","sources":["../src/command-utils.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAElD,MAAM,MAAM,aAAa,GAAG,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;AAE9D,MAAM,MAAM,qBAAqB,GAC7B;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAA;CAAE,GACrD;IACE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IACnB,QAAQ,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC,CAAC;AAEN,wBAAsB,eAAe,CACnC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,qBAAqB,CAAC,CAmChC"}
@@ -0,0 +1,38 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { validateProfile } from "@cprof/core";
3
+ export async function readProfileFile(filePath) {
4
+ let contents;
5
+ try {
6
+ contents = await readFile(filePath, "utf8");
7
+ }
8
+ catch (error) {
9
+ if (isNodeError(error) && error.code === "ENOENT") {
10
+ return {
11
+ ok: false,
12
+ exitCode: 2,
13
+ errors: [`file not found: ${filePath}`],
14
+ };
15
+ }
16
+ throw error;
17
+ }
18
+ try {
19
+ const value = JSON.parse(contents);
20
+ const validation = validateProfile(value);
21
+ if (!validation.valid) {
22
+ return { ok: false, exitCode: 1, errors: validation.errors };
23
+ }
24
+ return { ok: true, profile: value };
25
+ }
26
+ catch (error) {
27
+ return {
28
+ ok: false,
29
+ exitCode: 1,
30
+ errors: [
31
+ error instanceof Error ? error.message : "profile JSON is invalid",
32
+ ],
33
+ };
34
+ }
35
+ }
36
+ function isNodeError(error) {
37
+ return error instanceof Error && "code" in error;
38
+ }
@@ -0,0 +1,7 @@
1
+ export interface DiffCommandOptions {
2
+ readonly cwd: string;
3
+ readonly stdout: Pick<NodeJS.WriteStream, "write">;
4
+ readonly stderr: Pick<NodeJS.WriteStream, "write">;
5
+ }
6
+ export declare function runDiff(flags: readonly string[], options: DiffCommandOptions): Promise<number>;
7
+ //# sourceMappingURL=diff.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../src/commands/diff.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;IACnD,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;CACpD;AAED,wBAAsB,OAAO,CAC3B,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,MAAM,CAAC,CAoBjB"}
@@ -0,0 +1,21 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { resolve } from "node:path";
3
+ import { diffProfiles, formatProfileDiff } from "@cprof/core";
4
+ export async function runDiff(flags, options) {
5
+ const json = flags.includes("--json");
6
+ const paths = flags.filter((flag) => flag !== "--json");
7
+ if (paths.length !== 2) {
8
+ options.stderr.write("usage: cprof diff [--json] <a.json> <b.json>\n");
9
+ return 1;
10
+ }
11
+ const leftPath = paths[0];
12
+ const rightPath = paths[1];
13
+ const left = await readJson(resolve(options.cwd, leftPath));
14
+ const right = await readJson(resolve(options.cwd, rightPath));
15
+ const diff = diffProfiles(left, right);
16
+ options.stdout.write(json ? `${JSON.stringify(diff, null, 2)}\n` : formatProfileDiff(diff));
17
+ return 0;
18
+ }
19
+ async function readJson(filePath) {
20
+ return JSON.parse(await readFile(filePath, "utf8"));
21
+ }
@@ -0,0 +1,8 @@
1
+ export interface InitCommandOptions {
2
+ readonly cwd: string;
3
+ readonly homeDir?: string;
4
+ readonly stdout: Pick<NodeJS.WriteStream, "write">;
5
+ readonly stderr: Pick<NodeJS.WriteStream, "write">;
6
+ }
7
+ export declare function runInit(flags: readonly string[], options: InitCommandOptions): Promise<number>;
8
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;IACnD,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;CACpD;AAED,wBAAsB,OAAO,CAC3B,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,MAAM,CAAC,CA0DjB"}
@@ -0,0 +1,66 @@
1
+ import { writeFile } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import { basename, join, resolve } from "node:path";
4
+ import { createProfileGitignore, createScanReport, scanClaudeProfile, validateProfile, } from "@cprof/core";
5
+ export async function runInit(flags, options) {
6
+ const parsed = parseInitFlags(flags);
7
+ if (parsed.valid === false) {
8
+ options.stderr.write(`${parsed.error}\n`);
9
+ return 1;
10
+ }
11
+ const scan = await scanClaudeProfile({
12
+ name: createProfileName(options.cwd, parsed.mode, parsed.includeGlobal),
13
+ version: "1.0.0",
14
+ cwd: options.cwd,
15
+ homeDir: options.homeDir ?? homedir(),
16
+ outputRoot: options.cwd,
17
+ mode: parsed.mode,
18
+ includeGlobal: parsed.mode === "project" ? parsed.includeGlobal : false,
19
+ });
20
+ const manifest = scan.manifest;
21
+ const validation = validateProfile(manifest);
22
+ if (!validation.valid) {
23
+ options.stderr.write(`${validation.errors.join("\n")}\n`);
24
+ return validation.exitCode;
25
+ }
26
+ if (!scan.leakCheck.ok) {
27
+ const leakedPaths = [
28
+ ...new Set(scan.leakCheck.leaks.map((leak) => leak.path)),
29
+ ];
30
+ options.stderr.write(`refusing to write: redaction left a secret in ${leakedPaths.join(", ")}\n`);
31
+ return 3;
32
+ }
33
+ await writeFile(join(options.cwd, "claude-profile.json"), `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
34
+ await writeFile(join(options.cwd, ".gitignore"), createProfileGitignore(), "utf8");
35
+ await writeFile(join(options.cwd, "cprof-scan-report.txt"), createScanReport(scan.report), "utf8");
36
+ options.stdout.write(`Wrote claude-profile.json (${manifest.profileScope}${manifest.includesGlobal ? " + global" : ""})\n`);
37
+ return 0;
38
+ }
39
+ function parseInitFlags(flags) {
40
+ const supportedFlags = new Set(["--global", "--include-global"]);
41
+ const unknownFlag = flags.find((flag) => !supportedFlags.has(flag));
42
+ if (unknownFlag !== undefined) {
43
+ return { valid: false, error: `unknown init flag: ${unknownFlag}` };
44
+ }
45
+ if (flags.includes("--global") && flags.includes("--include-global")) {
46
+ return {
47
+ valid: false,
48
+ error: "init cannot combine --global and --include-global",
49
+ };
50
+ }
51
+ if (flags.includes("--global")) {
52
+ return { valid: true, mode: "global", includeGlobal: false };
53
+ }
54
+ return {
55
+ valid: true,
56
+ mode: "project",
57
+ includeGlobal: flags.includes("--include-global"),
58
+ };
59
+ }
60
+ function createProfileName(cwd, mode, includeGlobal) {
61
+ if (mode === "global") {
62
+ return "global-profile";
63
+ }
64
+ const projectName = basename(resolve(cwd)) || "project";
65
+ return includeGlobal ? `${projectName}-with-global` : projectName;
66
+ }
@@ -0,0 +1,9 @@
1
+ export interface InstallCommandOptions {
2
+ readonly cwd: string;
3
+ readonly homeDir?: string;
4
+ readonly env?: Readonly<Record<string, string | undefined>>;
5
+ readonly stdout: Pick<NodeJS.WriteStream, "write">;
6
+ readonly stderr: Pick<NodeJS.WriteStream, "write">;
7
+ }
8
+ export declare function runInstall(flags: readonly string[], options: InstallCommandOptions): Promise<number>;
9
+ //# sourceMappingURL=install.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../src/commands/install.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IAC5D,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;IACnD,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;CACpD;AAUD,wBAAsB,UAAU,CAC9B,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,MAAM,CAAC,CA6BjB"}
@@ -0,0 +1,73 @@
1
+ import { homedir } from "node:os";
2
+ import { resolve } from "node:path";
3
+ import { installProfile } from "@cprof/core";
4
+ export async function runInstall(flags, options) {
5
+ const parsed = parseInstallFlags(flags);
6
+ if (parsed.valid === false) {
7
+ options.stderr.write(`${parsed.error}\n`);
8
+ return 1;
9
+ }
10
+ const result = await installProfile({
11
+ profilePath: resolve(options.cwd, parsed.profilePath),
12
+ cwd: options.cwd,
13
+ homeDir: options.homeDir ?? homedir(),
14
+ env: options.env,
15
+ dryRun: parsed.dryRun,
16
+ force: parsed.force,
17
+ scope: parsed.scope,
18
+ installSource: parsed.profilePath,
19
+ });
20
+ const output = result.ok ? options.stdout : options.stderr;
21
+ output.write(result.report);
22
+ if (result.ok) {
23
+ options.stdout.write(`${parsed.dryRun ? "Planned" : "Installed"} ${result.writes.length} writes\n`);
24
+ }
25
+ return result.exitCode;
26
+ }
27
+ function parseInstallFlags(flags) {
28
+ let profilePath;
29
+ let dryRun = false;
30
+ let force = false;
31
+ let scope;
32
+ for (const flag of flags) {
33
+ if (flag === "--dry-run") {
34
+ dryRun = true;
35
+ continue;
36
+ }
37
+ if (flag === "--force") {
38
+ force = true;
39
+ continue;
40
+ }
41
+ if (flag === "--global") {
42
+ scope = "global";
43
+ continue;
44
+ }
45
+ if (flag === "--include-global") {
46
+ scope = "include-global";
47
+ continue;
48
+ }
49
+ if (flag.startsWith("--")) {
50
+ return { valid: false, error: `unknown install flag: ${flag}` };
51
+ }
52
+ if (profilePath !== undefined) {
53
+ return { valid: false, error: `unexpected install argument: ${flag}` };
54
+ }
55
+ profilePath = flag;
56
+ }
57
+ if (profilePath === undefined) {
58
+ return { valid: false, error: "install requires a profile path" };
59
+ }
60
+ if (flags.includes("--global") && flags.includes("--include-global")) {
61
+ return {
62
+ valid: false,
63
+ error: "install cannot combine --global and --include-global",
64
+ };
65
+ }
66
+ return {
67
+ valid: true,
68
+ profilePath,
69
+ dryRun,
70
+ force,
71
+ scope,
72
+ };
73
+ }
@@ -0,0 +1,8 @@
1
+ export interface ProfilesCommandOptions {
2
+ readonly cwd: string;
3
+ readonly homeDir?: string;
4
+ readonly stdout: Pick<NodeJS.WriteStream, "write">;
5
+ readonly stderr: Pick<NodeJS.WriteStream, "write">;
6
+ }
7
+ export declare function runProfiles(flags: readonly string[], options: ProfilesCommandOptions): Promise<number>;
8
+ //# sourceMappingURL=profiles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profiles.d.ts","sourceRoot":"","sources":["../../src/commands/profiles.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;IACnD,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;CACpD;AAQD,wBAAsB,WAAW,CAC/B,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,MAAM,CAAC,CAcjB"}
@@ -0,0 +1,47 @@
1
+ import { homedir } from "node:os";
2
+ import { join, resolve } from "node:path";
3
+ import { loadInstalledProfileState, } from "@cprof/core";
4
+ export async function runProfiles(flags, options) {
5
+ const parsed = parseProfilesFlags(flags);
6
+ if (!parsed.valid) {
7
+ options.stderr.write(`${parsed.error}\n`);
8
+ return 1;
9
+ }
10
+ const state = await loadInstalledProfileState(statePath(options.cwd, options.homeDir ?? homedir(), parsed.global));
11
+ options.stdout.write(formatInstalls(state.installs, parsed.json));
12
+ return 0;
13
+ }
14
+ function parseProfilesFlags(flags) {
15
+ const global = flags.includes("--global");
16
+ const json = flags.includes("--json");
17
+ const positional = flags.filter((flag) => flag !== "--global" && flag !== "--json");
18
+ const unknownFlag = positional.find((flag) => flag.startsWith("--"));
19
+ if (unknownFlag !== undefined) {
20
+ return { valid: false, error: `unknown profiles flag: ${unknownFlag}` };
21
+ }
22
+ const [action, extra] = positional;
23
+ if (action !== "list") {
24
+ return { valid: false, error: "profiles requires action: list" };
25
+ }
26
+ if (extra !== undefined) {
27
+ return { valid: false, error: `unexpected profiles argument: ${extra}` };
28
+ }
29
+ return { valid: true, global, json };
30
+ }
31
+ function statePath(cwd, homeDir, global) {
32
+ return global
33
+ ? join(resolve(homeDir), ".claude", ".cprof-state.json")
34
+ : join(resolve(cwd), ".cprof-state.json");
35
+ }
36
+ function formatInstalls(installs, json) {
37
+ if (json) {
38
+ return `${JSON.stringify({ installs }, null, 2)}\n`;
39
+ }
40
+ if (installs.length === 0) {
41
+ return "No installed profiles recorded.\n";
42
+ }
43
+ return `${installs.map(formatInstallLine).join("\n")}\n`;
44
+ }
45
+ function formatInstallLine(install) {
46
+ return `${install.name} ${install.version} (${install.target}) - ${install.source}`;
47
+ }
@@ -0,0 +1,9 @@
1
+ import { type CommandWriter } from "../command-utils.js";
2
+ export interface RefreshCommandOptions {
3
+ readonly cwd: string;
4
+ readonly homeDir?: string;
5
+ readonly stdout: CommandWriter;
6
+ readonly stderr: CommandWriter;
7
+ }
8
+ export declare function runRefresh(flags: readonly string[], options: RefreshCommandOptions): Promise<number>;
9
+ //# sourceMappingURL=refresh.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"refresh.d.ts","sourceRoot":"","sources":["../../src/commands/refresh.ts"],"names":[],"mappings":"AAWA,OAAO,EAAmB,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAE1E,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;CAChC;AAED,wBAAsB,UAAU,CAC9B,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,MAAM,CAAC,CAgEjB"}
@@ -0,0 +1,48 @@
1
+ import { writeFile } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { createProfileGitignore, createScanReport, scanClaudeProfile, validateProfile, } from "@cprof/core";
5
+ import { readProfileFile } from "../command-utils.js";
6
+ export async function runRefresh(flags, options) {
7
+ if (flags.length > 0) {
8
+ options.stderr.write(`unknown refresh flag: ${flags[0]}\n`);
9
+ return 1;
10
+ }
11
+ const profilePath = join(options.cwd, "claude-profile.json");
12
+ const existing = await readProfileFile(profilePath);
13
+ if (!existing.ok) {
14
+ options.stderr.write(`${existing.errors.join("\n")}\n`);
15
+ return existing.exitCode;
16
+ }
17
+ const scan = await scanClaudeProfile({
18
+ name: existing.profile.name,
19
+ version: existing.profile.version,
20
+ description: existing.profile.description,
21
+ claudeCode: existing.profile.claudeCode,
22
+ cwd: options.cwd,
23
+ homeDir: options.homeDir ?? homedir(),
24
+ outputRoot: options.cwd,
25
+ mode: existing.profile.profileScope,
26
+ includeGlobal: existing.profile.profileScope === "project"
27
+ ? existing.profile.includesGlobal
28
+ : false,
29
+ });
30
+ const refreshed = scan.manifest;
31
+ const validation = validateProfile(refreshed);
32
+ if (!validation.valid) {
33
+ options.stderr.write(`${validation.errors.join("\n")}\n`);
34
+ return validation.exitCode;
35
+ }
36
+ if (!scan.leakCheck.ok) {
37
+ const leakedPaths = [
38
+ ...new Set(scan.leakCheck.leaks.map((leak) => leak.path)),
39
+ ];
40
+ options.stderr.write(`refusing to write: redaction left a secret in ${leakedPaths.join(", ")}\n`);
41
+ return 3;
42
+ }
43
+ await writeFile(profilePath, `${JSON.stringify(refreshed, null, 2)}\n`, "utf8");
44
+ await writeFile(join(options.cwd, ".gitignore"), createProfileGitignore(), "utf8");
45
+ await writeFile(join(options.cwd, "cprof-scan-report.txt"), createScanReport(scan.report), "utf8");
46
+ options.stdout.write("Refreshed claude-profile.json\n");
47
+ return 0;
48
+ }
@@ -0,0 +1,7 @@
1
+ export interface ValidateCommandOptions {
2
+ readonly cwd: string;
3
+ readonly stdout: Pick<NodeJS.WriteStream, "write">;
4
+ readonly stderr: Pick<NodeJS.WriteStream, "write">;
5
+ }
6
+ export declare function runValidate(flags: readonly string[], options: ValidateCommandOptions): Promise<number>;
7
+ //# sourceMappingURL=validate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/commands/validate.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;IACnD,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;CACpD;AAED,wBAAsB,WAAW,CAC/B,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,MAAM,CAAC,CAqBjB"}
@@ -0,0 +1,22 @@
1
+ import { resolve } from "node:path";
2
+ import { validateProfileFile } from "@cprof/core";
3
+ export async function runValidate(flags, options) {
4
+ const json = flags.includes("--json");
5
+ const paths = flags.filter((flag) => flag !== "--json");
6
+ const filePath = paths[0];
7
+ if (filePath === undefined || paths.length > 1) {
8
+ options.stderr.write("usage: cprof validate [--json] <file>\n");
9
+ return 1;
10
+ }
11
+ const result = await validateProfileFile(resolve(options.cwd, filePath));
12
+ if (json) {
13
+ options.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
14
+ }
15
+ else if (result.valid) {
16
+ options.stdout.write("valid\n");
17
+ }
18
+ else {
19
+ options.stderr.write(`${result.errors.join("\n")}\n`);
20
+ }
21
+ return result.exitCode;
22
+ }
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ export interface MainOptions {
3
+ readonly cwd?: string;
4
+ readonly homeDir?: string;
5
+ readonly env?: Readonly<Record<string, string | undefined>>;
6
+ readonly stdout?: Pick<NodeJS.WriteStream, "write">;
7
+ readonly stderr?: Pick<NodeJS.WriteStream, "write">;
8
+ }
9
+ export declare function main(argv?: readonly string[], options?: MainOptions): Promise<number>;
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAaA,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IAC5D,QAAQ,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;IACpD,QAAQ,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;CACrD;AAyCD,wBAAsB,IAAI,CACxB,IAAI,GAAE,SAAS,MAAM,EAA0B,EAC/C,OAAO,GAAE,WAAgB,GACxB,OAAO,CAAC,MAAM,CAAC,CA8EjB"}
package/dist/index.js ADDED
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env node
2
+ import { runDiff } from "./commands/diff.js";
3
+ import { runInit } from "./commands/init.js";
4
+ import { runInstall } from "./commands/install.js";
5
+ import { runProfiles } from "./commands/profiles.js";
6
+ import { runRefresh } from "./commands/refresh.js";
7
+ import { runValidate } from "./commands/validate.js";
8
+ import { readFileSync } from "node:fs";
9
+ import { fileURLToPath } from "node:url";
10
+ import { dirname, join } from "node:path";
11
+ const USAGE = `cprof — snapshot, scrub, and migrate your Claude Code setup
12
+
13
+ Usage: cprof <command> [options]
14
+
15
+ Commands:
16
+ init [--global | --include-global] Snapshot the current setup into claude-profile.json
17
+ refresh Rebuild the profile from its recorded source scope
18
+ install <file> [--dry-run] [--force] [--global | --include-global]
19
+ Apply a trusted profile to this machine (deep merge)
20
+ validate <file> Validate a profile against the schema
21
+ diff <a.json> <b.json> Compare two profiles semantically
22
+ profiles list List profiles recorded by local installs
23
+
24
+ Options:
25
+ -h, --help Show this help
26
+ -v, --version Show the version
27
+
28
+ Profiles are local-first and secret-redacted on capture, but redaction is
29
+ best-effort — always review a profile before sharing it.
30
+
31
+ Docs: https://vedant1202.github.io/claude-prof/
32
+ `;
33
+ function readVersion() {
34
+ try {
35
+ const pkgPath = join(dirname(fileURLToPath(import.meta.url)), "..", "package.json");
36
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
37
+ return pkg.version ?? "0.0.0";
38
+ }
39
+ catch {
40
+ return "0.0.0";
41
+ }
42
+ }
43
+ export async function main(argv = process.argv.slice(2), options = {}) {
44
+ const stdout = options.stdout ?? process.stdout;
45
+ const stderr = options.stderr ?? process.stderr;
46
+ if (argv.includes("--version") || argv.includes("-v")) {
47
+ stdout.write(`${readVersion()}\n`);
48
+ return 0;
49
+ }
50
+ if (argv.length === 0 ||
51
+ argv[0] === "help" ||
52
+ argv.includes("--help") ||
53
+ argv.includes("-h")) {
54
+ stdout.write(USAGE);
55
+ return 0;
56
+ }
57
+ const [command, ...flags] = argv;
58
+ if (command === "init") {
59
+ return runInit(flags, {
60
+ cwd: options.cwd ?? process.cwd(),
61
+ homeDir: options.homeDir,
62
+ stdout,
63
+ stderr,
64
+ });
65
+ }
66
+ if (command === "refresh") {
67
+ return runRefresh(flags, {
68
+ cwd: options.cwd ?? process.cwd(),
69
+ homeDir: options.homeDir,
70
+ stdout,
71
+ stderr,
72
+ });
73
+ }
74
+ if (command === "install") {
75
+ return runInstall(flags, {
76
+ cwd: options.cwd ?? process.cwd(),
77
+ homeDir: options.homeDir,
78
+ env: options.env,
79
+ stdout,
80
+ stderr,
81
+ });
82
+ }
83
+ if (command === "validate") {
84
+ return runValidate(flags, {
85
+ cwd: options.cwd ?? process.cwd(),
86
+ stdout,
87
+ stderr,
88
+ });
89
+ }
90
+ if (command === "profiles") {
91
+ return runProfiles(flags, {
92
+ cwd: options.cwd ?? process.cwd(),
93
+ homeDir: options.homeDir,
94
+ stdout,
95
+ stderr,
96
+ });
97
+ }
98
+ if (command === "diff") {
99
+ return runDiff(flags, {
100
+ cwd: options.cwd ?? process.cwd(),
101
+ stdout,
102
+ stderr,
103
+ });
104
+ }
105
+ stderr.write(`unknown command: ${command}\nRun \`cprof --help\` to see available commands.\n`);
106
+ return 1;
107
+ }
108
+ if (import.meta.url === `file://${process.argv[1]}`) {
109
+ process.exitCode = await main();
110
+ }
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@cprof/cli",
3
+ "version": "0.0.1-alpha.0",
4
+ "description": "Local-first CLI to snapshot, scrub, and migrate your Claude Code setup as a redacted, portable profile.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "homepage": "https://vedant1202.github.io/claude-prof/",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/Vedant1202/claude-prof.git",
11
+ "directory": "packages/cli"
12
+ },
13
+ "bugs": "https://github.com/Vedant1202/claude-prof/issues",
14
+ "keywords": [
15
+ "claude",
16
+ "claude-code",
17
+ "anthropic",
18
+ "mcp",
19
+ "cli",
20
+ "developer-tools",
21
+ "local-first",
22
+ "secret-redaction",
23
+ "dotfiles",
24
+ "configuration",
25
+ "migration",
26
+ "backup"
27
+ ],
28
+ "engines": {
29
+ "node": ">=22"
30
+ },
31
+ "bin": {
32
+ "cprof": "./dist/index.js"
33
+ },
34
+ "exports": {
35
+ ".": {
36
+ "types": "./dist/index.d.ts",
37
+ "default": "./dist/index.js"
38
+ }
39
+ },
40
+ "files": [
41
+ "dist"
42
+ ],
43
+ "publishConfig": {
44
+ "access": "public"
45
+ },
46
+ "dependencies": {
47
+ "@cprof/schema": "0.0.1-alpha.0",
48
+ "@cprof/core": "0.0.1-alpha.0"
49
+ },
50
+ "devDependencies": {
51
+ "vitest": "^3.2.4"
52
+ },
53
+ "scripts": {
54
+ "build": "tsc -p tsconfig.json",
55
+ "test": "vitest run"
56
+ }
57
+ }