@simplysm/sd-claude 13.0.72 → 13.0.74

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.
@@ -0,0 +1,88 @@
1
+ # Code Simplifier Prompt
2
+
3
+ Template for `Agent(general-purpose)`. Fill in `[TARGET_PATH]`.
4
+
5
+ ```
6
+ You are a maintainability analyst reviewing code structure.
7
+ Your question: "Would a developer struggle to understand or modify this code?"
8
+
9
+ ## Target
10
+
11
+ Review ALL source files at [TARGET_PATH].
12
+
13
+ ## Step 1: List all source files
14
+
15
+ Use Glob to list all .ts files under the target path (exclude node_modules, dist).
16
+
17
+ ## Step 2: Understand the structure
18
+
19
+ - Map module dependencies and abstraction layers
20
+ - Compare whether similar-role files use consistent patterns
21
+ - Identify complexity hotspots: deep nesting, long functions, complex conditionals
22
+
23
+ ## Step 3: Find issues
24
+
25
+ Look for:
26
+ - Unnecessary complexity: over-abstraction, needless indirection, complex generics
27
+ - Duplication: same logic repeated, similar functions that could be unified
28
+ - Readability: hard-to-follow control flow, unclear variable names, implicit behavior
29
+ - Structure: too many files for simple concepts, or too many responsibilities in one file
30
+ - Maintainability risk: changes that cascade widely, tightly coupled modules
31
+
32
+ Do NOT report:
33
+ - Bugs, security, logic errors → that's the code reviewer's job
34
+ - Naming consistency, API design, type quality (including `any` types) → that's the API reviewer's job
35
+ - Property shorthand (`uuid: uuid` vs `uuid`)
36
+ - `else` after `return`
37
+ - Comment style or JSDoc presence/absence
38
+ - Import ordering or formatting preferences
39
+ - Magic numbers that are well-explained by adjacent comments
40
+ - Small interface duplication (< 10 fields) where extracting a base adds indirection without real benefit
41
+ - Type placement across packages unless it causes concrete import/dependency issues
42
+ - Issues in code OUTSIDE the target path (e.g., how other packages implement or consume these types)
43
+
44
+ ## Step 4: Self-verify before reporting
45
+
46
+ Before including ANY finding:
47
+
48
+ 1. **Impact test**: Would a developer actually struggle with this? Or is it just "could be slightly cleaner"?
49
+ 2. **Scope check**: Is the issue IN the target code, or in how other code uses it?
50
+ 3. **Overlap check**: Is this already in the code reviewer's or API reviewer's domain? If yes, skip it.
51
+
52
+ **Quality over quantity: 3 verified findings > 10 maybe-findings.**
53
+
54
+ ## Constraints
55
+
56
+ - Analysis only. Do NOT modify any files.
57
+ - Do NOT provide corrected code blocks. Describe issues and suggestions in words only.
58
+ - Only report issues with real evidence from the code.
59
+ - Focus on substance: structural issues that genuinely make the code hard to understand or modify.
60
+ - Do NOT report findings that belong to other reviewers' scope.
61
+
62
+ ## Output Format
63
+
64
+ Use this exact format for every finding:
65
+
66
+ ### [CRITICAL|WARNING|INFO] title
67
+
68
+ - **File**: path/to/file.ts:42
69
+ - **Evidence**: what you observed (include code snippet)
70
+ - **Issue**: what the problem is
71
+ - **Suggestion**: how to improve it (in words, not code)
72
+
73
+ Severity:
74
+ - CRITICAL: Major structural problem. Very hard to understand or modify safely.
75
+ - WARNING: Significant maintainability concern. Unnecessary complexity or duplication.
76
+ - INFO: Improvement opportunity. Cleaner approach exists but current code is workable.
77
+
78
+ Start your report with:
79
+
80
+ ## Maintainability Review Results
81
+
82
+ ### Summary
83
+ - Files reviewed: N
84
+ - Findings: X CRITICAL, Y WARNING, Z INFO
85
+
86
+ ### Findings
87
+ [findings here]
88
+ ```
@@ -1,2 +1,2 @@
1
- export declare function runAuthList(homeDir?: string): void;
1
+ export declare function runAuthList(homeDir?: string): Promise<void>;
2
2
  //# sourceMappingURL=auth-list.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth-list.d.ts","sourceRoot":"","sources":["../../src/commands/auth-list.ts"],"names":[],"mappings":"AAIA,wBAAgB,WAAW,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAuClD"}
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,7 +1,53 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
3
  import { listProfiles, getCurrentUserID, getProfileDir } from "./auth-utils.js";
4
- function runAuthList(homeDir) {
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) {
5
51
  const profiles = listProfiles(homeDir);
6
52
  if (profiles.length === 0) {
7
53
  console.log("No saved profiles.");
@@ -9,26 +55,38 @@ function runAuthList(homeDir) {
9
55
  }
10
56
  const currentUserID = getCurrentUserID(homeDir);
11
57
  const sorted = [...profiles].sort((a, b) => a.localeCompare(b));
12
- for (const name of sorted) {
13
- const profileDir = getProfileDir(name, homeDir);
14
- const authData = JSON.parse(
15
- fs.readFileSync(path.join(profileDir, "auth.json"), "utf-8")
16
- );
17
- const credData = JSON.parse(
18
- fs.readFileSync(path.join(profileDir, "credentials.json"), "utf-8")
19
- );
20
- const oauthAccount = authData["oauthAccount"];
21
- const email = oauthAccount?.["emailAddress"] ?? "";
22
- const userID = authData["userID"];
23
- const oauth = credData["claudeAiOauth"];
24
- let expiresStr = "unknown";
25
- if (oauth != null && typeof oauth["expiresAt"] === "number") {
26
- const d = new Date(oauth["expiresAt"]);
27
- expiresStr = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
28
- }
29
- const isActive = currentUserID != null && userID === currentUserID;
30
- const prefix = isActive ? "*" : " ";
31
- console.log(`${prefix} ${name} (${email}) expires: ${expiresStr}`);
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);
32
90
  }
33
91
  }
34
92
  export {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/commands/auth-list.ts"],
4
- "mappings": "AAAA,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,SAAS,cAAc,kBAAkB,qBAAqB;AAEvD,SAAS,YAAY,SAAwB;AAClD,QAAM,WAAW,aAAa,OAAO;AAErC,MAAI,SAAS,WAAW,GAAG;AAEzB,YAAQ,IAAI,oBAAoB;AAChC;AAAA,EACF;AAEA,QAAM,gBAAgB,iBAAiB,OAAO;AAC9C,QAAM,SAAS,CAAC,GAAG,QAAQ,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,cAAc,CAAC,CAAC;AAE9D,aAAW,QAAQ,QAAQ;AACzB,UAAM,aAAa,cAAc,MAAM,OAAO;AAE9C,UAAM,WAAW,KAAK;AAAA,MACpB,GAAG,aAAa,KAAK,KAAK,YAAY,WAAW,GAAG,OAAO;AAAA,IAC7D;AAEA,UAAM,WAAW,KAAK;AAAA,MACpB,GAAG,aAAa,KAAK,KAAK,YAAY,kBAAkB,GAAG,OAAO;AAAA,IACpE;AAEA,UAAM,eAAe,SAAS,cAAc;AAC5C,UAAM,QAAS,eAAe,cAAc,KAA4B;AACxE,UAAM,SAAS,SAAS,QAAQ;AAChC,UAAM,QAAQ,SAAS,eAAe;AACtC,QAAI,aAAa;AACjB,QAAI,SAAS,QAAQ,OAAO,MAAM,WAAW,MAAM,UAAU;AAC3D,YAAM,IAAI,IAAI,KAAK,MAAM,WAAW,CAAC;AACrC,mBAAa,GAAG,EAAE,YAAY,CAAC,IAAI,OAAO,EAAE,SAAS,IAAI,CAAC,EAAE,SAAS,GAAG,GAAG,CAAC,IAAI,OAAO,EAAE,QAAQ,CAAC,EAAE,SAAS,GAAG,GAAG,CAAC;AAAA,IACtH;AAEA,UAAM,WAAW,iBAAiB,QAAQ,WAAW;AACrD,UAAM,SAAS,WAAW,MAAM;AAGhC,YAAQ,IAAI,GAAG,MAAM,IAAI,IAAI,KAAK,KAAK,cAAc,UAAU,EAAE;AAAA,EACnE;AACF;",
4
+ "mappings": "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",
5
5
  "names": []
6
6
  }
package/dist/sd-claude.js CHANGED
@@ -50,9 +50,9 @@ await yargs(hideBin(process.argv)).help("help", "Help").alias("help", "h").comma
50
50
  "list",
51
51
  "Displays the list of saved accounts",
52
52
  (sub) => sub,
53
- () => {
53
+ async () => {
54
54
  try {
55
- runAuthList();
55
+ await runAuthList();
56
56
  } catch (err) {
57
57
  console.error(err.message);
58
58
  process.exit(1);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/sd-claude.ts"],
4
- "mappings": ";AAEA,OAAO,WAAW;AAClB,SAAS,eAAe;AACxB,SAAS,kBAAkB;AAC3B,SAAS,kBAAkB;AAC3B,SAAS,kBAAkB;AAC3B,SAAS,mBAAmB;AAC5B,SAAS,qBAAqB;AAE9B,MAAM,MAAM,QAAQ,QAAQ,IAAI,CAAC,EAC9B,KAAK,QAAQ,MAAM,EACnB,MAAM,QAAQ,GAAG,EACjB;AAAA,EACC;AAAA,EACA;AAAA,EACA,CAAC,QAAQ,IAAI,QAAQ,KAAK,EAAE,KAAK,MAAM;AAAA,EACvC,MAAM;AACJ,eAAW;AAAA,EACb;AACF,EACC;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAoC,CAAC,QACpD,IACG,QAAQ,KAAK,EACb,KAAK,MAAM,EACX;AAAA,IACC;AAAA,IACA;AAAA,IACA,CAAC,QACC,IAAI,WAAW,QAAQ;AAAA,MACrB,MAAM;AAAA,MACN,cAAc;AAAA,IAChB,CAAC;AAAA,IACH,CAAC,SAAS;AACR,UAAI;AACF,mBAAW,KAAK,IAAI;AAAA,MACtB,SAAS,KAAK;AAEZ,gBAAQ,MAAO,IAAc,OAAO;AACpC,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAAA,IACF;AAAA,EACF,EACC;AAAA,IACC;AAAA,IACA;AAAA,IACA,CAAC,QACC,IAAI,WAAW,QAAQ;AAAA,MACrB,MAAM;AAAA,MACN,cAAc;AAAA,IAChB,CAAC;AAAA,IACH,CAAC,SAAS;AACR,UAAI;AACF,mBAAW,KAAK,IAAI;AAAA,MACtB,SAAS,KAAK;AAEZ,gBAAQ,MAAO,IAAc,OAAO;AACpC,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAAA,IACF;AAAA,EACF,EACC;AAAA,IACC;AAAA,IACA;AAAA,IACA,CAAC,QAAQ;AAAA,IACT,MAAM;AACJ,UAAI;AACF,oBAAY;AAAA,MACd,SAAS,KAAK;AAEZ,gBAAQ,MAAO,IAAc,OAAO;AACpC,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAAA,IACF;AAAA,EACF,EACC;AAAA,IACC;AAAA,IACA;AAAA,IACA,CAAC,QACC,IAAI,WAAW,QAAQ;AAAA,MACrB,MAAM;AAAA,MACN,cAAc;AAAA,IAChB,CAAC;AAAA,IACH,CAAC,SAAS;AACR,UAAI;AACF,sBAAc,KAAK,IAAI;AAAA,MACzB,SAAS,KAAK;AAEZ,gBAAQ,MAAO,IAAc,OAAO;AACpC,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAAA,IACF;AAAA,EACF,EACC,cAAc,GAAG,oCAAoC;AAC1D,EACC,cAAc,GAAG,2BAA2B,EAC5C,OAAO,EACP,MAAM;",
4
+ "mappings": ";AAEA,OAAO,WAAW;AAClB,SAAS,eAAe;AACxB,SAAS,kBAAkB;AAC3B,SAAS,kBAAkB;AAC3B,SAAS,kBAAkB;AAC3B,SAAS,mBAAmB;AAC5B,SAAS,qBAAqB;AAE9B,MAAM,MAAM,QAAQ,QAAQ,IAAI,CAAC,EAC9B,KAAK,QAAQ,MAAM,EACnB,MAAM,QAAQ,GAAG,EACjB;AAAA,EACC;AAAA,EACA;AAAA,EACA,CAAC,QAAQ,IAAI,QAAQ,KAAK,EAAE,KAAK,MAAM;AAAA,EACvC,MAAM;AACJ,eAAW;AAAA,EACb;AACF,EACC;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAoC,CAAC,QACpD,IACG,QAAQ,KAAK,EACb,KAAK,MAAM,EACX;AAAA,IACC;AAAA,IACA;AAAA,IACA,CAAC,QACC,IAAI,WAAW,QAAQ;AAAA,MACrB,MAAM;AAAA,MACN,cAAc;AAAA,IAChB,CAAC;AAAA,IACH,CAAC,SAAS;AACR,UAAI;AACF,mBAAW,KAAK,IAAI;AAAA,MACtB,SAAS,KAAK;AAEZ,gBAAQ,MAAO,IAAc,OAAO;AACpC,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAAA,IACF;AAAA,EACF,EACC;AAAA,IACC;AAAA,IACA;AAAA,IACA,CAAC,QACC,IAAI,WAAW,QAAQ;AAAA,MACrB,MAAM;AAAA,MACN,cAAc;AAAA,IAChB,CAAC;AAAA,IACH,CAAC,SAAS;AACR,UAAI;AACF,mBAAW,KAAK,IAAI;AAAA,MACtB,SAAS,KAAK;AAEZ,gBAAQ,MAAO,IAAc,OAAO;AACpC,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAAA,IACF;AAAA,EACF,EACC;AAAA,IACC;AAAA,IACA;AAAA,IACA,CAAC,QAAQ;AAAA,IACT,YAAY;AACV,UAAI;AACF,cAAM,YAAY;AAAA,MACpB,SAAS,KAAK;AAEZ,gBAAQ,MAAO,IAAc,OAAO;AACpC,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAAA,IACF;AAAA,EACF,EACC;AAAA,IACC;AAAA,IACA;AAAA,IACA,CAAC,QACC,IAAI,WAAW,QAAQ;AAAA,MACrB,MAAM;AAAA,MACN,cAAc;AAAA,IAChB,CAAC;AAAA,IACH,CAAC,SAAS;AACR,UAAI;AACF,sBAAc,KAAK,IAAI;AAAA,MACzB,SAAS,KAAK;AAEZ,gBAAQ,MAAO,IAAc,OAAO;AACpC,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAAA,IACF;AAAA,EACF,EACC,cAAc,GAAG,oCAAoC;AAC1D,EACC,cAAc,GAAG,2BAA2B,EAC5C,OAAO,EACP,MAAM;",
5
5
  "names": []
6
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simplysm/sd-claude",
3
- "version": "13.0.72",
3
+ "version": "13.0.74",
4
4
  "description": "Simplysm Claude Code CLI — asset installer",
5
5
  "author": "simplysm",
6
6
  "license": "Apache-2.0",
@@ -2,7 +2,75 @@ import fs from "fs";
2
2
  import path from "path";
3
3
  import { listProfiles, getCurrentUserID, getProfileDir } from "./auth-utils.js";
4
4
 
5
- export function runAuthList(homeDir?: string): void {
5
+ const FETCH_TIMEOUT_MS = 5000;
6
+
7
+ interface UsageData {
8
+ utilization?: number;
9
+ resets_at?: string;
10
+ }
11
+
12
+ interface UsageResponse {
13
+ five_hour?: UsageData;
14
+ daily?: UsageData;
15
+ seven_day?: UsageData;
16
+ }
17
+
18
+ async function fetchUsage(accessToken: string): Promise<UsageResponse | undefined> {
19
+ try {
20
+ const controller = new AbortController();
21
+ const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
22
+
23
+ const response = await fetch("https://api.anthropic.com/api/oauth/usage", {
24
+ headers: {
25
+ Authorization: `Bearer ${accessToken}`,
26
+ "anthropic-beta": "oauth-2025-04-20",
27
+ },
28
+ signal: controller.signal,
29
+ });
30
+
31
+ clearTimeout(timeout);
32
+
33
+ if (!response.ok) {
34
+ return undefined;
35
+ }
36
+
37
+ return (await response.json()) as UsageResponse;
38
+ } catch {
39
+ return undefined;
40
+ }
41
+ }
42
+
43
+ function formatTimeRemaining(isoDate: string | undefined): string {
44
+ if (isoDate == null) return "";
45
+ try {
46
+ const resetTime = new Date(isoDate).getTime();
47
+ if (Number.isNaN(resetTime)) return "";
48
+
49
+ const diffMs = resetTime - Date.now();
50
+ if (diffMs <= 0) return "";
51
+
52
+ const diffMinutes = Math.floor(diffMs / (1000 * 60));
53
+ const diffHours = Math.floor(diffMinutes / 60);
54
+ const days = Math.floor(diffHours / 24);
55
+ const hours = diffHours % 24;
56
+ const minutes = diffMinutes % 60;
57
+
58
+ if (days > 0) return `${String(days)}d${String(hours)}h`;
59
+ if (hours > 0) return `${String(hours)}h${String(minutes)}m`;
60
+ return `${String(minutes)}m`;
61
+ } catch {
62
+ return "";
63
+ }
64
+ }
65
+
66
+ function formatUsage(label: string, data: UsageData | undefined): string {
67
+ if (data == null) return `${label}: ?`;
68
+ const pct = data.utilization != null ? `${String(Math.round(data.utilization))}%` : "?";
69
+ const remaining = formatTimeRemaining(data.resets_at);
70
+ return remaining ? `${label}: ${pct}(${remaining})` : `${label}: ${pct}`;
71
+ }
72
+
73
+ export async function runAuthList(homeDir?: string): Promise<void> {
6
74
  const profiles = listProfiles(homeDir);
7
75
 
8
76
  if (profiles.length === 0) {
@@ -14,31 +82,49 @@ export function runAuthList(homeDir?: string): void {
14
82
  const currentUserID = getCurrentUserID(homeDir);
15
83
  const sorted = [...profiles].sort((a, b) => a.localeCompare(b));
16
84
 
17
- for (const name of sorted) {
18
- const profileDir = getProfileDir(name, homeDir);
19
-
20
- const authData = JSON.parse(
21
- fs.readFileSync(path.join(profileDir, "auth.json"), "utf-8"),
22
- ) as Record<string, unknown>;
23
-
24
- const credData = JSON.parse(
25
- fs.readFileSync(path.join(profileDir, "credentials.json"), "utf-8"),
26
- ) as Record<string, unknown>;
27
-
28
- const oauthAccount = authData["oauthAccount"] as Record<string, unknown> | undefined;
29
- const email = (oauthAccount?.["emailAddress"] as string | undefined) ?? "";
30
- const userID = authData["userID"] as string | undefined;
31
- const oauth = credData["claudeAiOauth"] as Record<string, unknown> | undefined;
32
- let expiresStr = "unknown";
33
- if (oauth != null && typeof oauth["expiresAt"] === "number") {
34
- const d = new Date(oauth["expiresAt"]);
35
- expiresStr = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
36
- }
85
+ const results = await Promise.all(
86
+ sorted.map(async (name) => {
87
+ const profileDir = getProfileDir(name, homeDir);
88
+
89
+ const authData = JSON.parse(
90
+ fs.readFileSync(path.join(profileDir, "auth.json"), "utf-8"),
91
+ ) as Record<string, unknown>;
92
+
93
+ const credData = JSON.parse(
94
+ fs.readFileSync(path.join(profileDir, "credentials.json"), "utf-8"),
95
+ ) as Record<string, unknown>;
96
+
97
+ const oauthAccount = authData["oauthAccount"] as Record<string, unknown> | undefined;
98
+ const email = (oauthAccount?.["emailAddress"] as string | undefined) ?? "";
99
+ const userID = authData["userID"] as string | undefined;
100
+ const oauth = credData["claudeAiOauth"] as Record<string, unknown> | undefined;
101
+
102
+ let expiresStr = "unknown";
103
+ if (oauth != null && typeof oauth["expiresAt"] === "number") {
104
+ const d = new Date(oauth["expiresAt"]);
105
+ expiresStr = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
106
+ }
107
+
108
+ const isActive = currentUserID != null && userID === currentUserID;
109
+ const prefix = isActive ? "*" : " ";
110
+
111
+ // Fetch usage via OAuth token
112
+ const accessToken = oauth?.["accessToken"] as string | undefined;
113
+ const expiresAt = oauth?.["expiresAt"];
114
+ const tokenExpired = typeof expiresAt === "number" && Date.now() > expiresAt;
115
+ const usage =
116
+ accessToken != null && !tokenExpired ? await fetchUsage(accessToken) : undefined;
117
+
118
+ const dailyData = usage?.daily ?? usage?.five_hour;
119
+ const fiveHourStr = formatUsage("5h", dailyData);
120
+ const weekStr = formatUsage("7d", usage?.seven_day);
37
121
 
38
- const isActive = currentUserID != null && userID === currentUserID;
39
- const prefix = isActive ? "*" : " ";
122
+ return `${prefix} ${name} (${email}) expires: ${expiresStr} ${fiveHourStr} ${weekStr}`;
123
+ }),
124
+ );
40
125
 
126
+ for (const line of results) {
41
127
  // eslint-disable-next-line no-console
42
- console.log(`${prefix} ${name} (${email}) expires: ${expiresStr}`);
128
+ console.log(line);
43
129
  }
44
130
  }
package/src/sd-claude.ts CHANGED
@@ -63,9 +63,9 @@ await yargs(hideBin(process.argv))
63
63
  "list",
64
64
  "Displays the list of saved accounts",
65
65
  (sub) => sub,
66
- () => {
66
+ async () => {
67
67
  try {
68
- runAuthList();
68
+ await runAuthList();
69
69
  } catch (err) {
70
70
  // eslint-disable-next-line no-console
71
71
  console.error((err as Error).message);
@@ -4,6 +4,15 @@ import fs from "fs";
4
4
  import os from "os";
5
5
  import { runAuthList } from "../src/commands/auth-list";
6
6
 
7
+ // Mock global fetch to prevent real API calls
8
+ vi.stubGlobal(
9
+ "fetch",
10
+ vi.fn().mockResolvedValue({
11
+ ok: false,
12
+ json: () => Promise.resolve({}),
13
+ }),
14
+ );
15
+
7
16
  describe("runAuthList", () => {
8
17
  let tmpDir: string;
9
18
 
@@ -16,15 +25,15 @@ describe("runAuthList", () => {
16
25
  vi.restoreAllMocks();
17
26
  });
18
27
 
19
- test("outputs 'No saved profiles.' when no profiles exist", () => {
28
+ test("outputs 'No saved profiles.' when no profiles exist", async () => {
20
29
  const spy = vi.spyOn(console, "log").mockImplementation(() => {});
21
30
 
22
- runAuthList(tmpDir);
31
+ await runAuthList(tmpDir);
23
32
 
24
33
  expect(spy).toHaveBeenCalledWith("No saved profiles.");
25
34
  });
26
35
 
27
- test("outputs profiles sorted alphabetically with active marker", () => {
36
+ test("outputs profiles sorted alphabetically with active marker", async () => {
28
37
  const authDir = path.join(tmpDir, ".sd-claude", "auth");
29
38
 
30
39
  // Create profile "beta"
@@ -62,16 +71,22 @@ describe("runAuthList", () => {
62
71
 
63
72
  const spy = vi.spyOn(console, "log").mockImplementation(() => {});
64
73
 
65
- runAuthList(tmpDir);
74
+ await runAuthList(tmpDir);
66
75
 
67
76
  expect(spy).toHaveBeenCalledTimes(2);
68
- // alpha comes first (alphabetical), and is active
69
- expect(spy).toHaveBeenNthCalledWith(1, "* alpha (alpha@example.com) expires: 2025-06-25");
77
+ // alpha comes first (alphabetical), and is active; usage shows ? when fetch fails
78
+ expect(spy).toHaveBeenNthCalledWith(
79
+ 1,
80
+ "* alpha (alpha@example.com) expires: 2025-06-25 │ 5h: ? │ 7d: ?",
81
+ );
70
82
  // beta is not active
71
- expect(spy).toHaveBeenNthCalledWith(2, " beta (beta@example.com) expires: 2025-06-20");
83
+ expect(spy).toHaveBeenNthCalledWith(
84
+ 2,
85
+ " beta (beta@example.com) expires: 2025-06-20 │ 5h: ? │ 7d: ?",
86
+ );
72
87
  });
73
88
 
74
- test("shows email even when organizationName is missing", () => {
89
+ test("shows email even when organizationName is missing", async () => {
75
90
  const authDir = path.join(tmpDir, ".sd-claude", "auth");
76
91
 
77
92
  const profileDir = path.join(authDir, "personal");
@@ -90,12 +105,14 @@ describe("runAuthList", () => {
90
105
 
91
106
  const spy = vi.spyOn(console, "log").mockImplementation(() => {});
92
107
 
93
- runAuthList(tmpDir);
108
+ await runAuthList(tmpDir);
94
109
 
95
- expect(spy).toHaveBeenCalledWith(" personal (user@gmail.com) expires: 2025-07-01");
110
+ expect(spy).toHaveBeenCalledWith(
111
+ " personal (user@gmail.com) expires: 2025-07-01 │ 5h: ? │ 7d: ?",
112
+ );
96
113
  });
97
114
 
98
- test("shows 'unknown' when expiresAt is missing", () => {
115
+ test("shows 'unknown' when expiresAt is missing", async () => {
99
116
  const authDir = path.join(tmpDir, ".sd-claude", "auth");
100
117
 
101
118
  const profileDir = path.join(authDir, "noexpiry");
@@ -111,12 +128,14 @@ describe("runAuthList", () => {
111
128
 
112
129
  const spy = vi.spyOn(console, "log").mockImplementation(() => {});
113
130
 
114
- runAuthList(tmpDir);
131
+ await runAuthList(tmpDir);
115
132
 
116
- expect(spy).toHaveBeenCalledWith(" noexpiry (noexp@example.com) expires: unknown");
133
+ expect(spy).toHaveBeenCalledWith(
134
+ " noexpiry (noexp@example.com) expires: unknown │ 5h: ? │ 7d: ?",
135
+ );
117
136
  });
118
137
 
119
- test("marks active profile with * when userID matches", () => {
138
+ test("marks active profile with * when userID matches", async () => {
120
139
  const authDir = path.join(tmpDir, ".sd-claude", "auth");
121
140
 
122
141
  const profileDir = path.join(authDir, "work");
@@ -138,12 +157,14 @@ describe("runAuthList", () => {
138
157
 
139
158
  const spy = vi.spyOn(console, "log").mockImplementation(() => {});
140
159
 
141
- runAuthList(tmpDir);
160
+ await runAuthList(tmpDir);
142
161
 
143
- expect(spy).toHaveBeenCalledWith("* work (work@company.com) expires: 2025-12-31");
162
+ expect(spy).toHaveBeenCalledWith(
163
+ "* work (work@company.com) expires: 2025-12-31 │ 5h: ? │ 7d: ?",
164
+ );
144
165
  });
145
166
 
146
- test("non-active profile has space prefix instead of *", () => {
167
+ test("non-active profile has space prefix instead of *", async () => {
147
168
  const authDir = path.join(tmpDir, ".sd-claude", "auth");
148
169
 
149
170
  const profileDir = path.join(authDir, "other");
@@ -168,8 +189,10 @@ describe("runAuthList", () => {
168
189
 
169
190
  const spy = vi.spyOn(console, "log").mockImplementation(() => {});
170
191
 
171
- runAuthList(tmpDir);
192
+ await runAuthList(tmpDir);
172
193
 
173
- expect(spy).toHaveBeenCalledWith(" other (other@example.com) expires: 2025-08-15");
194
+ expect(spy).toHaveBeenCalledWith(
195
+ " other (other@example.com) expires: 2025-08-15 │ 5h: ? │ 7d: ?",
196
+ );
174
197
  });
175
198
  });
@@ -1,81 +0,0 @@
1
- ---
2
- name: sd-api-reviewer
3
- description: Reviews a library's public API for developer experience (DX) quality - naming consistency, industry standard alignment, intuitiveness, error messages, type hints, configuration complexity, and usage pattern coherence
4
- ---
5
-
6
- You are an expert API/DX reviewer who evaluates libraries from the **consumer's perspective**. Your goal is to identify friction points that developers encounter when using a package.
7
-
8
- ## Review Scope
9
-
10
- Analyze the specified package's public API surface (exports, types, configuration). The user will provide the target path.
11
-
12
- ## Core Review Responsibilities
13
-
14
- ### 1. Naming Review
15
-
16
- - **Industry standard comparison**: Compare naming patterns against major libraries in the same domain (use WebSearch)
17
- - **Internal consistency**: Same concept with different names, same pattern with different prefixes/suffixes
18
- - **Intuitiveness**: Whether the behavior can be predicted from the name alone
19
- - **Internal consistency over external standards**: Before suggesting a naming change, verify the existing pattern across ALL similar components in the library. If the library consistently uses one convention (e.g., `value`/`onValueChange` for all form controls), do NOT suggest an industry-standard alternative (e.g., `checked`/`onCheckedChange`) that would break internal consistency.
20
-
21
- ### 2. API Intuitiveness
22
-
23
- - **Learning curve**: Whether a first-time developer can use it without documentation
24
- - **Principle of least surprise**: APIs that behave differently than expected
25
- - **Default value quality**: Whether most use cases work without additional configuration
26
-
27
- ### 3. Type Hints & Error Messages
28
-
29
- - **Type sufficiency**: Whether enough type information is provided for autocompletion and compile-time validation
30
- - **Error message quality**: Whether error messages guide the user to the cause and solution
31
- - **Generic usage**: Whether type inference works naturally
32
-
33
- ### 4. Configuration & Boilerplate
34
-
35
- - **Configuration complexity**: Whether basic usage requires excessive setup
36
- - **Boilerplate**: Whether too much repetitive code is needed
37
- - **Progressive complexity**: Whether it scales naturally from simple to advanced usage
38
-
39
- ### 5. Usage Pattern Coherence
40
-
41
- - **Pattern consistency**: Whether similar tasks use similar patterns
42
- - **Composition**: Whether features combine naturally
43
- - **Escape hatch**: Whether there are ways to break out of framework constraints when needed
44
-
45
- ## Confidence Scoring
46
-
47
- Rate each issue 0-100:
48
-
49
- - **0**: False positive or subjective preference
50
- - **25**: Minor friction, workaround is obvious
51
- - **50**: Real friction but not blocking
52
- - **75**: Significant DX issue, developers will struggle
53
- - **100**: Critical — developers will misuse or give up
54
-
55
- **Only report issues with confidence >= 70.**
56
-
57
- ## Output Format
58
-
59
- Start with a brief summary of the package's public API surface.
60
-
61
- ### Findings by Category
62
-
63
- For each high-confidence issue:
64
-
65
- - Clear description with confidence score
66
- - File path and relevant export/type
67
- - Comparison with industry standard libraries (if applicable)
68
- - Concrete improvement suggestion
69
-
70
- ### Priority
71
-
72
- | Priority | Criteria |
73
- | -------- | -------------------------------------------------------------- |
74
- | **P0** | API misuse likely — naming misleads or types insufficient |
75
- | **P1** | Significant friction — unnecessary complexity or inconsistency |
76
- | **P2** | Minor improvement — better naming or defaults exist |
77
- | **Keep** | Already aligned with standards |
78
-
79
- ### Summary Table
80
-
81
- End with a table: current API, suggested change, priority, rationale.