@askalf/dario 3.9.0 → 3.9.1

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.
Files changed (2) hide show
  1. package/dist/oauth.js +69 -1
  2. package/package.json +1 -1
package/dist/oauth.js CHANGED
@@ -52,8 +52,47 @@ function getClaudeCodeCredentialsPath() {
52
52
  * store instead of ~/.claude/.credentials.json:
53
53
  * - macOS: Keychain, service "Claude Code-credentials"
54
54
  * - Linux: libsecret / Secret Service D-Bus API via `secret-tool`
55
- * - Windows: Windows Credential Manager via `cmdkey` (not yet implemented)
55
+ * - Windows: Windows Credential Manager via PowerShell + Win32 CredEnumerate
56
56
  */
57
+ const WIN_CRED_SCRIPT = `
58
+ $ErrorActionPreference = 'Stop'
59
+ $sig = @"
60
+ using System;
61
+ using System.Runtime.InteropServices;
62
+ public class CM {
63
+ [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
64
+ public struct CRED {
65
+ public uint Flags; public uint Type; public string TargetName;
66
+ public string Comment; public System.Runtime.InteropServices.ComTypes.FILETIME LW;
67
+ public uint BlobSize; public IntPtr Blob;
68
+ public uint Persist; public uint AC; public IntPtr Attrs;
69
+ public string Alias; public string UN;
70
+ }
71
+ [DllImport("advapi32.dll", EntryPoint="CredEnumerateW", CharSet=CharSet.Unicode, SetLastError=true)]
72
+ public static extern bool CredEnumerate(string filter, uint flag, out uint count, out IntPtr pCredentials);
73
+ [DllImport("advapi32.dll", EntryPoint="CredFree")]
74
+ public static extern void CredFree(IntPtr cred);
75
+ }
76
+ "@
77
+ Add-Type -TypeDefinition $sig
78
+ $count = 0
79
+ $ptr = [IntPtr]::Zero
80
+ if ([CM]::CredEnumerate('Claude Code-credentials*', 0, [ref]$count, [ref]$ptr)) {
81
+ try {
82
+ for ($i = 0; $i -lt $count; $i++) {
83
+ $credPtr = [System.Runtime.InteropServices.Marshal]::ReadIntPtr($ptr, $i * [IntPtr]::Size)
84
+ $cred = [System.Runtime.InteropServices.Marshal]::PtrToStructure($credPtr, [type][CM+CRED])
85
+ if ($cred.BlobSize -gt 0) {
86
+ $bytes = New-Object byte[] $cred.BlobSize
87
+ [System.Runtime.InteropServices.Marshal]::Copy($cred.Blob, $bytes, 0, $cred.BlobSize)
88
+ Write-Output ([System.Text.Encoding]::Unicode.GetString($bytes))
89
+ }
90
+ }
91
+ } finally {
92
+ [CM]::CredFree($ptr)
93
+ }
94
+ }
95
+ `;
57
96
  async function loadKeychainCredentials() {
58
97
  try {
59
98
  if (platform() === 'darwin') {
@@ -74,6 +113,35 @@ async function loadKeychainCredentials() {
74
113
  return parsed;
75
114
  }
76
115
  }
116
+ else if (platform() === 'win32') {
117
+ // Windows Credential Manager via PowerShell + Win32 CredEnumerate.
118
+ // Claude Code on Windows (via Node keytar) stores OAuth tokens as
119
+ // Generic credentials with target prefix "Claude Code-credentials".
120
+ // We enumerate matching credentials and return the first one that
121
+ // parses as a valid CC credentials blob. The password field is
122
+ // stored as UTF-16LE bytes (keytar convention on Windows).
123
+ //
124
+ // PowerShell CredEnumerate sets LastWin32Error=1168 (ERROR_NOT_FOUND)
125
+ // when the filter matches nothing — we catch the non-zero exit and
126
+ // return null so the caller falls back to the file-path checks.
127
+ const raw = await new Promise((resolve, reject) => {
128
+ execFile('powershell.exe', ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', WIN_CRED_SCRIPT], { timeout: 5000, windowsHide: true }, (err, stdout) => (err ? reject(err) : resolve(stdout)));
129
+ });
130
+ // Script emits one JSON blob per matching credential, newline-separated.
131
+ // Return the first one that parses with the expected CC shape.
132
+ for (const line of raw.split(/\r?\n/)) {
133
+ const s = line.trim();
134
+ if (!s)
135
+ continue;
136
+ try {
137
+ const parsed = JSON.parse(s);
138
+ if (parsed?.claudeAiOauth?.accessToken && parsed?.claudeAiOauth?.refreshToken) {
139
+ return parsed;
140
+ }
141
+ }
142
+ catch { /* not a valid JSON credential blob — try next */ }
143
+ }
144
+ }
77
145
  }
78
146
  catch { /* keychain not available or no entry */ }
79
147
  return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askalf/dario",
3
- "version": "3.9.0",
3
+ "version": "3.9.1",
4
4
  "description": "A local LLM router. One endpoint, every provider — Claude subscriptions, OpenAI, OpenRouter, Groq, local LiteLLM, any OpenAI-compat endpoint — your tools don't need to change.",
5
5
  "type": "module",
6
6
  "bin": {