@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.
@@ -13,13 +13,30 @@ name: "CodeQL Advanced"
13
13
 
14
14
  on:
15
15
  push:
16
- branches: [ "main" ]
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: [ "main" ]
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: '25 7 * * 3'
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: "22"
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 "[![npm](https://img.shields.io/npm/v/@eat-pray-ai/wingman)](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"
@@ -19,7 +19,7 @@ jobs:
19
19
  runs-on: ubuntu-latest
20
20
  strategy:
21
21
  matrix:
22
- node-version: ["22", "lts/*"]
22
+ node-version: ["24", "lts/*"]
23
23
  steps:
24
24
  - uses: actions/checkout@v6
25
25
  - uses: actions/setup-node@v6
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
+ [![npm](https://img.shields.io/npm/v/@eat-pray-ai/wingman)](https://www.npmjs.com/package/@eat-pray-ai/wingman)
4
+ [![test](https://img.shields.io/github/actions/workflow/status/eat-pray-ai/wingman/test.yml?label=test)](https://github.com/eat-pray-ai/wingman/actions/workflows/test.yml)
5
+ [![license](https://img.shields.io/github/license/eat-pray-ai/wingman)](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 | Data Source | Format |
19
- |---|---|---|
20
- | **Claude Code** | `~/.claude/projects/*/*.jsonl` | JSONL |
21
- | **opencode** | `~/.local/share/opencode/opencode.db` | SQLite |
22
- | **Gemini CLI** | `~/.gemini/tmp/*/chats/session-*.json` | JSON |
23
- | **Codex** | `~/.codex/state_5.sqlite` | SQLite |
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
- ```bash
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
- ```bash
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 | Short | Default | Description |
54
- |---|---|---|---|
55
- | `--output` | `-o` | `wingman.svg` | Output file path |
56
- | `--theme` | `-t` | `github-dark` | Theme name |
57
- | `--agents` | | all detected | Comma-separated agent filter |
58
- | `--since` | | 90 days ago | Start date (YYYY-MM-DD) |
59
- | `--until` | | today | End date (YYYY-MM-DD) |
60
- | `--days` | | `90` | Last N days shorthand |
61
- | `--sections` | | all | Comma-separated sections to include |
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
- ```bash
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 | Short | Default | Description |
87
- |---|---|---|---|
88
- | `--output` | `-o` | `resume.yaml` | Output file path |
89
- | `--name` | | `Wingman` | Resume name |
90
- | `--headline` | | `AI pair for everything` | Resume headline |
91
- | `--agents` | | all detected | Comma-separated agent filter |
92
- | `--since` | | 180 days ago | Start date (YYYY-MM-DD) |
93
- | `--until` | | today | End date (YYYY-MM-DD) |
94
- | `--days` | | `180` | Last N days shorthand |
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
- ```bash
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(), ".wingman", "cache");
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
- if (exactEntries && exactEntries.length > 0) return exactEntries[0];
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
- return fuzzyEntries[0];
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 since = /* @__PURE__ */ new Date(until.getTime() - opts.days * 24 * 60 * 60 * 1e3);
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 since = /* @__PURE__ */ new Date(until.getTime() - opts.days * 24 * 60 * 60 * 1e3);
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));