claude-think 0.3.2 → 0.4.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/CHANGELOG.md +19 -0
- package/README.md +39 -3
- package/package.json +1 -1
- package/src/cli/commands/edit.ts +8 -1
- package/src/cli/commands/help.ts +2 -3
- package/src/cli/commands/init.ts +25 -18
- package/src/cli/commands/profile-commands.ts +142 -0
- package/src/cli/commands/project-learn.ts +4 -4
- package/src/cli/commands/setup.ts +4 -4
- package/src/cli/commands/status.ts +18 -6
- package/src/cli/commands/sync.ts +15 -2
- package/src/cli/index.ts +40 -16
- package/src/core/config.ts +62 -9
- package/src/core/generator.ts +14 -10
- package/src/core/profiles.ts +147 -0
- package/src/core/project-detect.ts +5 -42
- package/src/core/security.ts +44 -0
- package/src/tui/App.tsx +101 -22
- package/src/tui/components/Memory.tsx +147 -11
- package/src/tui/components/ProfileSwitcher.tsx +295 -0
- package/src/tui/components/StatusBar.tsx +18 -1
- package/src/cli/commands/project.ts +0 -64
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,25 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.4.0] - 2025-02-05
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Multi-profile support** - switch between different configurations (work, personal, etc.)
|
|
12
|
+
- `think profile list` - list all profiles with active indicator
|
|
13
|
+
- `think profile use <name>` - switch to a profile (auto-syncs)
|
|
14
|
+
- `think profile create <name> [--from <profile>]` - create new profile
|
|
15
|
+
- `think profile delete <name>` - delete with confirmation
|
|
16
|
+
- TUI profile switcher (press `P`) - switch profiles without leaving the app
|
|
17
|
+
- Active profile shown in TUI header
|
|
18
|
+
- **Inline edit/delete in Memory section**
|
|
19
|
+
- Arrow keys to select items
|
|
20
|
+
- `Enter` to edit inline
|
|
21
|
+
- `d` to delete with confirmation
|
|
22
|
+
- `e` still opens $EDITOR for bulk edits
|
|
23
|
+
### Changed
|
|
24
|
+
- Directory structure now uses `~/.think/profiles/<name>/` for each profile
|
|
25
|
+
- Sync command shows which profile is being synced
|
|
26
|
+
|
|
8
27
|
## [0.3.2] - 2025-02-05
|
|
9
28
|
|
|
10
29
|
### Added
|
package/README.md
CHANGED
|
@@ -46,10 +46,24 @@ claude
|
|
|
46
46
|
|
|
47
47
|
## How it works
|
|
48
48
|
|
|
49
|
-
1. Your preferences live in `~/.think
|
|
50
|
-
2. `think sync` generates `~/.claude/CLAUDE.md`
|
|
49
|
+
1. Your preferences live in `~/.think/profiles/<name>/` (markdown files)
|
|
50
|
+
2. `think sync` generates `~/.claude/CLAUDE.md` from active profile
|
|
51
51
|
3. Claude Code auto-loads CLAUDE.md at session start
|
|
52
52
|
|
|
53
|
+
## Profiles
|
|
54
|
+
|
|
55
|
+
Switch between different configurations for work, personal projects, or clients:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
think profile list # See all profiles
|
|
59
|
+
think profile create work # Create new profile
|
|
60
|
+
think profile create client --from work # Copy from existing
|
|
61
|
+
think profile use work # Switch (auto-syncs)
|
|
62
|
+
think profile delete old # Remove a profile
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Press `P` in the TUI to switch profiles interactively.
|
|
66
|
+
|
|
53
67
|
## Commands
|
|
54
68
|
|
|
55
69
|
| Command | Description |
|
|
@@ -61,13 +75,35 @@ claude
|
|
|
61
75
|
| `think status` | Show current status |
|
|
62
76
|
| `think learn "..."` | Add a learning |
|
|
63
77
|
| `think review` | Review Claude's suggestions |
|
|
64
|
-
| `think profile` |
|
|
78
|
+
| `think profile list` | List all profiles |
|
|
79
|
+
| `think profile use <name>` | Switch to a profile |
|
|
80
|
+
| `think profile create <name>` | Create new profile |
|
|
81
|
+
| `think profile delete <name>` | Delete a profile |
|
|
82
|
+
| `think profile edit` | Edit profile.md |
|
|
65
83
|
| `think edit <file>` | Edit any config file |
|
|
66
84
|
| `think allow "cmd"` | Pre-approve a command |
|
|
67
85
|
| `think tree` | Preview project file tree |
|
|
68
86
|
| `think project learn` | Generate CLAUDE.md for current project |
|
|
69
87
|
| `think help` | Show all commands |
|
|
70
88
|
|
|
89
|
+
## TUI
|
|
90
|
+
|
|
91
|
+
Run `think` to launch the fullscreen TUI:
|
|
92
|
+
|
|
93
|
+
| Key | Action |
|
|
94
|
+
|-----|--------|
|
|
95
|
+
| `Tab` | Switch sections |
|
|
96
|
+
| `↑↓` / `jk` | Navigate / scroll |
|
|
97
|
+
| `Enter` | Edit selected item |
|
|
98
|
+
| `d` | Delete selected item |
|
|
99
|
+
| `a` | Quick actions (sync, learn, search) |
|
|
100
|
+
| `p` | Preview CLAUDE.md |
|
|
101
|
+
| `P` | Switch profile |
|
|
102
|
+
| `/` | Search all files |
|
|
103
|
+
| `e` | Open in $EDITOR |
|
|
104
|
+
| `?` | Help |
|
|
105
|
+
| `q` | Quit |
|
|
106
|
+
|
|
71
107
|
## What you can configure
|
|
72
108
|
|
|
73
109
|
- **Profile** - Communication style, preferences
|
package/package.json
CHANGED
package/src/cli/commands/edit.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { existsSync } from "fs";
|
|
2
2
|
import { spawn } from "child_process";
|
|
3
3
|
import chalk from "chalk";
|
|
4
|
-
import { CONFIG, thinkPath } from "../../core/config";
|
|
4
|
+
import { CONFIG, thinkPath, profilePath } from "../../core/config";
|
|
5
|
+
import { isPathWithin } from "../../core/security";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Open any ~/.think file in $EDITOR
|
|
@@ -30,6 +31,12 @@ export async function editCommand(file: string): Promise<void> {
|
|
|
30
31
|
const resolvedFile = shortcuts[file] || file;
|
|
31
32
|
const filePath = thinkPath(resolvedFile);
|
|
32
33
|
|
|
34
|
+
// Security: Validate path stays within the profile directory
|
|
35
|
+
if (!isPathWithin(profilePath(), filePath)) {
|
|
36
|
+
console.log(chalk.red(`Invalid path: "${file}" is outside the profile directory`));
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
|
|
33
40
|
if (!existsSync(filePath)) {
|
|
34
41
|
console.log(chalk.red(`File not found: ${filePath}`));
|
|
35
42
|
console.log();
|
package/src/cli/commands/help.ts
CHANGED
|
@@ -25,7 +25,7 @@ export async function helpCommand(): Promise<void> {
|
|
|
25
25
|
{ cmd: "think allow <cmd>", desc: "Add command to allowed list" },
|
|
26
26
|
{ cmd: "", desc: "" },
|
|
27
27
|
{ cmd: "think tree", desc: "Preview file tree for current directory" },
|
|
28
|
-
{ cmd: "think project
|
|
28
|
+
{ cmd: "think project learn", desc: "Generate CLAUDE.md for project" },
|
|
29
29
|
];
|
|
30
30
|
|
|
31
31
|
for (const { cmd, desc } of commands) {
|
|
@@ -57,8 +57,7 @@ export async function helpCommand(): Promise<void> {
|
|
|
57
57
|
console.log();
|
|
58
58
|
console.log(chalk.bold("Files:\n"));
|
|
59
59
|
console.log(` ${chalk.dim("Source:")} ~/.think/`);
|
|
60
|
-
console.log(` ${chalk.dim("
|
|
61
|
-
console.log(` ${chalk.dim("Project:")} .think.yaml (optional)`);
|
|
60
|
+
console.log(` ${chalk.dim("Output:")} ~/.claude/CLAUDE.md`);
|
|
62
61
|
|
|
63
62
|
console.log();
|
|
64
63
|
}
|
package/src/cli/commands/init.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { mkdir, copyFile
|
|
1
|
+
import { mkdir, copyFile } from "fs/promises";
|
|
2
2
|
import { existsSync } from "fs";
|
|
3
3
|
import { join, dirname } from "path";
|
|
4
4
|
import chalk from "chalk";
|
|
5
|
-
import { CONFIG,
|
|
5
|
+
import { CONFIG, profileFilePath } from "../../core/config";
|
|
6
|
+
import { ensureProfilesStructure } from "../../core/profiles";
|
|
6
7
|
|
|
7
8
|
const TEMPLATE_DIR = join(dirname(dirname(dirname(__dirname))), "src", "templates");
|
|
8
9
|
|
|
@@ -13,31 +14,35 @@ export async function initCommand(options: { force?: boolean }): Promise<void> {
|
|
|
13
14
|
const { force } = options;
|
|
14
15
|
|
|
15
16
|
// Check if already initialized
|
|
16
|
-
if (existsSync(CONFIG.
|
|
17
|
-
console.log(chalk.yellow(`~/.think already
|
|
17
|
+
if (existsSync(CONFIG.profilesDir) && !force) {
|
|
18
|
+
console.log(chalk.yellow(`~/.think already initialized. Use --force to reinitialize.`));
|
|
18
19
|
return;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
console.log(chalk.blue("Initializing ~/.think..."));
|
|
22
23
|
|
|
23
|
-
//
|
|
24
|
+
// Ensure base structure exists
|
|
25
|
+
await mkdir(CONFIG.thinkDir, { recursive: true });
|
|
26
|
+
ensureProfilesStructure();
|
|
27
|
+
|
|
28
|
+
// Create directory structure in default profile
|
|
29
|
+
const profileRoot = profileFilePath(CONFIG.defaultProfile);
|
|
24
30
|
const dirs = [
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
thinkPath(CONFIG.dirs.projects),
|
|
31
|
+
profileRoot,
|
|
32
|
+
join(profileRoot, CONFIG.dirs.preferences),
|
|
33
|
+
join(profileRoot, CONFIG.dirs.permissions),
|
|
34
|
+
join(profileRoot, CONFIG.dirs.skills),
|
|
35
|
+
join(profileRoot, CONFIG.dirs.agents),
|
|
36
|
+
join(profileRoot, CONFIG.dirs.memory),
|
|
37
|
+
join(profileRoot, CONFIG.dirs.automation),
|
|
38
|
+
join(profileRoot, CONFIG.dirs.templates),
|
|
34
39
|
];
|
|
35
40
|
|
|
36
41
|
for (const dir of dirs) {
|
|
37
42
|
await mkdir(dir, { recursive: true });
|
|
38
43
|
}
|
|
39
44
|
|
|
40
|
-
// Copy templates
|
|
45
|
+
// Copy templates to default profile
|
|
41
46
|
const templateMap: Record<string, string> = {
|
|
42
47
|
"profile.md": CONFIG.files.profile,
|
|
43
48
|
"tools.md": CONFIG.files.tools,
|
|
@@ -55,7 +60,7 @@ export async function initCommand(options: { force?: boolean }): Promise<void> {
|
|
|
55
60
|
|
|
56
61
|
for (const [template, dest] of Object.entries(templateMap)) {
|
|
57
62
|
const srcPath = join(TEMPLATE_DIR, template);
|
|
58
|
-
const destPath =
|
|
63
|
+
const destPath = profileFilePath(CONFIG.defaultProfile, dest);
|
|
59
64
|
|
|
60
65
|
// Only copy if destination doesn't exist (unless force)
|
|
61
66
|
if (!existsSync(destPath) || force) {
|
|
@@ -70,7 +75,9 @@ export async function initCommand(options: { force?: boolean }): Promise<void> {
|
|
|
70
75
|
console.log(chalk.green("Done! Your ~/.think directory is ready."));
|
|
71
76
|
console.log();
|
|
72
77
|
console.log("Next steps:");
|
|
73
|
-
console.log(` 1.
|
|
74
|
-
console.log(` 2. Run ${chalk.cyan("think sync")} to generate
|
|
78
|
+
console.log(` 1. Run ${chalk.cyan("think setup")} to configure your profile`);
|
|
79
|
+
console.log(` 2. Run ${chalk.cyan("think sync")} to generate CLAUDE.md`);
|
|
75
80
|
console.log(` 3. Start using Claude with your personal context!`);
|
|
81
|
+
console.log();
|
|
82
|
+
console.log(chalk.gray(`Profile: default (at ~/.think/profiles/default/)`));
|
|
76
83
|
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import * as p from "@clack/prompts";
|
|
3
|
+
import {
|
|
4
|
+
listProfiles,
|
|
5
|
+
switchProfile,
|
|
6
|
+
createProfile,
|
|
7
|
+
deleteProfile,
|
|
8
|
+
profileExists,
|
|
9
|
+
} from "../../core/profiles";
|
|
10
|
+
import { getActiveProfile } from "../../core/config";
|
|
11
|
+
import { syncCommand } from "../commands/sync";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* List all profiles, showing which is active with a checkmark
|
|
15
|
+
*/
|
|
16
|
+
export async function profileListCommand(): Promise<void> {
|
|
17
|
+
const profiles = listProfiles();
|
|
18
|
+
|
|
19
|
+
if (profiles.length === 0) {
|
|
20
|
+
console.log(chalk.yellow("No profiles found. Run `think init` first."));
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
console.log("Profiles:");
|
|
25
|
+
for (const profile of profiles) {
|
|
26
|
+
if (profile.isActive) {
|
|
27
|
+
console.log(` ${chalk.green("\u2713")} ${chalk.cyan(profile.name)} (active)`);
|
|
28
|
+
} else {
|
|
29
|
+
console.log(` ${chalk.cyan(profile.name)}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Switch to a profile, then run sync
|
|
36
|
+
*/
|
|
37
|
+
export async function profileUseCommand(name: string): Promise<void> {
|
|
38
|
+
if (!profileExists(name)) {
|
|
39
|
+
console.log(chalk.red(`Profile "${name}" does not exist.`));
|
|
40
|
+
console.log();
|
|
41
|
+
console.log("Available profiles:");
|
|
42
|
+
const profiles = listProfiles();
|
|
43
|
+
for (const profile of profiles) {
|
|
44
|
+
console.log(` - ${chalk.cyan(profile.name)}`);
|
|
45
|
+
}
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const currentProfile = getActiveProfile();
|
|
50
|
+
if (currentProfile === name) {
|
|
51
|
+
console.log(chalk.yellow(`Already using profile "${name}".`));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
switchProfile(name);
|
|
57
|
+
console.log(chalk.green(`Switched to profile "${chalk.cyan(name)}".`));
|
|
58
|
+
console.log();
|
|
59
|
+
await syncCommand();
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.error(chalk.red(`Failed to switch profile: ${error}`));
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Create a new profile, optionally copying from an existing one
|
|
68
|
+
*/
|
|
69
|
+
export async function profileCreateCommand(
|
|
70
|
+
name: string,
|
|
71
|
+
options: { from?: string }
|
|
72
|
+
): Promise<void> {
|
|
73
|
+
if (profileExists(name)) {
|
|
74
|
+
console.log(chalk.red(`Profile "${name}" already exists.`));
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (options.from && !profileExists(options.from)) {
|
|
79
|
+
console.log(chalk.red(`Source profile "${options.from}" does not exist.`));
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
createProfile(name, options.from);
|
|
85
|
+
|
|
86
|
+
if (options.from) {
|
|
87
|
+
console.log(
|
|
88
|
+
chalk.green(
|
|
89
|
+
`Created profile "${chalk.cyan(name)}" from "${chalk.cyan(options.from)}".`
|
|
90
|
+
)
|
|
91
|
+
);
|
|
92
|
+
} else {
|
|
93
|
+
console.log(chalk.green(`Created profile "${chalk.cyan(name)}".`));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
console.log();
|
|
97
|
+
console.log(`Run ${chalk.cyan(`think profile use ${name}`)} to switch to it.`);
|
|
98
|
+
} catch (error) {
|
|
99
|
+
console.error(chalk.red(`Failed to create profile: ${error}`));
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Delete a profile with confirmation
|
|
106
|
+
*/
|
|
107
|
+
export async function profileDeleteCommand(name: string): Promise<void> {
|
|
108
|
+
if (!profileExists(name)) {
|
|
109
|
+
console.log(chalk.red(`Profile "${name}" does not exist.`));
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const activeProfile = getActiveProfile();
|
|
114
|
+
const isActive = activeProfile === name;
|
|
115
|
+
|
|
116
|
+
const confirmMessage = isActive
|
|
117
|
+
? `Are you sure you want to delete the active profile "${name}"? This will switch to the default profile.`
|
|
118
|
+
: `Are you sure you want to delete profile "${name}"?`;
|
|
119
|
+
|
|
120
|
+
const confirmed = await p.confirm({
|
|
121
|
+
message: confirmMessage,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
if (p.isCancel(confirmed) || !confirmed) {
|
|
125
|
+
console.log(chalk.yellow("Deletion cancelled."));
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
deleteProfile(name);
|
|
131
|
+
console.log(chalk.green(`Deleted profile "${chalk.cyan(name)}".`));
|
|
132
|
+
|
|
133
|
+
if (isActive) {
|
|
134
|
+
console.log();
|
|
135
|
+
console.log(`Switched to default profile.`);
|
|
136
|
+
await syncCommand();
|
|
137
|
+
}
|
|
138
|
+
} catch (error) {
|
|
139
|
+
console.error(chalk.red(`Failed to delete profile: ${error}`));
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { existsSync } from "fs";
|
|
2
|
-
import { writeFile, readFile, readdir
|
|
3
|
-
import { join
|
|
1
|
+
import { existsSync, readFileSync } from "fs";
|
|
2
|
+
import { writeFile, readFile, readdir } from "fs/promises";
|
|
3
|
+
import { join } from "path";
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
import { detectProject, ProjectType } from "../../core/project-detect";
|
|
6
6
|
|
|
@@ -180,7 +180,7 @@ function findEntryPoints(dir: string, type: ProjectType): string[] {
|
|
|
180
180
|
const pkgPath = join(dir, "package.json");
|
|
181
181
|
if (existsSync(pkgPath)) {
|
|
182
182
|
try {
|
|
183
|
-
const pkg = JSON.parse(
|
|
183
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
184
184
|
if (pkg.bin) {
|
|
185
185
|
const bins = typeof pkg.bin === "string" ? [pkg.bin] : Object.values(pkg.bin);
|
|
186
186
|
for (const bin of bins as string[]) {
|
|
@@ -300,16 +300,16 @@ export async function setupCommand(): Promise<void> {
|
|
|
300
300
|
});
|
|
301
301
|
if (p.isCancel(infrastructure)) return handleCancel();
|
|
302
302
|
|
|
303
|
-
const monorepo = await p.
|
|
303
|
+
const monorepo = await p.multiselect({
|
|
304
304
|
message: "Monorepo tooling?",
|
|
305
305
|
options: [
|
|
306
|
-
{ value: "none", label: "None / Single repo" },
|
|
307
306
|
{ value: "Turborepo", label: "Turborepo" },
|
|
308
307
|
{ value: "Bun workspaces", label: "Bun workspaces" },
|
|
309
308
|
{ value: "Nx", label: "Nx" },
|
|
310
309
|
{ value: "pnpm workspaces", label: "pnpm workspaces" },
|
|
311
310
|
{ value: "Lerna", label: "Lerna" },
|
|
312
311
|
],
|
|
312
|
+
required: false,
|
|
313
313
|
});
|
|
314
314
|
if (p.isCancel(monorepo)) return handleCancel();
|
|
315
315
|
|
|
@@ -615,10 +615,10 @@ ${(infrastructure as string[]).map((i) => `- ${i}`).join("\n")}`);
|
|
|
615
615
|
}
|
|
616
616
|
|
|
617
617
|
// Monorepo
|
|
618
|
-
if (monorepo
|
|
618
|
+
if ((monorepo as string[]).length > 0) {
|
|
619
619
|
toolsSections.push(`
|
|
620
620
|
## Monorepo
|
|
621
|
-
|
|
621
|
+
${(monorepo as string[]).map((m) => `- ${m}`).join("\n")}`);
|
|
622
622
|
}
|
|
623
623
|
|
|
624
624
|
// Testing
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync } from "fs";
|
|
2
|
-
import { readFile, readdir } from "fs/promises";
|
|
2
|
+
import { readFile, readdir, stat } from "fs/promises";
|
|
3
3
|
import chalk from "chalk";
|
|
4
|
-
import { CONFIG, thinkPath,
|
|
4
|
+
import { CONFIG, thinkPath, getActiveProfile } from "../../core/config";
|
|
5
5
|
import { extractLearnings } from "../../core/dedup";
|
|
6
6
|
import { printBanner } from "../../core/banner";
|
|
7
7
|
|
|
@@ -19,11 +19,17 @@ export async function statusCommand(): Promise<void> {
|
|
|
19
19
|
|
|
20
20
|
console.log(chalk.green("✓ ~/.think initialized"));
|
|
21
21
|
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
// Show active profile
|
|
23
|
+
const profile = getActiveProfile();
|
|
24
|
+
console.log(`Profile: ${chalk.cyan(profile)}`);
|
|
25
|
+
|
|
26
|
+
// Check CLAUDE.md and show token estimate
|
|
27
|
+
if (existsSync(CONFIG.claudeMdPath)) {
|
|
28
|
+
const content = await readFile(CONFIG.claudeMdPath, "utf-8");
|
|
29
|
+
const tokens = Math.ceil(content.length / 4);
|
|
30
|
+
console.log(chalk.green(`✓ CLAUDE.md generated (~${formatTokens(tokens)} tokens)`));
|
|
25
31
|
} else {
|
|
26
|
-
console.log(chalk.yellow("○
|
|
32
|
+
console.log(chalk.yellow("○ CLAUDE.md not generated. Run `think sync`"));
|
|
27
33
|
}
|
|
28
34
|
|
|
29
35
|
console.log();
|
|
@@ -64,3 +70,9 @@ export async function statusCommand(): Promise<void> {
|
|
|
64
70
|
|
|
65
71
|
console.log();
|
|
66
72
|
}
|
|
73
|
+
|
|
74
|
+
function formatTokens(tokens: number): string {
|
|
75
|
+
if (tokens < 1000) return tokens.toString();
|
|
76
|
+
if (tokens < 10000) return `${(tokens / 1000).toFixed(1)}k`;
|
|
77
|
+
return `${Math.round(tokens / 1000)}k`;
|
|
78
|
+
}
|
package/src/cli/commands/sync.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync } from "fs";
|
|
2
|
+
import { readFile } from "fs/promises";
|
|
2
3
|
import chalk from "chalk";
|
|
3
|
-
import { CONFIG } from "../../core/config";
|
|
4
|
+
import { CONFIG, getActiveProfile } from "../../core/config";
|
|
4
5
|
import { generatePlugin } from "../../core/generator";
|
|
5
6
|
|
|
6
7
|
/**
|
|
@@ -13,14 +14,20 @@ export async function syncCommand(): Promise<void> {
|
|
|
13
14
|
process.exit(1);
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
const profile = getActiveProfile();
|
|
18
|
+
console.log(chalk.blue(`Syncing profile "${chalk.cyan(profile)}" to ~/.claude/CLAUDE.md...`));
|
|
17
19
|
|
|
18
20
|
try {
|
|
19
21
|
await generatePlugin();
|
|
20
22
|
|
|
23
|
+
// Calculate token estimate
|
|
24
|
+
const content = await readFile(CONFIG.claudeMdPath, "utf-8");
|
|
25
|
+
const tokens = Math.ceil(content.length / 4);
|
|
26
|
+
|
|
21
27
|
console.log(chalk.green("Done!"));
|
|
22
28
|
console.log();
|
|
23
29
|
console.log(`Generated: ${chalk.cyan(CONFIG.claudeMdPath)}`);
|
|
30
|
+
console.log(`Size: ~${chalk.magenta(formatTokens(tokens))} tokens`);
|
|
24
31
|
console.log();
|
|
25
32
|
console.log("Claude will automatically load your context in new sessions.");
|
|
26
33
|
} catch (error) {
|
|
@@ -28,3 +35,9 @@ export async function syncCommand(): Promise<void> {
|
|
|
28
35
|
process.exit(1);
|
|
29
36
|
}
|
|
30
37
|
}
|
|
38
|
+
|
|
39
|
+
function formatTokens(tokens: number): string {
|
|
40
|
+
if (tokens < 1000) return tokens.toString();
|
|
41
|
+
if (tokens < 10000) return `${(tokens / 1000).toFixed(1)}k`;
|
|
42
|
+
return `${Math.round(tokens / 1000)}k`;
|
|
43
|
+
}
|
package/src/cli/index.ts
CHANGED
|
@@ -5,11 +5,16 @@ import { syncCommand } from "./commands/sync";
|
|
|
5
5
|
import { learnCommand } from "./commands/learn";
|
|
6
6
|
import { statusCommand } from "./commands/status";
|
|
7
7
|
import { profileCommand } from "./commands/profile";
|
|
8
|
+
import {
|
|
9
|
+
profileListCommand,
|
|
10
|
+
profileUseCommand,
|
|
11
|
+
profileCreateCommand,
|
|
12
|
+
profileDeleteCommand,
|
|
13
|
+
} from "./commands/profile-commands";
|
|
8
14
|
import { editCommand } from "./commands/edit";
|
|
9
15
|
import { allowCommand } from "./commands/allow";
|
|
10
16
|
import { reviewCommand } from "./commands/review";
|
|
11
17
|
import { treeCommand } from "./commands/tree";
|
|
12
|
-
import { projectInitCommand } from "./commands/project";
|
|
13
18
|
import { projectLearnCommand } from "./commands/project-learn";
|
|
14
19
|
import { helpCommand } from "./commands/help";
|
|
15
20
|
import { setupCommand } from "./commands/setup";
|
|
@@ -50,12 +55,40 @@ program
|
|
|
50
55
|
.description("Show current think status")
|
|
51
56
|
.action(statusCommand);
|
|
52
57
|
|
|
53
|
-
//
|
|
54
|
-
program
|
|
58
|
+
// Profile management commands
|
|
59
|
+
const profileCmd = program
|
|
55
60
|
.command("profile")
|
|
61
|
+
.description("Manage profiles");
|
|
62
|
+
|
|
63
|
+
profileCmd
|
|
64
|
+
.command("list")
|
|
65
|
+
.description("List all profiles")
|
|
66
|
+
.action(profileListCommand);
|
|
67
|
+
|
|
68
|
+
profileCmd
|
|
69
|
+
.command("use <name>")
|
|
70
|
+
.description("Switch to a profile")
|
|
71
|
+
.action(profileUseCommand);
|
|
72
|
+
|
|
73
|
+
profileCmd
|
|
74
|
+
.command("create <name>")
|
|
75
|
+
.description("Create a new profile")
|
|
76
|
+
.option("--from <profile>", "Copy from existing profile")
|
|
77
|
+
.action(profileCreateCommand);
|
|
78
|
+
|
|
79
|
+
profileCmd
|
|
80
|
+
.command("delete <name>")
|
|
81
|
+
.description("Delete a profile")
|
|
82
|
+
.action(profileDeleteCommand);
|
|
83
|
+
|
|
84
|
+
profileCmd
|
|
85
|
+
.command("edit")
|
|
56
86
|
.description("Open profile.md in $EDITOR")
|
|
57
87
|
.action(profileCommand);
|
|
58
88
|
|
|
89
|
+
// Also allow `think profile` with no subcommand to edit
|
|
90
|
+
profileCmd.action(profileCommand);
|
|
91
|
+
|
|
59
92
|
// Edit any file
|
|
60
93
|
program
|
|
61
94
|
.command("edit <file>")
|
|
@@ -81,20 +114,11 @@ program
|
|
|
81
114
|
.description("Preview file tree for current directory")
|
|
82
115
|
.action(treeCommand);
|
|
83
116
|
|
|
84
|
-
// Project
|
|
85
|
-
|
|
117
|
+
// Project command - generate project CLAUDE.md
|
|
118
|
+
program
|
|
86
119
|
.command("project")
|
|
87
|
-
.description("
|
|
88
|
-
|
|
89
|
-
projectCmd
|
|
90
|
-
.command("init")
|
|
91
|
-
.description("Initialize .think.yaml for current project")
|
|
92
|
-
.option("-f, --force", "Overwrite existing config")
|
|
93
|
-
.action(projectInitCommand);
|
|
94
|
-
|
|
95
|
-
projectCmd
|
|
96
|
-
.command("learn")
|
|
97
|
-
.description("Generate CLAUDE.md with project structure")
|
|
120
|
+
.description("Generate CLAUDE.md for current project")
|
|
121
|
+
.alias("project learn")
|
|
98
122
|
.option("-f, --force", "Overwrite existing CLAUDE.md")
|
|
99
123
|
.action(projectLearnCommand);
|
|
100
124
|
|