@simplysm/sd-claude 13.0.82 → 13.0.83

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.
Files changed (51) hide show
  1. package/README.md +43 -0
  2. package/claude/skills/sd-plan/SKILL.md +4 -3
  3. package/package.json +3 -19
  4. package/scripts/postinstall.mjs +126 -9
  5. package/scripts/sd-entries.mjs +33 -0
  6. package/scripts/sync-claude-assets.mjs +4 -21
  7. package/dist/commands/auth-add.d.ts +0 -2
  8. package/dist/commands/auth-add.d.ts.map +0 -1
  9. package/dist/commands/auth-add.js +0 -32
  10. package/dist/commands/auth-add.js.map +0 -6
  11. package/dist/commands/auth-list.d.ts +0 -2
  12. package/dist/commands/auth-list.d.ts.map +0 -1
  13. package/dist/commands/auth-list.js +0 -95
  14. package/dist/commands/auth-list.js.map +0 -6
  15. package/dist/commands/auth-remove.d.ts +0 -2
  16. package/dist/commands/auth-remove.d.ts.map +0 -1
  17. package/dist/commands/auth-remove.js +0 -22
  18. package/dist/commands/auth-remove.js.map +0 -6
  19. package/dist/commands/auth-use.d.ts +0 -2
  20. package/dist/commands/auth-use.d.ts.map +0 -1
  21. package/dist/commands/auth-use.js +0 -33
  22. package/dist/commands/auth-use.js.map +0 -6
  23. package/dist/commands/auth-utils.d.ts +0 -11
  24. package/dist/commands/auth-utils.d.ts.map +0 -1
  25. package/dist/commands/auth-utils.js +0 -57
  26. package/dist/commands/auth-utils.js.map +0 -6
  27. package/dist/commands/install.d.ts +0 -2
  28. package/dist/commands/install.d.ts.map +0 -1
  29. package/dist/commands/install.js +0 -127
  30. package/dist/commands/install.js.map +0 -6
  31. package/dist/index.d.ts +0 -7
  32. package/dist/index.d.ts.map +0 -1
  33. package/dist/index.js +0 -7
  34. package/dist/index.js.map +0 -6
  35. package/dist/sd-claude.d.ts +0 -3
  36. package/dist/sd-claude.d.ts.map +0 -1
  37. package/dist/sd-claude.js +0 -78
  38. package/dist/sd-claude.js.map +0 -6
  39. package/src/commands/auth-add.ts +0 -36
  40. package/src/commands/auth-list.ts +0 -130
  41. package/src/commands/auth-remove.ts +0 -26
  42. package/src/commands/auth-use.ts +0 -53
  43. package/src/commands/auth-utils.ts +0 -65
  44. package/src/commands/install.ts +0 -183
  45. package/src/index.ts +0 -7
  46. package/src/sd-claude.ts +0 -98
  47. package/tests/auth-add.spec.ts +0 -74
  48. package/tests/auth-list.spec.ts +0 -198
  49. package/tests/auth-remove.spec.ts +0 -74
  50. package/tests/auth-use.spec.ts +0 -153
  51. package/tests/auth-utils.spec.ts +0 -173
package/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # @simplysm/sd-claude
2
+
3
+ > Simplysm Claude Code asset installer
4
+
5
+ A postinstall hook that provisions [Claude Code](https://docs.anthropic.com/en/docs/claude-code) projects with shared skills, rules, hooks, and a status line.
6
+
7
+ On `npm install` (postinstall), the package automatically copies its bundled `sd-*` assets into the consuming project's `.claude/` directory and configures `settings.json` with the status line and session-start hook.
8
+
9
+ ## Bundled Assets
10
+
11
+ The following assets are installed into the project's `.claude/` directory.
12
+
13
+ ### Skills
14
+
15
+ | Skill | Trigger | Description |
16
+ |-------|---------|-------------|
17
+ | `sd-commit` | `/sd-commit` | Stages changes, generates a Conventional Commits message, and commits |
18
+ | `sd-check` | `/sd-check` | Runs `check` script (typecheck + lint + test), auto-fixes code errors in a loop via `sd-debug` |
19
+ | `sd-plan` | `/sd-plan` | Generates a fully clarified implementation plan through iterative Q&A |
20
+ | `sd-debug` | `/sd-debug` | Analyzes errors/stack traces, diagnoses root cause, then delegates to `sd-plan` |
21
+ | `sd-review` | `/sd-review` | Scans code for potential bugs across 5 categories, then plans and applies fixes |
22
+ | `sd-simplify` | `/sd-simplify` | Reviews code for simplification opportunities, then plans and applies changes |
23
+ | `sd-api-review` | `/sd-api-review` | Compares public API against standards and popular libraries, proposes naming/structure improvements |
24
+ | `sd-init` | `/sd-init` | Auto-generates `CLAUDE.md` by analyzing the project's scripts, dependencies, and lint config |
25
+ | `sd-readme` | `/sd-readme` | Generates README.md documentation for monorepo packages |
26
+ | `sd-document` | `/sd-document` | Reads/writes `.docx`, `.xlsx`, `.pptx`, `.pdf` files via Python extraction scripts |
27
+ | `sd-email-analyze` | `/sd-email-analyze` | Parses `.eml`/`.msg` email files, extracts headers, body, inline images, and attachments |
28
+
29
+ ### Rules
30
+
31
+ | Rule file | Purpose |
32
+ |-----------|---------|
33
+ | `sd-claude-rules.md` | Enforces YAGNI, no unsolicited code edits, Playwright output directory |
34
+ | `sd-simplysm-usage.md` | Directs Claude to read package README.md files for `@simplysm/*` API details |
35
+
36
+ ### Hooks & Status Line
37
+
38
+ | Asset | Description |
39
+ |-------|-------------|
40
+ | `sd-session-start.sh` | Session-start hook that reminds Claude to read `.claude/rules/*.md` and `CLAUDE.md` |
41
+ | `sd-statusline.py` | Status line script showing: folder name, model, context %, 5h usage %, 7d usage % |
42
+
43
+ The status line fetches usage data from the Anthropic API in the background (every 3 minutes) and caches results in `~/.claude/statusline-cache.json`.
@@ -23,10 +23,10 @@ description: 이 스킬은 사용자가 "계획 세워줘", "plan 만들어", "s
23
23
 
24
24
  ### 2-1. 초안 작성
25
25
 
26
- 계획서 초안을 작성하라. 구현 단계는 **검증 → 구현 → 확인** 순서로 배치하라:
27
- - 테스트 프레임워크가 있으면 → 테스트 코드 먼저 작성
28
- - 테스트 프레임워크가 없으면 → CLI 실행, dry-run 등 검증 방법 명시
26
+ 계획서 초안을 작성하라. 반드시 TDD원칙에 따라 작성하라:
27
+ - 코드 작업이면 → 테스트 코드 먼저 작성
29
28
  - 비코드 작업이면 → 자체 검증 체크리스트 먼저 정의
29
+ - 코드 작업이지만 프로젝트 자체에 테스트환경 자체가 구축되어있지 않다면 → CLI, dry-run등의 검증방법 제시
30
30
 
31
31
  ### 2-2. 명확화 사이클
32
32
 
@@ -35,6 +35,7 @@ description: 이 스킬은 사용자가 "계획 세워줘", "plan 만들어", "s
35
35
  1. **추출**: 계획서를 아래 "불명확 판단 기준" 12개 항목에 전부 대조하여 불명확 항목을 나열하라.
36
36
  2. **의존성 분석**: 항목 간 의존 관계를 파악하라. ("A가 정해져야 B를 질문 가능" → B는 A에 의존)
37
37
  3. **질문**: 의존 대상이 없는 항목들을 AskUserQuestion 도구 **하나당 단 하나의 질문**만 하라. 각 질문에 2~5개 선택지를 제시하라.
38
+ - `한건의 설명제시 -> AskUserQuestion` 반복
38
39
  4. **반영**: 답변을 모두 반영하여 계획서를 업데이트하고, 1번으로 돌아가라.
39
40
 
40
41
  불명확 항목 0개 → **Step 2.5 최종 검증**으로 이동.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@simplysm/sd-claude",
3
- "version": "13.0.82",
4
- "description": "Simplysm Claude Code CLI — asset installer",
3
+ "version": "13.0.83",
4
+ "description": "Simplysm Claude Code asset installer",
5
5
  "author": "simplysm",
6
6
  "license": "Apache-2.0",
7
7
  "repository": {
@@ -10,27 +10,11 @@
10
10
  "directory": "packages/sd-claude"
11
11
  },
12
12
  "type": "module",
13
- "main": "./dist/index.js",
14
- "types": "./dist/index.d.ts",
15
13
  "files": [
16
- "dist",
17
- "src",
18
- "tests",
19
14
  "scripts",
20
- "claude",
21
- "docs"
15
+ "claude"
22
16
  ],
23
- "sideEffects": false,
24
- "dependencies": {
25
- "yargs": "^18.0.0"
26
- },
27
- "devDependencies": {
28
- "@types/yargs": "^17.0.35"
29
- },
30
17
  "scripts": {
31
18
  "postinstall": "node scripts/postinstall.mjs"
32
- },
33
- "bin": {
34
- "sd-claude": "./dist/sd-claude.js"
35
19
  }
36
20
  }
@@ -1,15 +1,132 @@
1
1
  /**
2
- * postinstall thin wrapper.
3
- * dist/가 없으면 (모노레포 개발 환경) 건너뛴다.
2
+ * Installs Claude Code assets to the project's .claude/ directory.
3
+ * postinstall hook 실패해도 pnpm install을 차단하지 않는다.
4
4
  */
5
- import { existsSync } from "fs";
6
- import { resolve, dirname } from "path";
5
+ import fs from "fs";
6
+ import path from "path";
7
7
  import { fileURLToPath } from "url";
8
- import { execFileSync } from "child_process";
8
+ import { collectSdEntries, forEachSdEntry } from "./sd-entries.mjs";
9
9
 
10
- const __dirname = dirname(fileURLToPath(import.meta.url));
11
- const distEntry = resolve(__dirname, "../dist/sd-claude.js");
10
+ try {
11
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
+ // scripts/ → package root
13
+ const pkgRoot = path.resolve(__dirname, "..");
14
+ const sourceDir = path.join(pkgRoot, "claude");
12
15
 
13
- if (existsSync(distEntry)) {
14
- execFileSync("node", [distEntry, "install"], { stdio: "inherit" });
16
+ const projectRoot = findProjectRoot(__dirname);
17
+ if (projectRoot == null) {
18
+ console.log("[@simplysm/sd-claude] Could not find project root, skipping installation.");
19
+ process.exit(0);
20
+ }
21
+
22
+ // Skip execution if this is the simplysm monorepo with the same major version
23
+ if (isSimplysmMonorepoSameMajor(projectRoot, pkgRoot)) {
24
+ process.exit(0);
25
+ }
26
+
27
+ // Skip if the source directory doesn't exist (claude/ may not exist in monorepo dev environment)
28
+ if (!fs.existsSync(sourceDir)) {
29
+ process.exit(0);
30
+ }
31
+
32
+ const sourceEntries = collectSdEntries(sourceDir);
33
+ if (sourceEntries.length === 0) {
34
+ process.exit(0);
35
+ }
36
+
37
+ const targetDir = path.join(projectRoot, ".claude");
38
+
39
+ cleanSdEntries(targetDir);
40
+ copySdEntries(sourceDir, targetDir, sourceEntries);
41
+ setupSettings(targetDir);
42
+
43
+ console.log(`[@simplysm/sd-claude] Installed ${sourceEntries.length} sd-* entries.`);
44
+ } catch (err) {
45
+ // Ignore errors to prevent postinstall failure from blocking the entire pnpm install
46
+ console.warn("[@simplysm/sd-claude] postinstall warning:", err.message);
47
+ }
48
+
49
+ /** Finds the project root from INIT_CWD or node_modules path. */
50
+ function findProjectRoot(dirname) {
51
+ if (process.env["INIT_CWD"] != null) {
52
+ return process.env["INIT_CWD"];
53
+ }
54
+
55
+ const sep = path.sep;
56
+ const marker = sep + "node_modules" + sep;
57
+ const idx = dirname.indexOf(marker);
58
+ return idx !== -1 ? dirname.substring(0, idx) : undefined;
59
+ }
60
+
61
+ /** Checks if this is the simplysm monorepo with the same major version. */
62
+ function isSimplysmMonorepoSameMajor(projectRoot, pkgRoot) {
63
+ const projectPkgPath = path.join(projectRoot, "package.json");
64
+ if (!fs.existsSync(projectPkgPath)) return false;
65
+
66
+ const projectPkg = JSON.parse(fs.readFileSync(projectPkgPath, "utf-8"));
67
+ if (projectPkg.name !== "simplysm") return false;
68
+
69
+ const sdClaudePkgPath = path.join(pkgRoot, "package.json");
70
+ if (!fs.existsSync(sdClaudePkgPath)) return false;
71
+
72
+ const sdClaudePkg = JSON.parse(fs.readFileSync(sdClaudePkgPath, "utf-8"));
73
+
74
+ const projectMajor = projectPkg.version?.split(".")[0];
75
+ const sdClaudeMajor = sdClaudePkg.version?.split(".")[0];
76
+ return projectMajor != null && projectMajor === sdClaudeMajor;
77
+ }
78
+
79
+ /** Removes existing sd-* entries. */
80
+ function cleanSdEntries(targetDir) {
81
+ if (!fs.existsSync(targetDir)) return;
82
+ forEachSdEntry(targetDir, (rel) => {
83
+ fs.rmSync(path.join(targetDir, rel), { recursive: true });
84
+ });
85
+ }
86
+
87
+ /** Copies sd-* entries. */
88
+ function copySdEntries(sourceDir, targetDir, entries) {
89
+ fs.mkdirSync(targetDir, { recursive: true });
90
+ for (const entry of entries) {
91
+ const src = path.join(sourceDir, entry);
92
+ const dest = path.join(targetDir, entry);
93
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
94
+ fs.cpSync(src, dest, { recursive: true });
95
+ }
96
+ }
97
+
98
+ /** Ensures statusLine and SessionStart hooks are configured in settings.json. */
99
+ function setupSettings(targetDir) {
100
+ const settingsPath = path.join(targetDir, "settings.json");
101
+
102
+ let settings = {};
103
+ if (fs.existsSync(settingsPath)) {
104
+ settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
105
+ }
106
+
107
+ // statusLine: always overwrite
108
+ settings["statusLine"] = { type: "command", command: "python .claude/sd-statusline.py" };
109
+
110
+ // SessionStart: ensure sd-session-start hook exists with correct config
111
+ const sdSessionEntry = {
112
+ matcher: "startup|resume|clear|compact",
113
+ hooks: [{ type: "command", command: "bash .claude/sd-session-start.sh" }],
114
+ };
115
+
116
+ const sessionStart = settings["SessionStart"];
117
+
118
+ if (sessionStart == null) {
119
+ settings["SessionStart"] = [sdSessionEntry];
120
+ } else {
121
+ const idx = sessionStart.findIndex((entry) =>
122
+ entry.hooks?.some((hook) => hook.command.includes("sd-session-start")),
123
+ );
124
+ if (idx >= 0) {
125
+ sessionStart[idx] = sdSessionEntry;
126
+ } else {
127
+ sessionStart.push(sdSessionEntry);
128
+ }
129
+ }
130
+
131
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
15
132
  }
@@ -0,0 +1,33 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+
4
+ /**
5
+ * Iterates over sd-* entries in a directory (root level + one level of subdirectories).
6
+ * @param {string} dir - Base directory to scan
7
+ * @param {(relativePath: string) => void} callback - Called with each sd-* entry's relative path
8
+ */
9
+ export function forEachSdEntry(dir, callback) {
10
+ for (const dirent of fs.readdirSync(dir, { withFileTypes: true })) {
11
+ if (dirent.name.startsWith("sd-")) {
12
+ callback(dirent.name);
13
+ } else if (dirent.isDirectory()) {
14
+ const subPath = path.join(dir, dirent.name);
15
+ for (const name of fs.readdirSync(subPath)) {
16
+ if (name.startsWith("sd-")) {
17
+ callback(path.join(dirent.name, name));
18
+ }
19
+ }
20
+ }
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Collects all sd-* entry relative paths from a directory.
26
+ * @param {string} dir - Base directory to scan
27
+ * @returns {string[]} Array of relative paths
28
+ */
29
+ export function collectSdEntries(dir) {
30
+ const entries = [];
31
+ forEachSdEntry(dir, (rel) => entries.push(rel));
32
+ return entries;
33
+ }
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import fs from "fs";
6
6
  import path from "path";
7
+ import { collectSdEntries } from "./sd-entries.mjs";
7
8
 
8
9
  const cliDir = process.cwd();
9
10
  const projectRoot = path.resolve(cliDir, "../..");
@@ -15,31 +16,13 @@ if (fs.existsSync(targetDir)) {
15
16
  fs.rmSync(targetDir, { recursive: true });
16
17
  }
17
18
 
18
- // sd-* 항목 탐색
19
- const allEntries = [];
19
+ // sd-* 항목 탐색 및 복사
20
+ const allEntries = collectSdEntries(claudeDir);
20
21
 
21
- // 루트 레벨: sd-*
22
- for (const name of fs.readdirSync(claudeDir)) {
23
- if (name.startsWith("sd-")) {
24
- allEntries.push(name);
25
- }
26
- }
27
-
28
- // 서브 디렉토리: */sd-*
29
- for (const dirent of fs.readdirSync(claudeDir, { withFileTypes: true })) {
30
- if (!dirent.isDirectory() || dirent.name.startsWith("sd-")) continue;
31
- const subPath = path.join(claudeDir, dirent.name);
32
- for (const name of fs.readdirSync(subPath)) {
33
- if (name.startsWith("sd-")) {
34
- allEntries.push(path.join(dirent.name, name));
35
- }
36
- }
37
- }
38
-
39
- // 복사
40
22
  for (const entry of allEntries) {
41
23
  const src = path.join(claudeDir, entry);
42
24
  const dest = path.join(targetDir, entry);
25
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
43
26
  fs.cpSync(src, dest, { recursive: true });
44
27
  }
45
28
 
@@ -1,2 +0,0 @@
1
- export declare function runAuthAdd(name: string, homeDir?: string): void;
2
- //# sourceMappingURL=auth-add.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"auth-add.d.ts","sourceRoot":"","sources":["../../src/commands/auth-add.ts"],"names":[],"mappings":"AAUA,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAyB/D"}
@@ -1,32 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import {
4
- validateName,
5
- profileExists,
6
- getProfileDir,
7
- readCurrentAuth,
8
- readCurrentCredentials
9
- } from "./auth-utils.js";
10
- function runAuthAdd(name, homeDir) {
11
- validateName(name);
12
- if (profileExists(name, homeDir)) {
13
- throw new Error(
14
- `Profile '${name}' already exists. Remove it first with: sd-claude auth remove ${name}`
15
- );
16
- }
17
- const { oauthAccount, userID } = readCurrentAuth(homeDir);
18
- const credentials = readCurrentCredentials(homeDir);
19
- const profileDir = getProfileDir(name, homeDir);
20
- fs.mkdirSync(profileDir, { recursive: true });
21
- fs.writeFileSync(
22
- path.join(profileDir, "auth.json"),
23
- JSON.stringify({ oauthAccount, userID }, null, 2)
24
- );
25
- fs.writeFileSync(path.join(profileDir, "credentials.json"), JSON.stringify(credentials, null, 2));
26
- const email = oauthAccount["emailAddress"];
27
- console.log(`Saved profile '${name}' (${email ?? userID})`);
28
- }
29
- export {
30
- runAuthAdd
31
- };
32
- //# sourceMappingURL=auth-add.js.map
@@ -1,6 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/commands/auth-add.ts"],
4
- "mappings": "AAAA,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEA,SAAS,WAAW,MAAc,SAAwB;AAC/D,eAAa,IAAI;AAEjB,MAAI,cAAc,MAAM,OAAO,GAAG;AAChC,UAAM,IAAI;AAAA,MACR,YAAY,IAAI,iEAAiE,IAAI;AAAA,IACvF;AAAA,EACF;AAEA,QAAM,EAAE,cAAc,OAAO,IAAI,gBAAgB,OAAO;AACxD,QAAM,cAAc,uBAAuB,OAAO;AAElD,QAAM,aAAa,cAAc,MAAM,OAAO;AAC9C,KAAG,UAAU,YAAY,EAAE,WAAW,KAAK,CAAC;AAE5C,KAAG;AAAA,IACD,KAAK,KAAK,YAAY,WAAW;AAAA,IACjC,KAAK,UAAU,EAAE,cAAc,OAAO,GAAG,MAAM,CAAC;AAAA,EAClD;AAEA,KAAG,cAAc,KAAK,KAAK,YAAY,kBAAkB,GAAG,KAAK,UAAU,aAAa,MAAM,CAAC,CAAC;AAEhG,QAAM,QAAQ,aAAa,cAAc;AAEzC,UAAQ,IAAI,kBAAkB,IAAI,MAAM,SAAS,MAAM,GAAG;AAC5D;",
5
- "names": []
6
- }
@@ -1,2 +0,0 @@
1
- export declare function runAuthList(homeDir?: string): Promise<void>;
2
- //# sourceMappingURL=auth-list.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"auth-list.d.ts","sourceRoot":"","sources":["../../src/commands/auth-list.ts"],"names":[],"mappings":"AAwEA,wBAAsB,WAAW,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAyDjE"}
@@ -1,95 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import { listProfiles, getCurrentUserID, getProfileDir } from "./auth-utils.js";
4
- const FETCH_TIMEOUT_MS = 5e3;
5
- async function fetchUsage(accessToken) {
6
- try {
7
- const controller = new AbortController();
8
- const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
9
- const response = await fetch("https://api.anthropic.com/api/oauth/usage", {
10
- headers: {
11
- Authorization: `Bearer ${accessToken}`,
12
- "anthropic-beta": "oauth-2025-04-20"
13
- },
14
- signal: controller.signal
15
- });
16
- clearTimeout(timeout);
17
- if (!response.ok) {
18
- return void 0;
19
- }
20
- return await response.json();
21
- } catch {
22
- return void 0;
23
- }
24
- }
25
- function formatTimeRemaining(isoDate) {
26
- if (isoDate == null) return "";
27
- try {
28
- const resetTime = new Date(isoDate).getTime();
29
- if (Number.isNaN(resetTime)) return "";
30
- const diffMs = resetTime - Date.now();
31
- if (diffMs <= 0) return "";
32
- const diffMinutes = Math.floor(diffMs / (1e3 * 60));
33
- const diffHours = Math.floor(diffMinutes / 60);
34
- const days = Math.floor(diffHours / 24);
35
- const hours = diffHours % 24;
36
- const minutes = diffMinutes % 60;
37
- if (days > 0) return `${String(days)}d${String(hours)}h`;
38
- if (hours > 0) return `${String(hours)}h${String(minutes)}m`;
39
- return `${String(minutes)}m`;
40
- } catch {
41
- return "";
42
- }
43
- }
44
- function formatUsage(label, data) {
45
- if (data == null) return `${label}: ?`;
46
- const pct = data.utilization != null ? `${String(Math.round(data.utilization))}%` : "?";
47
- const remaining = formatTimeRemaining(data.resets_at);
48
- return remaining ? `${label}: ${pct}(${remaining})` : `${label}: ${pct}`;
49
- }
50
- async function runAuthList(homeDir) {
51
- const profiles = listProfiles(homeDir);
52
- if (profiles.length === 0) {
53
- console.log("No saved profiles.");
54
- return;
55
- }
56
- const currentUserID = getCurrentUserID(homeDir);
57
- const sorted = [...profiles].sort((a, b) => a.localeCompare(b));
58
- const results = await Promise.all(
59
- sorted.map(async (name) => {
60
- const profileDir = getProfileDir(name, homeDir);
61
- const authData = JSON.parse(
62
- fs.readFileSync(path.join(profileDir, "auth.json"), "utf-8")
63
- );
64
- const credData = JSON.parse(
65
- fs.readFileSync(path.join(profileDir, "credentials.json"), "utf-8")
66
- );
67
- const oauthAccount = authData["oauthAccount"];
68
- const email = oauthAccount?.["emailAddress"] ?? "";
69
- const userID = authData["userID"];
70
- const oauth = credData["claudeAiOauth"];
71
- let expiresStr = "unknown";
72
- if (oauth != null && typeof oauth["expiresAt"] === "number") {
73
- const d = new Date(oauth["expiresAt"]);
74
- expiresStr = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
75
- }
76
- const isActive = currentUserID != null && userID === currentUserID;
77
- const prefix = isActive ? "*" : " ";
78
- const accessToken = oauth?.["accessToken"];
79
- const expiresAt = oauth?.["expiresAt"];
80
- const tokenExpired = typeof expiresAt === "number" && Date.now() > expiresAt;
81
- const usage = accessToken != null && !tokenExpired ? await fetchUsage(accessToken) : void 0;
82
- const dailyData = usage?.daily ?? usage?.five_hour;
83
- const fiveHourStr = formatUsage("5h", dailyData);
84
- const weekStr = formatUsage("7d", usage?.seven_day);
85
- return `${prefix} ${name} (${email}) expires: ${expiresStr} \u2502 ${fiveHourStr} \u2502 ${weekStr}`;
86
- })
87
- );
88
- for (const line of results) {
89
- console.log(line);
90
- }
91
- }
92
- export {
93
- runAuthList
94
- };
95
- //# sourceMappingURL=auth-list.js.map
@@ -1,6 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/commands/auth-list.ts"],
4
- "mappings": "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",
5
- "names": []
6
- }
@@ -1,2 +0,0 @@
1
- export declare function runAuthRemove(name: string, homeDir?: string): void;
2
- //# sourceMappingURL=auth-remove.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"auth-remove.d.ts","sourceRoot":"","sources":["../../src/commands/auth-remove.ts"],"names":[],"mappings":"AAIA,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAqBlE"}
@@ -1,22 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import { validateName, profileExists, getProfileDir, getCurrentUserID } from "./auth-utils.js";
4
- function runAuthRemove(name, homeDir) {
5
- validateName(name);
6
- if (!profileExists(name, homeDir)) {
7
- throw new Error(`Profile '${name}' not found.`);
8
- }
9
- const profileDir = getProfileDir(name, homeDir);
10
- const authJsonPath = path.join(profileDir, "auth.json");
11
- const authData = JSON.parse(fs.readFileSync(authJsonPath, "utf-8"));
12
- const currentUserID = getCurrentUserID(homeDir);
13
- if (currentUserID != null && currentUserID === authData.userID) {
14
- console.warn(`Warning: '${name}' is currently active.`);
15
- }
16
- fs.rmSync(profileDir, { recursive: true });
17
- console.log(`Removed profile '${name}'`);
18
- }
19
- export {
20
- runAuthRemove
21
- };
22
- //# sourceMappingURL=auth-remove.js.map
@@ -1,6 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/commands/auth-remove.ts"],
4
- "mappings": "AAAA,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,SAAS,cAAc,eAAe,eAAe,wBAAwB;AAEtE,SAAS,cAAc,MAAc,SAAwB;AAClE,eAAa,IAAI;AAEjB,MAAI,CAAC,cAAc,MAAM,OAAO,GAAG;AACjC,UAAM,IAAI,MAAM,YAAY,IAAI,cAAc;AAAA,EAChD;AAEA,QAAM,aAAa,cAAc,MAAM,OAAO;AAC9C,QAAM,eAAe,KAAK,KAAK,YAAY,WAAW;AACtD,QAAM,WAAW,KAAK,MAAM,GAAG,aAAa,cAAc,OAAO,CAAC;AAElE,QAAM,gBAAgB,iBAAiB,OAAO;AAC9C,MAAI,iBAAiB,QAAQ,kBAAkB,SAAS,QAAQ;AAE9D,YAAQ,KAAK,aAAa,IAAI,wBAAwB;AAAA,EACxD;AAEA,KAAG,OAAO,YAAY,EAAE,WAAW,KAAK,CAAC;AAGzC,UAAQ,IAAI,oBAAoB,IAAI,GAAG;AACzC;",
5
- "names": []
6
- }
@@ -1,2 +0,0 @@
1
- export declare function runAuthUse(name: string, homeDir?: string): void;
2
- //# sourceMappingURL=auth-use.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"auth-use.d.ts","sourceRoot":"","sources":["../../src/commands/auth-use.ts"],"names":[],"mappings":"AAKA,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CA+C/D"}
@@ -1,33 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import os from "os";
4
- import { validateName, profileExists, getProfileDir } from "./auth-utils.js";
5
- function runAuthUse(name, homeDir) {
6
- validateName(name);
7
- if (!profileExists(name, homeDir)) {
8
- throw new Error(`Profile '${name}' not found.`);
9
- }
10
- const profileDir = getProfileDir(name, homeDir);
11
- const home = homeDir ?? os.homedir();
12
- const authJson = JSON.parse(fs.readFileSync(path.join(profileDir, "auth.json"), "utf-8"));
13
- const savedCredentials = JSON.parse(
14
- fs.readFileSync(path.join(profileDir, "credentials.json"), "utf-8")
15
- );
16
- const claudeAiOauth = savedCredentials["claudeAiOauth"];
17
- if (claudeAiOauth?.expiresAt != null && claudeAiOauth.expiresAt < Date.now()) {
18
- console.warn("Warning: Token expired. Run /login after switching.");
19
- }
20
- const claudeJsonPath = path.join(home, ".claude.json");
21
- const claudeData = JSON.parse(fs.readFileSync(claudeJsonPath, "utf-8"));
22
- claudeData["oauthAccount"] = authJson.oauthAccount;
23
- claudeData["userID"] = authJson.userID;
24
- fs.writeFileSync(claudeJsonPath, JSON.stringify(claudeData, null, 2));
25
- const credentialsPath = path.join(home, ".claude", ".credentials.json");
26
- fs.writeFileSync(credentialsPath, JSON.stringify(savedCredentials, null, 2));
27
- const email = authJson.oauthAccount["emailAddress"] ?? "unknown";
28
- console.log(`Switched to ${name} (${email})`);
29
- }
30
- export {
31
- runAuthUse
32
- };
33
- //# sourceMappingURL=auth-use.js.map
@@ -1,6 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/commands/auth-use.ts"],
4
- "mappings": "AAAA,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,OAAO,QAAQ;AACf,SAAS,cAAc,eAAe,qBAAqB;AAEpD,SAAS,WAAW,MAAc,SAAwB;AAC/D,eAAa,IAAI;AAEjB,MAAI,CAAC,cAAc,MAAM,OAAO,GAAG;AACjC,UAAM,IAAI,MAAM,YAAY,IAAI,cAAc;AAAA,EAChD;AAEA,QAAM,aAAa,cAAc,MAAM,OAAO;AAC9C,QAAM,OAAO,WAAW,GAAG,QAAQ;AAGnC,QAAM,WAAW,KAAK,MAAM,GAAG,aAAa,KAAK,KAAK,YAAY,WAAW,GAAG,OAAO,CAAC;AAKxF,QAAM,mBAAmB,KAAK;AAAA,IAC5B,GAAG,aAAa,KAAK,KAAK,YAAY,kBAAkB,GAAG,OAAO;AAAA,EACpE;AAGA,QAAM,gBAAgB,iBAAiB,eAAe;AACtD,MAAI,eAAe,aAAa,QAAQ,cAAc,YAAY,KAAK,IAAI,GAAG;AAE5E,YAAQ,KAAK,qDAAqD;AAAA,EACpE;AAGA,QAAM,iBAAiB,KAAK,KAAK,MAAM,cAAc;AACrD,QAAM,aAAa,KAAK,MAAM,GAAG,aAAa,gBAAgB,OAAO,CAAC;AAKtE,aAAW,cAAc,IAAI,SAAS;AACtC,aAAW,QAAQ,IAAI,SAAS;AAEhC,KAAG,cAAc,gBAAgB,KAAK,UAAU,YAAY,MAAM,CAAC,CAAC;AAGpE,QAAM,kBAAkB,KAAK,KAAK,MAAM,WAAW,mBAAmB;AACtE,KAAG,cAAc,iBAAiB,KAAK,UAAU,kBAAkB,MAAM,CAAC,CAAC;AAG3E,QAAM,QAAS,SAAS,aAAa,cAAc,KAA4B;AAE/E,UAAQ,IAAI,eAAe,IAAI,KAAK,KAAK,GAAG;AAC9C;",
5
- "names": []
6
- }
@@ -1,11 +0,0 @@
1
- export declare function validateName(name: string): void;
2
- export declare function getProfileDir(name: string, homeDir?: string): string;
3
- export declare function profileExists(name: string, homeDir?: string): boolean;
4
- export declare function listProfiles(homeDir?: string): string[];
5
- export declare function readCurrentAuth(homeDir?: string): {
6
- oauthAccount: Record<string, unknown>;
7
- userID: string;
8
- };
9
- export declare function readCurrentCredentials(homeDir?: string): Record<string, unknown>;
10
- export declare function getCurrentUserID(homeDir?: string): string | undefined;
11
- //# sourceMappingURL=auth-utils.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"auth-utils.d.ts","sourceRoot":"","sources":["../../src/commands/auth-utils.ts"],"names":[],"mappings":"AAMA,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAM/C;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAEpE;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAErE;AAED,wBAAgB,YAAY,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAUvD;AAED,wBAAgB,eAAe,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG;IACjD,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,MAAM,EAAE,MAAM,CAAC;CAChB,CAYA;AAED,wBAAgB,sBAAsB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAGhF;AAED,wBAAgB,gBAAgB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAQrE"}
@@ -1,57 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import os from "os";
4
- const NAME_PATTERN = /^[a-z0-9_-]+$/;
5
- function validateName(name) {
6
- if (!NAME_PATTERN.test(name)) {
7
- throw new Error(
8
- `Invalid name '${name}'. Use only lowercase letters, numbers, hyphens, underscores.`
9
- );
10
- }
11
- }
12
- function getProfileDir(name, homeDir) {
13
- return path.join(homeDir ?? os.homedir(), ".sd-claude", "auth", name);
14
- }
15
- function profileExists(name, homeDir) {
16
- return fs.existsSync(getProfileDir(name, homeDir));
17
- }
18
- function listProfiles(homeDir) {
19
- const authDir = path.join(homeDir ?? os.homedir(), ".sd-claude", "auth");
20
- if (!fs.existsSync(authDir)) {
21
- return [];
22
- }
23
- return fs.readdirSync(authDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
24
- }
25
- function readCurrentAuth(homeDir) {
26
- const claudeJsonPath = path.join(homeDir ?? os.homedir(), ".claude.json");
27
- const data = JSON.parse(fs.readFileSync(claudeJsonPath, "utf-8"));
28
- const oauthAccount = data["oauthAccount"];
29
- const userID = data["userID"];
30
- if (oauthAccount == null || userID == null) {
31
- throw new Error("Not logged in. Run /login first.");
32
- }
33
- return { oauthAccount, userID };
34
- }
35
- function readCurrentCredentials(homeDir) {
36
- const credentialsPath = path.join(homeDir ?? os.homedir(), ".claude", ".credentials.json");
37
- return JSON.parse(fs.readFileSync(credentialsPath, "utf-8"));
38
- }
39
- function getCurrentUserID(homeDir) {
40
- const claudeJsonPath = path.join(homeDir ?? os.homedir(), ".claude.json");
41
- try {
42
- const data = JSON.parse(fs.readFileSync(claudeJsonPath, "utf-8"));
43
- return data["userID"];
44
- } catch {
45
- return void 0;
46
- }
47
- }
48
- export {
49
- getCurrentUserID,
50
- getProfileDir,
51
- listProfiles,
52
- profileExists,
53
- readCurrentAuth,
54
- readCurrentCredentials,
55
- validateName
56
- };
57
- //# sourceMappingURL=auth-utils.js.map