@eat-pray-ai/wingman 0.1.0 → 0.2.0
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/.github/workflows/codeql.yml +22 -5
- package/.github/workflows/publish.yml +4 -5
- package/.github/workflows/release.yml +12 -0
- package/.github/workflows/test.yml +1 -1
- package/LICENSE +21 -0
- package/README.md +37 -28
- package/dist/src/cli.mjs +57 -5
- package/dist/src/cli.mjs.map +1 -1
- package/package.json +1 -1
- package/src/cli.ts +2 -0
- package/src/pricing/AGENTS.md +1 -1
- package/src/pricing/engine.ts +45 -5
- package/src/pricing/models-dev.ts +31 -1
- package/tsconfig.json +1 -1
- package/tsdown.config.ts +1 -1
- package/.idea/workspace.xml +0 -125
|
@@ -13,13 +13,30 @@ name: "CodeQL Advanced"
|
|
|
13
13
|
|
|
14
14
|
on:
|
|
15
15
|
push:
|
|
16
|
-
branches: [
|
|
17
|
-
tags-ignore:
|
|
18
|
-
|
|
16
|
+
branches: [main]
|
|
17
|
+
tags-ignore: ["*"]
|
|
18
|
+
paths:
|
|
19
|
+
- "src/**"
|
|
20
|
+
- "bin/**"
|
|
21
|
+
- "scripts/**"
|
|
22
|
+
- "package.json"
|
|
23
|
+
- "package-lock.json"
|
|
24
|
+
- "tsconfig.json"
|
|
25
|
+
- "tsdown.config.ts"
|
|
26
|
+
- ".github/**/*.yml"
|
|
19
27
|
pull_request:
|
|
20
|
-
branches: [
|
|
28
|
+
branches: [main]
|
|
29
|
+
paths:
|
|
30
|
+
- "src/**"
|
|
31
|
+
- "bin/**"
|
|
32
|
+
- "scripts/**"
|
|
33
|
+
- "package.json"
|
|
34
|
+
- "package-lock.json"
|
|
35
|
+
- "tsconfig.json"
|
|
36
|
+
- "tsdown.config.ts"
|
|
37
|
+
- ".github/**/*.yml"
|
|
21
38
|
schedule:
|
|
22
|
-
- cron:
|
|
39
|
+
- cron: "25 7 * * 3"
|
|
23
40
|
|
|
24
41
|
jobs:
|
|
25
42
|
analyze:
|
|
@@ -4,18 +4,17 @@ on:
|
|
|
4
4
|
push:
|
|
5
5
|
tags: ["v*"]
|
|
6
6
|
|
|
7
|
-
permissions:
|
|
8
|
-
contents: read
|
|
9
|
-
id-token: write
|
|
10
|
-
|
|
11
7
|
jobs:
|
|
12
8
|
publish:
|
|
13
9
|
runs-on: ubuntu-latest
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
id-token: write
|
|
14
13
|
steps:
|
|
15
14
|
- uses: actions/checkout@v6
|
|
16
15
|
- uses: actions/setup-node@v6
|
|
17
16
|
with:
|
|
18
|
-
node-version: "
|
|
17
|
+
node-version: "24"
|
|
19
18
|
cache: npm
|
|
20
19
|
- run: npm ci
|
|
21
20
|
- run: npm version "${GITHUB_REF_NAME#v}" --no-git-tag-version
|
|
@@ -31,8 +31,20 @@ jobs:
|
|
|
31
31
|
else
|
|
32
32
|
NOTES=$(git log --pretty=format:"- %s" "${TAG}")
|
|
33
33
|
fi
|
|
34
|
+
VERSION="${TAG#v}"
|
|
34
35
|
{
|
|
35
36
|
echo "body<<EOF"
|
|
37
|
+
echo "## Install"
|
|
38
|
+
echo ""
|
|
39
|
+
echo '```bash'
|
|
40
|
+
echo "npx @eat-pray-ai/wingman@${VERSION} card"
|
|
41
|
+
echo "npx @eat-pray-ai/wingman@${VERSION} resume"
|
|
42
|
+
echo '```'
|
|
43
|
+
echo ""
|
|
44
|
+
echo "[](https://www.npmjs.com/package/@eat-pray-ai/wingman/v/${VERSION})"
|
|
45
|
+
echo ""
|
|
46
|
+
echo "## Changes"
|
|
47
|
+
echo ""
|
|
36
48
|
echo "$NOTES"
|
|
37
49
|
echo "EOF"
|
|
38
50
|
} >> "$GITHUB_OUTPUT"
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) [2026] [eat-pray-ai & OpenWaygate]
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Wingman
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@eat-pray-ai/wingman)
|
|
4
|
+
[](https://github.com/eat-pray-ai/wingman/actions/workflows/test.yml)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
3
7
|
Showcase your AI pair usage — SVG cards, resumes, and more.
|
|
4
8
|
|
|
5
9
|
<table>
|
|
@@ -7,6 +11,10 @@ Showcase your AI pair usage — SVG cards, resumes, and more.
|
|
|
7
11
|
<th align="center">SVG Card</th>
|
|
8
12
|
<th align="center">Resume (<a href="docs/wingman.pdf">PDF</a>)</th>
|
|
9
13
|
</tr>
|
|
14
|
+
<tr>
|
|
15
|
+
<td align="left"><code>npx @eat-pray-ai/wingman card</code></td>
|
|
16
|
+
<td align="left"><code>npx @eat-pray-ai/wingman resume</code></td>
|
|
17
|
+
</tr>
|
|
10
18
|
<tr>
|
|
11
19
|
<td align="center"><img src="docs/wingman.svg" width="400" alt="SVG Card"/></td>
|
|
12
20
|
<td align="center"><img src="docs/wingman.png" width="400" alt="Resume"/></td>
|
|
@@ -15,16 +23,17 @@ Showcase your AI pair usage — SVG cards, resumes, and more.
|
|
|
15
23
|
|
|
16
24
|
## Supported Agents
|
|
17
25
|
|
|
18
|
-
| Agent
|
|
19
|
-
|
|
20
|
-
| **Claude Code** | `~/.claude/projects/*/*.jsonl`
|
|
21
|
-
| **opencode**
|
|
22
|
-
| **Gemini CLI**
|
|
23
|
-
| **Codex**
|
|
26
|
+
| Agent | Data Source | Format |
|
|
27
|
+
|-----------------|----------------------------------------|--------|
|
|
28
|
+
| **Claude Code** | `~/.claude/projects/*/*.jsonl` | JSONL |
|
|
29
|
+
| **opencode** | `~/.local/share/opencode/opencode.db` | SQLite |
|
|
30
|
+
| **Gemini CLI** | `~/.gemini/tmp/*/chats/session-*.json` | JSON |
|
|
31
|
+
| **Codex** | `~/.codex/state_5.sqlite` | SQLite |
|
|
32
|
+
| **MORE** | Coming soon! | TBD |
|
|
24
33
|
|
|
25
34
|
## Quick Start
|
|
26
35
|
|
|
27
|
-
```
|
|
36
|
+
```shell
|
|
28
37
|
# Generate an SVG stats card (last 90 days)
|
|
29
38
|
npx @eat-pray-ai/wingman card
|
|
30
39
|
|
|
@@ -36,7 +45,7 @@ npx @eat-pray-ai/wingman resume
|
|
|
36
45
|
|
|
37
46
|
### `card` — SVG Stats Card
|
|
38
47
|
|
|
39
|
-
```
|
|
48
|
+
```shell
|
|
40
49
|
# All agents, last 90 days (default)
|
|
41
50
|
wingman card
|
|
42
51
|
|
|
@@ -50,15 +59,15 @@ wingman card --since 2026-01-01 --until 2026-03-30
|
|
|
50
59
|
wingman card --days 7 --theme github-dark
|
|
51
60
|
```
|
|
52
61
|
|
|
53
|
-
| Flag
|
|
54
|
-
|
|
55
|
-
| `--output`
|
|
56
|
-
| `--theme`
|
|
57
|
-
| `--agents`
|
|
58
|
-
| `--since`
|
|
59
|
-
| `--until`
|
|
60
|
-
| `--days`
|
|
61
|
-
| `--sections` |
|
|
62
|
+
| Flag | Short | Default | Description |
|
|
63
|
+
|--------------|-------|---------------|-------------------------------------|
|
|
64
|
+
| `--output` | `-o` | `wingman.svg` | Output file path |
|
|
65
|
+
| `--theme` | `-t` | `github-dark` | Theme name |
|
|
66
|
+
| `--agents` | | all detected | Comma-separated agent filter |
|
|
67
|
+
| `--since` | | 90 days ago | Start date (YYYY-MM-DD) |
|
|
68
|
+
| `--until` | | today | End date (YYYY-MM-DD) |
|
|
69
|
+
| `--days` | | `90` | Last N days shorthand |
|
|
70
|
+
| `--sections` | | all | Comma-separated sections to include |
|
|
62
71
|
|
|
63
72
|
The default `github-dark` theme renders:
|
|
64
73
|
|
|
@@ -72,7 +81,7 @@ The default `github-dark` theme renders:
|
|
|
72
81
|
|
|
73
82
|
### `resume` — rendercv YAML Resume
|
|
74
83
|
|
|
75
|
-
```
|
|
84
|
+
```shell
|
|
76
85
|
# All agents, last 180 days (default)
|
|
77
86
|
wingman resume
|
|
78
87
|
|
|
@@ -83,15 +92,15 @@ wingman resume --name "My Team" --headline "AI Development"
|
|
|
83
92
|
wingman resume -o my-resume.yaml
|
|
84
93
|
```
|
|
85
94
|
|
|
86
|
-
| Flag
|
|
87
|
-
|
|
88
|
-
| `--output`
|
|
89
|
-
| `--name`
|
|
90
|
-
| `--headline` |
|
|
91
|
-
| `--agents`
|
|
92
|
-
| `--since`
|
|
93
|
-
| `--until`
|
|
94
|
-
| `--days`
|
|
95
|
+
| Flag | Short | Default | Description |
|
|
96
|
+
|--------------|-------|--------------------------|------------------------------|
|
|
97
|
+
| `--output` | `-o` | `resume.yaml` | Output file path |
|
|
98
|
+
| `--name` | | `Wingman` | Resume name |
|
|
99
|
+
| `--headline` | | `AI pair for everything` | Resume headline |
|
|
100
|
+
| `--agents` | | all detected | Comma-separated agent filter |
|
|
101
|
+
| `--since` | | 180 days ago | Start date (YYYY-MM-DD) |
|
|
102
|
+
| `--until` | | today | End date (YYYY-MM-DD) |
|
|
103
|
+
| `--days` | | `180` | Last N days shorthand |
|
|
95
104
|
|
|
96
105
|
The generated YAML follows the [rendercv](https://rendercv.com/) schema with sections:
|
|
97
106
|
|
|
@@ -117,7 +126,7 @@ Agent Adapters → UsageRecord[] → Aggregator → ShowcaseData → Renderer
|
|
|
117
126
|
|
|
118
127
|
## Development
|
|
119
128
|
|
|
120
|
-
```
|
|
129
|
+
```shell
|
|
121
130
|
npm install
|
|
122
131
|
npm run dev -- card --days 30 # run directly via tsx
|
|
123
132
|
npm run dev -- resume # generate resume YAML
|
package/dist/src/cli.mjs
CHANGED
|
@@ -416,7 +416,7 @@ function getAllAdapters() {
|
|
|
416
416
|
//#endregion
|
|
417
417
|
//#region src/pricing/models-dev.ts
|
|
418
418
|
const MODELS_DEV_URL = "https://models.dev/api.json";
|
|
419
|
-
const CACHE_DIR = join(homedir(), ".
|
|
419
|
+
const CACHE_DIR = join(homedir(), ".cache", "wingman");
|
|
420
420
|
const CACHE_FILE = join(CACHE_DIR, "models.json");
|
|
421
421
|
const CACHE_TTL_MS = 1440 * 60 * 1e3;
|
|
422
422
|
/** Maps model family prefixes to the official AI lab name */
|
|
@@ -527,6 +527,31 @@ async function fetchModelPricing() {
|
|
|
527
527
|
return result;
|
|
528
528
|
}
|
|
529
529
|
/**
|
|
530
|
+
* Fetch model family mapping from models.dev (reuses same 24h disk cache).
|
|
531
|
+
*
|
|
532
|
+
* Returns a Map keyed by modelId → family string (e.g. "claude-sonnet-4-5" → "claude").
|
|
533
|
+
* Uses the `family` field from the API when available, falls back to modelId itself.
|
|
534
|
+
*/
|
|
535
|
+
async function fetchModelFamilies() {
|
|
536
|
+
let data = await readCache();
|
|
537
|
+
if (!data) {
|
|
538
|
+
data = await fetchFromApi();
|
|
539
|
+
await writeCache(data);
|
|
540
|
+
}
|
|
541
|
+
const result = /* @__PURE__ */ new Map();
|
|
542
|
+
for (const providerData of Object.values(data)) {
|
|
543
|
+
const models = providerData?.models;
|
|
544
|
+
if (!models || typeof models !== "object") continue;
|
|
545
|
+
for (const [modelId, model] of Object.entries(models)) {
|
|
546
|
+
if (result.has(modelId)) continue;
|
|
547
|
+
const raw = model;
|
|
548
|
+
const family = typeof raw.family === "string" ? raw.family : modelId;
|
|
549
|
+
result.set(modelId, family);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
return result;
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
530
555
|
* Fetch model metadata from models.dev (reuses same 24h disk cache).
|
|
531
556
|
*
|
|
532
557
|
* Returns a Map keyed by model ID. Uses the same normalized-ID fallback
|
|
@@ -586,14 +611,22 @@ async function fetchModelInfo() {
|
|
|
586
611
|
*/
|
|
587
612
|
async function createPricingEngine(overrides) {
|
|
588
613
|
const catalog = await fetchModelPricing();
|
|
614
|
+
const families = await fetchModelFamilies();
|
|
589
615
|
const overrideMap = /* @__PURE__ */ new Map();
|
|
590
616
|
if (overrides) for (const o of overrides) overrideMap.set(`${o.modelId}::${o.provider}`, o);
|
|
591
617
|
const normalizedIndex = /* @__PURE__ */ new Map();
|
|
618
|
+
const familyIndex = /* @__PURE__ */ new Map();
|
|
592
619
|
for (const [modelId, pricings] of catalog) {
|
|
593
620
|
const key = normalizeModelId(modelId);
|
|
594
621
|
const existing = normalizedIndex.get(key);
|
|
595
622
|
if (existing) existing.push(...pricings);
|
|
596
623
|
else normalizedIndex.set(key, [...pricings]);
|
|
624
|
+
const fam = families.get(modelId);
|
|
625
|
+
if (fam) {
|
|
626
|
+
const famExisting = familyIndex.get(fam);
|
|
627
|
+
if (famExisting) famExisting.push(...pricings);
|
|
628
|
+
else familyIndex.set(fam, [...pricings]);
|
|
629
|
+
}
|
|
597
630
|
}
|
|
598
631
|
function resolve(modelId, provider) {
|
|
599
632
|
if (provider) {
|
|
@@ -605,7 +638,12 @@ async function createPricingEngine(overrides) {
|
|
|
605
638
|
const match = exactEntries.find((p) => p.provider === provider);
|
|
606
639
|
if (match) return match;
|
|
607
640
|
}
|
|
608
|
-
|
|
641
|
+
const family = families.get(modelId) ?? modelId;
|
|
642
|
+
const official = modelLab(family).toLowerCase();
|
|
643
|
+
if (exactEntries && exactEntries.length > 0) {
|
|
644
|
+
const match = exactEntries.find((p) => p.provider === official);
|
|
645
|
+
if (match) return match;
|
|
646
|
+
}
|
|
609
647
|
const normalized = normalizeModelId(modelId);
|
|
610
648
|
const fuzzyEntries = normalizedIndex.get(normalized);
|
|
611
649
|
if (fuzzyEntries && fuzzyEntries.length > 0) {
|
|
@@ -613,8 +651,16 @@ async function createPricingEngine(overrides) {
|
|
|
613
651
|
const match = fuzzyEntries.find((p) => p.provider === provider);
|
|
614
652
|
if (match) return match;
|
|
615
653
|
}
|
|
616
|
-
|
|
654
|
+
const officialMatch = fuzzyEntries.find((p) => p.provider === official);
|
|
655
|
+
if (officialMatch) return officialMatch;
|
|
656
|
+
}
|
|
657
|
+
const familyEntries = familyIndex.get(family);
|
|
658
|
+
if (familyEntries && familyEntries.length > 0) {
|
|
659
|
+
const officialMatch = familyEntries.find((p) => p.provider === official);
|
|
660
|
+
if (officialMatch) return officialMatch;
|
|
617
661
|
}
|
|
662
|
+
if (fuzzyEntries && fuzzyEntries.length > 0) return fuzzyEntries[0];
|
|
663
|
+
if (exactEntries && exactEntries.length > 0) return exactEntries[0];
|
|
618
664
|
return null;
|
|
619
665
|
}
|
|
620
666
|
function calculateCost(record) {
|
|
@@ -2122,7 +2168,10 @@ program.command("card").description("Generate an SVG stats card from local AI ag
|
|
|
2122
2168
|
const until = opts.until ? /* @__PURE__ */ new Date(opts.until + "T23:59:59.999") : /* @__PURE__ */ new Date();
|
|
2123
2169
|
let since;
|
|
2124
2170
|
if (opts.since) since = /* @__PURE__ */ new Date(opts.since + "T00:00:00");
|
|
2125
|
-
else
|
|
2171
|
+
else {
|
|
2172
|
+
since = /* @__PURE__ */ new Date(until.getTime() - opts.days * 24 * 60 * 60 * 1e3);
|
|
2173
|
+
since.setHours(0, 0, 0, 0);
|
|
2174
|
+
}
|
|
2126
2175
|
const theme = getTheme(opts.theme);
|
|
2127
2176
|
if (!theme) {
|
|
2128
2177
|
console.error(`Unknown theme "${opts.theme}". Available: ${getAvailableThemes().join(", ")}`);
|
|
@@ -2178,7 +2227,10 @@ program.command("resume").description("Generate a rendercv-compatible YAML resum
|
|
|
2178
2227
|
const until = opts.until ? /* @__PURE__ */ new Date(opts.until + "T23:59:59.999") : /* @__PURE__ */ new Date();
|
|
2179
2228
|
let since;
|
|
2180
2229
|
if (opts.since) since = /* @__PURE__ */ new Date(opts.since + "T00:00:00");
|
|
2181
|
-
else
|
|
2230
|
+
else {
|
|
2231
|
+
since = /* @__PURE__ */ new Date(until.getTime() - opts.days * 24 * 60 * 60 * 1e3);
|
|
2232
|
+
since.setHours(0, 0, 0, 0);
|
|
2233
|
+
}
|
|
2182
2234
|
const agentFilter = opts.agents ? new Set(opts.agents.split(",").map((s) => s.trim())) : null;
|
|
2183
2235
|
let adapters = getAllAdapters();
|
|
2184
2236
|
if (agentFilter) adapters = adapters.filter((a) => agentFilter.has(a.name));
|