@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.
- package/README.md +286 -13
- package/claude/refs/sd-code-conventions.md +11 -0
- package/claude/rules/sd-claude-rules.md +13 -2
- package/claude/skills/sd-brainstorm/SKILL.md +1 -1
- package/claude/skills/sd-check/SKILL.md +15 -6
- package/claude/skills/sd-commit/SKILL.md +2 -0
- package/claude/skills/sd-debug/find-polluter.sh +8 -2
- package/claude/skills/sd-debug/root-cause-tracing.md +2 -2
- package/claude/skills/sd-plan/SKILL.md +11 -2
- package/claude/skills/sd-plan-dev/SKILL.md +5 -3
- package/claude/skills/sd-plan-dev/final-review-prompt.md +3 -3
- package/claude/skills/sd-readme/SKILL.md +86 -106
- package/claude/skills/sd-review/SKILL.md +58 -62
- package/claude/skills/sd-review/api-reviewer-prompt.md +90 -0
- package/claude/skills/sd-review/code-reviewer-prompt.md +85 -0
- package/claude/skills/sd-review/code-simplifier-prompt.md +88 -0
- package/dist/commands/auth-list.d.ts +1 -1
- package/dist/commands/auth-list.d.ts.map +1 -1
- package/dist/commands/auth-list.js +79 -21
- package/dist/commands/auth-list.js.map +1 -1
- package/dist/sd-claude.js +2 -2
- package/dist/sd-claude.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/auth-list.ts +110 -24
- package/src/sd-claude.ts +2 -2
- package/tests/auth-list.spec.ts +42 -19
- package/claude/agents/sd-api-reviewer.md +0 -81
- package/claude/agents/sd-code-reviewer.md +0 -48
- package/claude/agents/sd-code-simplifier.md +0 -47
- package/claude/agents/sd-security-reviewer.md +0 -92
|
@@ -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":"
|
|
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
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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;
|
|
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);
|
package/dist/sd-claude.js.map
CHANGED
|
@@ -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,
|
|
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
|
@@ -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
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
39
|
-
|
|
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(
|
|
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);
|
package/tests/auth-list.spec.ts
CHANGED
|
@@ -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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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.
|