Files
superwork/vscode/src/cc/profiles.ts
T
cruldra dae1232f19 🎨 style(vscode): 清零 lint 欠账
非纯格式的改动:
- logger:info 级日志原先走 console.log(no-console 禁止),改为通过 VS Code
  LogOutputChannel(Output 面板 "Superpowers")输出 info/warn/error,并在
  activate 时注册 dispose
- esbuild.mjs:顶层 await 收进 async main(),失败时退出码 1;构建脚本打印
  进度属正当输出,文件级关闭 no-console 并注明原因
- KanbanPanel 资源路径正则:去掉 `\.\/[^"]+` 分支(已被 `[^"/][^"]*` 完全
  覆盖),匹配结果不变
- webhook/server:onError/onListening 互相引用,改为函数声明消除先用后定义
- SettingsModal:删掉针对未启用规则 react-hooks/exhaustive-deps 的失效
  disable 注释,effect 依赖 [open] 保持不变(按打开时机重置表单是有意的)
- issues.ts 提示词里两处全角空格换成普通空格
- 其余为 node:process / node:buffer 显式导入与 switch 拆行

Claude-Session: https://claude.ai/code/session_01HTMgwaE3woqM5yvsHiuSAa
2026-08-31 22:33:11 +08:00

148 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Lists Claude settings profiles from a resolved directory.
* Each `.json` file is a profile: `name` = basename without extension,
* `path` = absolute path for `claude --settings <path>`.
*
* Resolution order for the profiles directory:
* 1. `$SUPERPOWERS_PROFILES_DIR` (env override)
* 2. settings `profilesDirectory` via setProfilesDirOverride (supports ~)
* 3. auto candidates (first readable):
* ~/Sources/cruldra-profile/claude-config/profiles
* ~/.claude/profile-settings
* ~/.claude/profiles
* 4. fallback to the cruldra-profile path for error messages
*
* Missing/unreadable dirs yield an empty list — never throws.
*/
import { accessSync, constants } from 'node:fs'
import { readdir } from 'node:fs/promises'
import { homedir } from 'node:os'
import * as path from 'node:path'
import process from 'node:process'
import { expandTilde } from '../git/worktree'
export interface ClaudeProfile {
/** basename without .json */
name: string
/** absolute path */
path: string
}
/** Settings-panel override; empty/undefined means auto-detect. */
let profilesDirOverride: string | undefined
/**
* Apply settings `profilesDirectory` (or clear with empty/undefined).
* Call on activate, settings open, and after settings save.
*/
export function setProfilesDirOverride(dir: string | undefined): void {
const t = dir?.trim()
profilesDirOverride = t || undefined
}
function isReadableDir(dir: string): boolean {
try {
accessSync(dir, constants.R_OK)
return true
}
catch {
return false
}
}
function isReadableFile(file: string): boolean {
try {
accessSync(file, constants.R_OK)
return true
}
catch {
return false
}
}
/**
* Resolve the Claude profiles directory without hardcoding a user home path.
* Prefers the first existing candidate; falls back to the conventional path
* under `$HOME` so error messages stay meaningful when nothing exists yet.
*/
export function getProfilesDir(): string {
// ① env 显式覆盖优先
const envOverride = process.env.SUPERPOWERS_PROFILES_DIR?.trim()
if (envOverride)
return path.resolve(envOverride)
// ② 设置面板 override(支持 ~)
if (profilesDirOverride)
return path.resolve(expandTilde(profilesDirOverride))
// ③ auto:真实 profile 源优先,再兼容常见链接/目录
const home = homedir()
const candidates = [
path.join(home, 'Sources', 'cruldra-profile', 'claude-config', 'profiles'),
path.join(home, '.claude', 'profile-settings'),
path.join(home, '.claude', 'profiles'),
]
for (const dir of candidates) {
if (isReadableDir(dir))
return dir
}
return candidates[0]!
}
/**
* Remap a stored profile path (often an absolute path from another machine)
* onto the current profiles directory by basename when the original is missing.
*/
export function resolveProfilePath(stored: string | undefined): string {
const raw = stored?.trim()
if (!raw)
return getDefaultProfilePath()
if (isReadableFile(raw))
return raw
const remapped = path.join(getProfilesDir(), path.basename(raw))
if (isReadableFile(remapped))
return remapped
return raw
}
/**
* List Claude settings profiles. Empty array if the directory is missing
* or unreadable. Sorted alphabetically by name.
*/
export async function listClaudeProfiles(): Promise<ClaudeProfile[]> {
const dir = getProfilesDir()
try {
const entries = await readdir(dir)
const profiles: ClaudeProfile[] = []
for (const entry of entries) {
if (!entry.endsWith('.json'))
continue
const name = entry.slice(0, -'.json'.length)
profiles.push({ name, path: path.join(dir, entry) })
}
profiles.sort((a, b) => a.name.localeCompare(b.name))
return profiles
}
catch {
return []
}
}
/**
* Default profile for new sessions: prefer `offical.json` (legacy spelling),
* then `official.json`. Path is always under getProfilesDir().
*/
export function getDefaultProfilePath(): string {
const dir = getProfilesDir()
for (const name of ['offical.json', 'official.json']) {
const p = path.join(dir, name)
if (isReadableFile(p))
return p
}
return path.join(dir, 'offical.json')
}