@simplysm/sd-claude 13.0.78 → 13.0.81
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/claude/rules/sd-claude-rules.md +4 -63
- package/claude/rules/sd-simplysm-usage.md +7 -0
- package/claude/sd-session-start.sh +10 -0
- package/claude/sd-statusline.py +249 -0
- package/claude/skills/sd-api-review/SKILL.md +89 -0
- package/claude/skills/sd-check/SKILL.md +55 -57
- package/claude/skills/sd-commit/SKILL.md +37 -42
- package/claude/skills/sd-debug/SKILL.md +75 -265
- package/claude/skills/sd-document/SKILL.md +63 -53
- package/claude/skills/sd-document/_common.py +94 -0
- package/claude/skills/sd-document/extract_docx.py +19 -48
- package/claude/skills/sd-document/extract_pdf.py +22 -50
- package/claude/skills/sd-document/extract_pptx.py +17 -40
- package/claude/skills/sd-document/extract_xlsx.py +19 -40
- package/claude/skills/sd-email-analyze/SKILL.md +23 -31
- package/claude/skills/sd-email-analyze/email-analyzer.py +79 -65
- package/claude/skills/sd-init/SKILL.md +133 -0
- package/claude/skills/sd-plan/SKILL.md +69 -120
- package/claude/skills/sd-readme/SKILL.md +106 -131
- package/claude/skills/sd-review/SKILL.md +38 -155
- package/claude/skills/sd-simplify/SKILL.md +59 -0
- package/dist/commands/install.js +20 -6
- package/dist/commands/install.js.map +1 -1
- package/package.json +3 -2
- package/src/commands/install.ts +29 -7
- package/README.md +0 -297
- package/claude/refs/sd-angular.md +0 -127
- package/claude/refs/sd-code-conventions.md +0 -155
- package/claude/refs/sd-directories.md +0 -7
- package/claude/refs/sd-library-issue.md +0 -7
- package/claude/refs/sd-migration.md +0 -7
- package/claude/refs/sd-orm-v12.md +0 -81
- package/claude/refs/sd-orm.md +0 -23
- package/claude/refs/sd-service.md +0 -5
- package/claude/refs/sd-simplysm-docs.md +0 -52
- package/claude/refs/sd-solid.md +0 -68
- package/claude/refs/sd-workflow.md +0 -25
- package/claude/rules/sd-refs-linker.md +0 -52
- package/claude/sd-statusline.js +0 -296
- package/claude/skills/sd-api-name-review/SKILL.md +0 -154
- package/claude/skills/sd-brainstorm/SKILL.md +0 -215
- package/claude/skills/sd-debug/condition-based-waiting-example.ts +0 -158
- package/claude/skills/sd-debug/condition-based-waiting.md +0 -114
- package/claude/skills/sd-debug/defense-in-depth.md +0 -128
- package/claude/skills/sd-debug/find-polluter.sh +0 -64
- package/claude/skills/sd-debug/root-cause-tracing.md +0 -168
- package/claude/skills/sd-discuss/SKILL.md +0 -91
- package/claude/skills/sd-explore/SKILL.md +0 -118
- package/claude/skills/sd-plan-dev/SKILL.md +0 -294
- package/claude/skills/sd-plan-dev/code-quality-reviewer-prompt.md +0 -49
- package/claude/skills/sd-plan-dev/final-review-prompt.md +0 -50
- package/claude/skills/sd-plan-dev/implementer-prompt.md +0 -60
- package/claude/skills/sd-plan-dev/spec-reviewer-prompt.md +0 -45
- package/claude/skills/sd-review/api-reviewer-prompt.md +0 -75
- package/claude/skills/sd-review/code-reviewer-prompt.md +0 -82
- package/claude/skills/sd-review/convention-checker-prompt.md +0 -61
- package/claude/skills/sd-review/refactoring-analyzer-prompt.md +0 -92
- package/claude/skills/sd-skill/SKILL.md +0 -417
- package/claude/skills/sd-skill/anthropic-best-practices.md +0 -156
- package/claude/skills/sd-skill/cso-guide.md +0 -161
- package/claude/skills/sd-skill/examples/CLAUDE_MD_TESTING.md +0 -200
- package/claude/skills/sd-skill/persuasion-principles.md +0 -220
- package/claude/skills/sd-skill/testing-skills-with-subagents.md +0 -408
- package/claude/skills/sd-skill/writing-guide.md +0 -159
- package/claude/skills/sd-tdd/SKILL.md +0 -385
- package/claude/skills/sd-tdd/testing-anti-patterns.md +0 -317
- package/claude/skills/sd-use/SKILL.md +0 -67
- package/claude/skills/sd-worktree/SKILL.md +0 -78
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
# ORM Guidelines (v12)
|
|
2
|
-
|
|
3
|
-
## Table Definition — Decorator-based
|
|
4
|
-
|
|
5
|
-
```typescript
|
|
6
|
-
@Table({ description: "Users", database: "mydb" })
|
|
7
|
-
export class User {
|
|
8
|
-
@Column({ primaryKey: 1, autoIncrement: true, description: "ID" })
|
|
9
|
-
id!: number;
|
|
10
|
-
|
|
11
|
-
@Column({ dataType: { type: "STRING", length: 100 }, description: "Name" })
|
|
12
|
-
name!: string;
|
|
13
|
-
|
|
14
|
-
@Column({ nullable: true, description: "Email" })
|
|
15
|
-
email?: string;
|
|
16
|
-
|
|
17
|
-
@ForeignKey(["departmentId"], () => Department, "Department")
|
|
18
|
-
department?: Department;
|
|
19
|
-
|
|
20
|
-
@ForeignKeyTarget(() => Order, "user", "Orders")
|
|
21
|
-
orders?: Order[];
|
|
22
|
-
}
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
### Decorators
|
|
26
|
-
|
|
27
|
-
- `@Table({ description, database?, schema?, name?, view?, procedure? })` — define table/view/procedure
|
|
28
|
-
- `@Column({ description, name?, dataType?, nullable?, autoIncrement?, primaryKey? })` — define column
|
|
29
|
-
- `@ForeignKey(columnNames, targetTypeFwd, description)` — FK relationship
|
|
30
|
-
- `@ForeignKeyTarget(sourceTypeFwd, fkPropertyKey, description, multiplicity?)` — FK reverse relationship
|
|
31
|
-
- `@ReferenceKey(columnNames, targetTypeFwd, description)` — reference relationship
|
|
32
|
-
- `@ReferenceKeyTarget(sourceTypeFwd, refKeyPropertyKey, description, multiplicity?)` — reference reverse relationship
|
|
33
|
-
- `@Index({ name?, order?, orderBy?, unique? })` — index
|
|
34
|
-
|
|
35
|
-
### Requirements
|
|
36
|
-
|
|
37
|
-
- `tsconfig` requires `experimentalDecorators: true` and `emitDecoratorMetadata: true`
|
|
38
|
-
|
|
39
|
-
## DbContext
|
|
40
|
-
|
|
41
|
-
```typescript
|
|
42
|
-
export abstract class MyDbContext extends DbContext {
|
|
43
|
-
user = new Queryable(this, User);
|
|
44
|
-
order = new Queryable(this, Order);
|
|
45
|
-
|
|
46
|
-
get migrations(): Type<IDbMigration>[] {
|
|
47
|
-
return [Migration001, Migration002];
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
## Query
|
|
53
|
-
|
|
54
|
-
```typescript
|
|
55
|
-
// Select
|
|
56
|
-
const users = await db.user
|
|
57
|
-
.select((item) => ({ id: item.id, name: item.name }))
|
|
58
|
-
.where((item) => [db.qh.equal(item.id, userId)])
|
|
59
|
-
.resultAsync();
|
|
60
|
-
|
|
61
|
-
// Insert
|
|
62
|
-
await db.user.insertAsync([{ name: "John Doe" }]);
|
|
63
|
-
|
|
64
|
-
// Connect with transaction
|
|
65
|
-
await db.connectAsync(async () => {
|
|
66
|
-
await db.user.insertAsync([{ name: "John Doe" }]);
|
|
67
|
-
});
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
## SQL Injection Prevention
|
|
71
|
-
|
|
72
|
-
ORM uses string escaping (not parameter binding). **Always validate user input before ORM queries.**
|
|
73
|
-
|
|
74
|
-
```typescript
|
|
75
|
-
const userId = Number(req.query.id);
|
|
76
|
-
if (Number.isNaN(userId)) throw new Error("Invalid ID");
|
|
77
|
-
await db.user
|
|
78
|
-
.select((item) => ({ id: item.id, name: item.name }))
|
|
79
|
-
.where((item) => [db.qh.equal(item.id, userId)])
|
|
80
|
-
.resultAsync();
|
|
81
|
-
```
|
package/claude/refs/sd-orm.md
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
# ORM Guidelines
|
|
2
|
-
|
|
3
|
-
## Table Definition
|
|
4
|
-
|
|
5
|
-
```typescript
|
|
6
|
-
const User = Table("User")
|
|
7
|
-
.database("mydb")
|
|
8
|
-
.columns((c) => ({ id: c.bigint().autoIncrement(), name: c.varchar(100) }))
|
|
9
|
-
.primaryKey("id");
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
## SQL Injection Prevention
|
|
13
|
-
|
|
14
|
-
ORM uses string escaping (not parameter binding). **Always validate user input before ORM queries.**
|
|
15
|
-
|
|
16
|
-
```typescript
|
|
17
|
-
const userId = Number(req.query.id);
|
|
18
|
-
if (Number.isNaN(userId)) throw new Error("Invalid ID");
|
|
19
|
-
await db
|
|
20
|
-
.user()
|
|
21
|
-
.where((u) => [expr.eq(u.id, userId)])
|
|
22
|
-
.result();
|
|
23
|
-
```
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
# @simplysm Package Documentation
|
|
2
|
-
|
|
3
|
-
When you need API details, usage examples, or component props for `@simplysm/*` packages,
|
|
4
|
-
read the package's README.md from node_modules.
|
|
5
|
-
|
|
6
|
-
## How to use
|
|
7
|
-
|
|
8
|
-
Read the package README directly:
|
|
9
|
-
|
|
10
|
-
```
|
|
11
|
-
node_modules/@simplysm/{package-name}/README.md
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
If not found (pnpm hoisting), try:
|
|
15
|
-
|
|
16
|
-
```
|
|
17
|
-
packages/*/node_modules/@simplysm/{package-name}/README.md
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
## When to use
|
|
21
|
-
|
|
22
|
-
**MANDATORY**: Read the relevant README BEFORE any of the following:
|
|
23
|
-
|
|
24
|
-
- Writing new code that uses `@simplysm/*` APIs
|
|
25
|
-
- Fixing type errors or bugs in code that uses `@simplysm/*` APIs
|
|
26
|
-
- Making assumptions about type mappings (e.g., DB column types → TypeScript types)
|
|
27
|
-
- Refactoring or migrating code that depends on `@simplysm/*` packages
|
|
28
|
-
|
|
29
|
-
Do NOT guess API behavior or type mappings — always verify from the README first.
|
|
30
|
-
|
|
31
|
-
## Available Packages
|
|
32
|
-
|
|
33
|
-
| Package | Description |
|
|
34
|
-
| ------------------------------ | --------------------------------------------------------------- |
|
|
35
|
-
| `core-common` | Common utilities, custom types (DateTime, DateOnly, Time, Uuid) |
|
|
36
|
-
| `core-browser` | Browser-specific extensions |
|
|
37
|
-
| `core-node` | Node.js utilities (filesystem, workers) |
|
|
38
|
-
| `orm-common` | ORM query builder, table schema definitions |
|
|
39
|
-
| `orm-node` | DB connectors (MySQL, MSSQL, PostgreSQL) |
|
|
40
|
-
| `service-common` | Service protocol, type definitions |
|
|
41
|
-
| `service-client` | WebSocket client |
|
|
42
|
-
| `service-server` | Fastify-based HTTP/WebSocket server |
|
|
43
|
-
| `solid` | SolidJS UI components + Tailwind CSS |
|
|
44
|
-
| `excel` | Excel (.xlsx) read/write |
|
|
45
|
-
| `storage` | FTP/SFTP client |
|
|
46
|
-
| `sd-cli` | Build, lint, typecheck CLI tool |
|
|
47
|
-
| `claude` | Claude Code skills/agents (auto-installs via postinstall) |
|
|
48
|
-
| `eslint-plugin` | Custom ESLint rules |
|
|
49
|
-
| `capacitor-plugin-auto-update` | Auto update |
|
|
50
|
-
| `capacitor-plugin-broadcast` | Broadcast |
|
|
51
|
-
| `capacitor-plugin-file-system` | File system |
|
|
52
|
-
| `capacitor-plugin-usb-storage` | USB storage |
|
package/claude/refs/sd-solid.md
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
# SolidJS Guidelines
|
|
2
|
-
|
|
3
|
-
**SolidJS is NOT React!**
|
|
4
|
-
|
|
5
|
-
## Core Concepts
|
|
6
|
-
|
|
7
|
-
- Component functions run **once** at mount (not on every state change)
|
|
8
|
-
- Fine-grained reactivity: unchanged signals don't re-evaluate expressions
|
|
9
|
-
- `createMemo`: only for expensive computations used in multiple places
|
|
10
|
-
- **Props destructuring prohibited** → use `props.xxx`
|
|
11
|
-
- Conditionals: `<Show>`, Lists: `<For>`
|
|
12
|
-
- No SSR → browser APIs usable directly
|
|
13
|
-
- Responsive: Mobile UI below 520px
|
|
14
|
-
- Chrome 84+ target
|
|
15
|
-
- CSS NOT transpiled → no `aspect-ratio`, `inset`, `:is()`, `:where()`
|
|
16
|
-
|
|
17
|
-
## Props Design
|
|
18
|
-
|
|
19
|
-
- Props that don't need parameters must accept plain values (`editable={perms().edit}`), not wrapped in functions (`editable={() => perms().edit}`) — use function props only when parameters are needed (callbacks)
|
|
20
|
-
|
|
21
|
-
## Implementation Rules
|
|
22
|
-
|
|
23
|
-
- Prefer signals/stores over Provider/Context
|
|
24
|
-
- Check existing patterns before introducing abstractions
|
|
25
|
-
- Before modifying components: always Read the file to check existing props/patterns
|
|
26
|
-
|
|
27
|
-
## Hook Naming
|
|
28
|
-
|
|
29
|
-
- `create*`: Reactive hooks wrapping SolidJS primitives
|
|
30
|
-
- `use*`: Hooks depending on Provider Context
|
|
31
|
-
- Others: no hook prefix
|
|
32
|
-
|
|
33
|
-
## Compound Components
|
|
34
|
-
|
|
35
|
-
All sub-components via dot notation only (`Parent.Child`).
|
|
36
|
-
|
|
37
|
-
- Export using `Object.assign` pattern:
|
|
38
|
-
```ts
|
|
39
|
-
export const Select = Object.assign(SelectInnerComponent, {
|
|
40
|
-
Item: SelectItem,
|
|
41
|
-
Header: SelectHeader,
|
|
42
|
-
Action: SelectAction,
|
|
43
|
-
ItemTemplate: SelectItemTemplate,
|
|
44
|
-
});
|
|
45
|
-
```
|
|
46
|
-
- Do NOT declare a separate type or interface for the compound component (e.g., `SelectComponent`, `TabsComponent`)
|
|
47
|
-
- Do NOT use type assertions on the export (e.g., `as SelectComponent`)
|
|
48
|
-
- Don't export sub-components separately (export parent only)
|
|
49
|
-
- UI elements → compound sub-components, non-rendering config (state, behavior, callbacks) → props
|
|
50
|
-
|
|
51
|
-
## Tailwind CSS
|
|
52
|
-
|
|
53
|
-
- `darkMode: "class"`, `aspectRatio` plugin disabled (Chrome 84)
|
|
54
|
-
- Semantic colors: `primary`(blue), `info`(sky), `success`(green), `warning`(amber), `danger`(red), `base`(zinc) → never use `zinc-*` directly
|
|
55
|
-
- Heights: `field`, `field-sm`, `field-lg`
|
|
56
|
-
- z-index: `sidebar`(100), `sidebar-backdrop`(99), `dropdown`(1000)
|
|
57
|
-
- Default `rem`, use `em` for text-relative sizing (e.g., Icon)
|
|
58
|
-
- Use `clsx()` with semantic grouping + `twMerge()` for conflict resolution
|
|
59
|
-
- Before modifying styles: Read existing class patterns of the same component
|
|
60
|
-
- **Class strings inline**: Do not extract class strings into separate variables — write them directly in the JSX return
|
|
61
|
-
- **`*.styles.ts` files**: Tailwind class strings must be wrapped with `clsx()` for IntelliSense support
|
|
62
|
-
|
|
63
|
-
## Application View Naming (`client-*`)
|
|
64
|
-
|
|
65
|
-
- `~Sheet` — List view based on DataSheet (e.g., `UserSheet`)
|
|
66
|
-
- `~Detail` — Single record detail/edit view (e.g., `MyInfoDetail`)
|
|
67
|
-
- `~View` — Everything else (e.g., `LoginView`, `MainView`)
|
|
68
|
-
- Directory: `src/views/` (usable as standalone page, embedded component, or modal)
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
# Workflow Rules
|
|
2
|
-
|
|
3
|
-
- **No auto-proceeding after skill completion**: When the user explicitly invokes a skill, report the result and **stop** once the skill finishes. Do not guess the next step and proceed arbitrarily. Wait for explicit user instructions if further work is needed.
|
|
4
|
-
- **Exception — yolo mode**: When a skill explicitly defines a "yolo mode" that chains multiple skills sequentially (e.g., sd-plan's "Path A: yolo"), auto-proceeding is permitted for the duration of that chain. Each step MUST still be invoked via the Skill tool.
|
|
5
|
-
|
|
6
|
-
## Problem-Solving Principles
|
|
7
|
-
|
|
8
|
-
- **Root cause first**: When encountering errors or unexpected behavior, always investigate the root cause before attempting a fix. Do not apply workarounds, hacks, or surface-level patches.
|
|
9
|
-
- **No band-aid fixes**: Avoid techniques like suppressing errors, adding defensive checks to hide symptoms, bypassing validation, or inflating timeout values. These mask the real problem and create technical debt.
|
|
10
|
-
- **Consider refactoring**: If the root cause reveals a design flaw or structural issue, propose a refactoring approach rather than working around it. A proper fix — even if larger in scope — is better than a fragile workaround.
|
|
11
|
-
- **Trace the full chain**: Follow the error or issue through the entire call chain (caller -> callee -> dependencies) to understand why it happens, not just where it happens.
|
|
12
|
-
- **When uncertain, ask**: If the root cause is unclear or the fix requires significant changes, discuss with the user before proceeding. Present findings and options rather than silently applying a quick fix.
|
|
13
|
-
|
|
14
|
-
## Pre-coding Checklist
|
|
15
|
-
|
|
16
|
-
- Before creating new files: Glob/Read similar existing files to check structure and patterns
|
|
17
|
-
- Before modifying functions/classes: Read the file to understand existing code style
|
|
18
|
-
- When unsure about API/method usage: Check signatures in source code
|
|
19
|
-
- **Always search the local codebase first.** Do not search the web or external docs until you have confirmed the answer is not in local code.
|
|
20
|
-
- **If confidence is low, ask the user instead of writing code**
|
|
21
|
-
|
|
22
|
-
## Memory Policy
|
|
23
|
-
|
|
24
|
-
- **Do NOT use auto memory** (`~/.claude/projects/.../memory/`). It is environment-specific and does not persist across machines.
|
|
25
|
-
- All persistent knowledge belongs in `.claude/rules/` or project docs (committed to git).
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
# Reference Guide
|
|
2
|
-
|
|
3
|
-
Before starting work, **Read** the relevant reference files from `.claude/refs/`.
|
|
4
|
-
|
|
5
|
-
## Version Detection
|
|
6
|
-
|
|
7
|
-
Determine the major version by the `version` field in `package.json`.
|
|
8
|
-
|
|
9
|
-
- `12.x.x` → **v12** (< 13)
|
|
10
|
-
- `13.x.x` → **v13** (>= 13)
|
|
11
|
-
|
|
12
|
-
## Common (all versions)
|
|
13
|
-
|
|
14
|
-
| When | Read this file |
|
|
15
|
-
| ------------------------------------------------ | ------------------------------------- |
|
|
16
|
-
| Writing, modifying, or reviewing code | `.claude/refs/sd-code-conventions.md` |
|
|
17
|
-
| Working with `.cache/` or Playwright screenshots | `.claude/refs/sd-directories.md` |
|
|
18
|
-
| Using `@simplysm/*` package APIs | `.claude/refs/sd-simplysm-docs.md` |
|
|
19
|
-
| Debugging, problem-solving, or planning approach | `.claude/refs/sd-workflow.md` |
|
|
20
|
-
| Using `@simplysm/service-*` packages | `.claude/refs/sd-service.md` |
|
|
21
|
-
| Migrating/porting code from another codebase | `.claude/refs/sd-migration.md` |
|
|
22
|
-
| Debugging in a project that uses `@simplysm/*` as an external dependency (not the simplysm monorepo itself) | `.claude/refs/sd-library-issue.md` |
|
|
23
|
-
|
|
24
|
-
## v12 only (< 13)
|
|
25
|
-
|
|
26
|
-
| When | Read this file |
|
|
27
|
-
| -------------------------------------- | ---------------------------- |
|
|
28
|
-
| Working on Angular / @simplysm/sd-angular | `.claude/refs/sd-angular.md` |
|
|
29
|
-
| Using `@simplysm/sd-orm-*` packages | `.claude/refs/sd-orm-v12.md` |
|
|
30
|
-
|
|
31
|
-
- v12 is **Angular** based (no SolidJS)
|
|
32
|
-
- ORM uses **decorator** pattern (`@Table`, `@Column`)
|
|
33
|
-
- Package names: use `sd-` prefix (`sd-core-common`, `sd-orm-common`, etc.)
|
|
34
|
-
- Package manager: **yarn**
|
|
35
|
-
|
|
36
|
-
## v13 only (>= 13)
|
|
37
|
-
|
|
38
|
-
| When | Read this file |
|
|
39
|
-
| -------------------------------------------- | -------------------------- |
|
|
40
|
-
| Working on SolidJS / @simplysm/solid / Tailwind | `.claude/refs/sd-solid.md` |
|
|
41
|
-
| Using `@simplysm/orm-*` packages | `.claude/refs/sd-orm.md` |
|
|
42
|
-
|
|
43
|
-
- v13 is **SolidJS** based (no Angular)
|
|
44
|
-
- ORM uses **functional builder** pattern (`Table().columns().primaryKey()`)
|
|
45
|
-
- Package names: no prefix (`core-common`, `orm-common`, etc.)
|
|
46
|
-
- Package manager: **pnpm**
|
|
47
|
-
|
|
48
|
-
## Rules
|
|
49
|
-
|
|
50
|
-
- Read the reference BEFORE starting the related work (not after)
|
|
51
|
-
- You may read multiple references if the task spans multiple areas
|
|
52
|
-
- If unsure whether a reference applies, read it — the cost of reading is low
|
package/claude/sd-statusline.js
DELETED
|
@@ -1,296 +0,0 @@
|
|
|
1
|
-
// @ts-check
|
|
2
|
-
import fs from "fs";
|
|
3
|
-
import os from "os";
|
|
4
|
-
import path from "path";
|
|
5
|
-
import { stdin } from "process";
|
|
6
|
-
|
|
7
|
-
//#region Constants
|
|
8
|
-
|
|
9
|
-
const STDIN_TIMEOUT_MS = 5000;
|
|
10
|
-
const FETCH_TIMEOUT_MS = 3000;
|
|
11
|
-
const CACHE_TTL_MS = 60_000; // 1 minutes
|
|
12
|
-
const CACHE_PATH = path.join(os.homedir(), ".claude", "usage-api-cache.json");
|
|
13
|
-
|
|
14
|
-
//#endregion
|
|
15
|
-
|
|
16
|
-
//#region Stdin
|
|
17
|
-
|
|
18
|
-
/** @returns {Promise<string>} */
|
|
19
|
-
function readStdin() {
|
|
20
|
-
return new Promise((resolve) => {
|
|
21
|
-
let data = "";
|
|
22
|
-
|
|
23
|
-
const cleanup = () => {
|
|
24
|
-
stdin.removeAllListeners("data");
|
|
25
|
-
stdin.removeAllListeners("end");
|
|
26
|
-
stdin.removeAllListeners("error");
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const timeout = setTimeout(() => {
|
|
30
|
-
cleanup();
|
|
31
|
-
resolve("");
|
|
32
|
-
}, STDIN_TIMEOUT_MS);
|
|
33
|
-
|
|
34
|
-
stdin.setEncoding("utf8");
|
|
35
|
-
stdin.on("data", (chunk) => {
|
|
36
|
-
data += chunk;
|
|
37
|
-
});
|
|
38
|
-
stdin.on("end", () => {
|
|
39
|
-
clearTimeout(timeout);
|
|
40
|
-
cleanup();
|
|
41
|
-
resolve(data);
|
|
42
|
-
});
|
|
43
|
-
stdin.on("error", () => {
|
|
44
|
-
clearTimeout(timeout);
|
|
45
|
-
cleanup();
|
|
46
|
-
resolve("");
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
//#endregion
|
|
52
|
-
|
|
53
|
-
//#region OAuth
|
|
54
|
-
|
|
55
|
-
/** @returns {string | undefined} */
|
|
56
|
-
function getOAuthToken() {
|
|
57
|
-
try {
|
|
58
|
-
const configDir = process.env.CLAUDE_CONFIG_DIR ?? path.join(os.homedir(), ".claude");
|
|
59
|
-
const credentialsPath = path.join(configDir, ".credentials.json");
|
|
60
|
-
if (!fs.existsSync(credentialsPath)) {
|
|
61
|
-
return undefined;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const content = fs.readFileSync(credentialsPath, "utf-8");
|
|
65
|
-
const credentials = JSON.parse(content);
|
|
66
|
-
const oauth = credentials.claudeAiOauth;
|
|
67
|
-
|
|
68
|
-
// Check token expiration
|
|
69
|
-
if (oauth?.expiresAt != null && Date.now() > oauth.expiresAt) {
|
|
70
|
-
return undefined;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return oauth?.accessToken;
|
|
74
|
-
} catch {
|
|
75
|
-
return undefined;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Read cached usage data from local file.
|
|
81
|
-
* @returns {{ data: object, timestamp: number } | undefined}
|
|
82
|
-
*/
|
|
83
|
-
function readCache() {
|
|
84
|
-
try {
|
|
85
|
-
if (!fs.existsSync(CACHE_PATH)) return undefined;
|
|
86
|
-
const content = fs.readFileSync(CACHE_PATH, "utf-8");
|
|
87
|
-
return JSON.parse(content);
|
|
88
|
-
} catch {
|
|
89
|
-
return undefined;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Write usage data to local cache file.
|
|
95
|
-
* @param {object} data
|
|
96
|
-
*/
|
|
97
|
-
function writeCache(data) {
|
|
98
|
-
try {
|
|
99
|
-
fs.writeFileSync(CACHE_PATH, JSON.stringify({ data, timestamp: Date.now() }), "utf-8");
|
|
100
|
-
} catch {
|
|
101
|
-
// ignore write errors
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Fetch Anthropic API usage information using OAuth token, with 1-minute cache.
|
|
107
|
-
* @param {string} token - OAuth access token
|
|
108
|
-
* @param {string} version - Claude Code version
|
|
109
|
-
* @returns {Promise<{
|
|
110
|
-
* seven_day?: {utilization?: number, resets_at?: string},
|
|
111
|
-
* daily?: {utilization?: number, resets_at?: string},
|
|
112
|
-
* five_hour?: {utilization?: number, resets_at?: string},
|
|
113
|
-
* extra_usage?: {is_enabled?: boolean, monthly_limit?: number | null, used_credits?: number}
|
|
114
|
-
* } | undefined>}
|
|
115
|
-
*/
|
|
116
|
-
async function fetchUsage(token, version) {
|
|
117
|
-
// Return cached data if still valid
|
|
118
|
-
const cache = readCache();
|
|
119
|
-
if (cache != null && Date.now() - cache.timestamp < CACHE_TTL_MS) {
|
|
120
|
-
return cache.data;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
try {
|
|
124
|
-
const controller = new AbortController();
|
|
125
|
-
const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
126
|
-
|
|
127
|
-
const response = await fetch("https://api.anthropic.com/api/oauth/usage", {
|
|
128
|
-
headers: {
|
|
129
|
-
"Authorization": `Bearer ${token}`,
|
|
130
|
-
"Accept": "application/json",
|
|
131
|
-
'anthropic-beta': 'oauth-2025-04-20'
|
|
132
|
-
},
|
|
133
|
-
signal: controller.signal,
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
clearTimeout(timeout);
|
|
137
|
-
|
|
138
|
-
if (!response.ok) {
|
|
139
|
-
// API failed — update timestamp to prevent retry for TTL duration
|
|
140
|
-
writeCache(cache?.data ?? {});
|
|
141
|
-
return undefined;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const data = await response.json();
|
|
145
|
-
|
|
146
|
-
if (data == null || typeof data !== "object") {
|
|
147
|
-
writeCache(cache?.data ?? {});
|
|
148
|
-
return undefined;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
writeCache(data);
|
|
152
|
-
return data;
|
|
153
|
-
} catch {
|
|
154
|
-
// Network error — update timestamp to prevent retry for TTL duration
|
|
155
|
-
writeCache(cache?.data ?? {});
|
|
156
|
-
return undefined;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
//#endregion
|
|
161
|
-
|
|
162
|
-
//#region Formatting
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* @param {number | undefined} value
|
|
166
|
-
* @returns {string}
|
|
167
|
-
*/
|
|
168
|
-
function formatPercent(value) {
|
|
169
|
-
if (value == null) return "?";
|
|
170
|
-
return Math.round(value).toString();
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* @param {string | undefined} isoDate
|
|
175
|
-
* @returns {string}
|
|
176
|
-
*/
|
|
177
|
-
function formatTimeRemaining(isoDate) {
|
|
178
|
-
if (isoDate == null) return "";
|
|
179
|
-
try {
|
|
180
|
-
const resetTime = new Date(isoDate).getTime();
|
|
181
|
-
if (Number.isNaN(resetTime)) return "";
|
|
182
|
-
|
|
183
|
-
const now = Date.now();
|
|
184
|
-
const diffMs = resetTime - now;
|
|
185
|
-
|
|
186
|
-
if (diffMs <= 0) return "";
|
|
187
|
-
|
|
188
|
-
const diffMinutes = Math.floor(diffMs / (1000 * 60));
|
|
189
|
-
const diffHours = Math.floor(diffMinutes / 60);
|
|
190
|
-
const days = Math.floor(diffHours / 24);
|
|
191
|
-
const hours = diffHours % 24;
|
|
192
|
-
const minutes = diffMinutes % 60;
|
|
193
|
-
|
|
194
|
-
if (days > 0) {
|
|
195
|
-
return `${days}d${hours}h`;
|
|
196
|
-
}
|
|
197
|
-
if (hours > 0) {
|
|
198
|
-
return `${hours}h${minutes}m`;
|
|
199
|
-
}
|
|
200
|
-
return `${minutes}m`;
|
|
201
|
-
} catch {
|
|
202
|
-
return "";
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
//#endregion
|
|
207
|
-
|
|
208
|
-
//#region Main
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* JSON information received from stdin
|
|
212
|
-
* @typedef {object} StdinInput
|
|
213
|
-
* @property {{display_name?: string}} [model] - Model information
|
|
214
|
-
* @property {{context_window_size?: number, remaining_context_tokens?: number, current_usage?: {input_tokens?: number, output_tokens?: number, cache_creation_input_tokens?: number, cache_read_input_tokens?: number}}} [context_window] - Context window information
|
|
215
|
-
* @property {{tokens_used?: number, tokens_limit?: number}} [weekly_token_usage] - Weekly token usage (fallback)
|
|
216
|
-
* @property {string} [version] - Claude Code version
|
|
217
|
-
*/
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Output Claude Code status bar information.
|
|
221
|
-
* Combine JSON received from stdin with OAuth API response to output
|
|
222
|
-
* model name, context usage, and daily/weekly usage.
|
|
223
|
-
*/
|
|
224
|
-
async function main() {
|
|
225
|
-
const inputStr = await readStdin();
|
|
226
|
-
/** @type {StdinInput} */
|
|
227
|
-
let input = {};
|
|
228
|
-
|
|
229
|
-
if (inputStr !== "") {
|
|
230
|
-
try {
|
|
231
|
-
input = JSON.parse(inputStr);
|
|
232
|
-
} catch {
|
|
233
|
-
// Use empty object if JSON parsing fails
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Basic information
|
|
238
|
-
const modelName = input.model?.display_name ?? "Unknown";
|
|
239
|
-
const contextSize = input.context_window?.context_window_size ?? 0;
|
|
240
|
-
const usage = input.context_window?.current_usage;
|
|
241
|
-
const contextUsed =
|
|
242
|
-
(usage?.input_tokens ?? 0) +
|
|
243
|
-
(usage?.output_tokens ?? 0) +
|
|
244
|
-
(usage?.cache_creation_input_tokens ?? 0) +
|
|
245
|
-
(usage?.cache_read_input_tokens ?? 0);
|
|
246
|
-
const contextPercent = contextSize > 0 ? Math.round((contextUsed / contextSize) * 100) : 0;
|
|
247
|
-
|
|
248
|
-
// Try fetching usage with OAuth token
|
|
249
|
-
const token = getOAuthToken();
|
|
250
|
-
let dailyPercent = "?";
|
|
251
|
-
let dailyResetTime = "";
|
|
252
|
-
let weekPercent = "?";
|
|
253
|
-
let weekResetDay = "";
|
|
254
|
-
let extraUsage = "";
|
|
255
|
-
|
|
256
|
-
if (token != null) {
|
|
257
|
-
const usageResponse = await fetchUsage(token, input.version ?? "unknown");
|
|
258
|
-
if (usageResponse != null) {
|
|
259
|
-
// Use daily or five_hour
|
|
260
|
-
const dailyData = usageResponse.daily ?? usageResponse.five_hour;
|
|
261
|
-
dailyPercent = formatPercent(dailyData?.utilization);
|
|
262
|
-
dailyResetTime = formatTimeRemaining(dailyData?.resets_at);
|
|
263
|
-
weekPercent = formatPercent(usageResponse.seven_day?.utilization);
|
|
264
|
-
weekResetDay = formatTimeRemaining(usageResponse.seven_day?.resets_at);
|
|
265
|
-
|
|
266
|
-
// Extra usage
|
|
267
|
-
if (usageResponse.extra_usage?.is_enabled && usageResponse.extra_usage.used_credits != null) {
|
|
268
|
-
extraUsage = `$${(usageResponse.extra_usage.used_credits / 100).toFixed(2)}`;
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// Fallback: weekly_token_usage
|
|
274
|
-
if (weekPercent === "?" && input.weekly_token_usage != null) {
|
|
275
|
-
const used = input.weekly_token_usage.tokens_used ?? 0;
|
|
276
|
-
const limit = input.weekly_token_usage.tokens_limit ?? 0;
|
|
277
|
-
if (limit > 0) {
|
|
278
|
-
weekPercent = Math.round((used / limit) * 100).toString();
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// Folder name + git branch
|
|
283
|
-
const cwd = input.cwd ?? process.cwd();
|
|
284
|
-
const folderName = path.basename(cwd);
|
|
285
|
-
|
|
286
|
-
// Output
|
|
287
|
-
const dailyStr = dailyResetTime ? `${dailyPercent}%(${dailyResetTime})` : `${dailyPercent}%`;
|
|
288
|
-
const weekStr = weekResetDay ? `${weekPercent}%(${weekResetDay})` : `${weekPercent}%`;
|
|
289
|
-
const parts = [folderName, modelName, `${contextPercent}%`, dailyStr, weekStr];
|
|
290
|
-
if (extraUsage) parts.push(extraUsage);
|
|
291
|
-
console.log(parts.join(" │ "));
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
void main();
|
|
295
|
-
|
|
296
|
-
//#endregion
|