pi-freeflow 1.23.0 → 1.23.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.
- package/CHANGELOG.md +6 -0
- package/README.md +2 -0
- package/package.json +1 -1
- package/src/cline-accounts.ts +42 -0
- package/src/commands.ts +16 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.23.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Cline logins no longer accept the same account twice. Two logins for one account share a single free quota, so the second one added nothing while making it look like you had more capacity — signing in with an account you already saved now says which slot has it and leaves your logins untouched. If a set of logins already contains the same account twice, `/freeflow cline accounts` marks the later one `— same account as [slot]` so you can tell them apart and remove the spare.
|
|
8
|
+
|
|
3
9
|
## 1.23.0
|
|
4
10
|
|
|
5
11
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -239,6 +239,8 @@ Rotating per-account promo, direct only (never through the relay pool). Sign in
|
|
|
239
239
|
|
|
240
240
|
Add as many logins as you want — each one gets its own slot (`default`, `slot-2`, `slot-3`, …) and a request that hits a limit on one rolls straight to the next. To sign in with a different email, sign out of Cline in that browser first (`/freeflow cline signout` prints the link), or use a private window so your current session stays put.
|
|
241
241
|
|
|
242
|
+
Each account can only be saved once: an extra login for an account you already have shares the same free quota, so it is refused rather than saved. If a pool already holds two logins for one account (saved before this check), `/freeflow cline accounts` marks the later one `— same account as [default]`, and `/freeflow cline logout <slot>` drops it.
|
|
243
|
+
|
|
242
244
|
| Model ID | Creator / Lab | Context | Max Output | Thinking | Vision |
|
|
243
245
|
| :--- | :--- | :--- | :--- | :--- | :--- |
|
|
244
246
|
| `cline-free/deepseek-v4.1-flash` | DeepSeek | **1M** (1.000.000) | **384K** (384.000) | `low/high/max` | ✅ |
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-freeflow",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.23.
|
|
4
|
+
"version": "1.23.1",
|
|
5
5
|
"description": "Thin provider for OMP/Pi — model list + dumb relay proxy + log; host pi-ai owns thinking/normalization",
|
|
6
6
|
"main": "extensions/index.ts",
|
|
7
7
|
"types": "src/index.ts",
|
package/src/cline-accounts.ts
CHANGED
|
@@ -238,11 +238,48 @@ function markActiveSlotOnDisk(slot: string): void {
|
|
|
238
238
|
savePool(live);
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
+
/**
|
|
242
|
+
* Identity of a Cline login. Two slots holding the same account share one free
|
|
243
|
+
* quota, so rotating between them buys nothing — callers use this to detect
|
|
244
|
+
* that instead of saving a duplicate.
|
|
245
|
+
*
|
|
246
|
+
* accountId (WorkOS user id) is authoritative; email is the readable fallback.
|
|
247
|
+
* When neither side carries an identity — a legacy `clp_` key slot has none —
|
|
248
|
+
* only an identical bearer proves the same account.
|
|
249
|
+
*/
|
|
250
|
+
function sameClineAccount(account: ClineAccount, identity: { token?: string; accountId?: string; email?: string }): boolean {
|
|
251
|
+
const idA = account.accountId?.trim();
|
|
252
|
+
const idB = identity.accountId?.trim();
|
|
253
|
+
if (idA && idB) return idA === idB;
|
|
254
|
+
const mailA = account.email?.trim().toLowerCase();
|
|
255
|
+
const mailB = identity.email?.trim().toLowerCase();
|
|
256
|
+
if (mailA && mailB) return mailA === mailB;
|
|
257
|
+
const token = identity.token;
|
|
258
|
+
return typeof token === "string" && token.length > 0 && account.token === token;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* The other slot already holding this account, or null when it is new.
|
|
263
|
+
* `exceptSlot` skips the slot being written, so re-logging into an existing
|
|
264
|
+
* slot is never treated as a duplicate.
|
|
265
|
+
*/
|
|
266
|
+
export function findClineAccountSlot(
|
|
267
|
+
pool: ClinePoolState,
|
|
268
|
+
identity: { token?: string; accountId?: string; email?: string },
|
|
269
|
+
exceptSlot?: string,
|
|
270
|
+
): string | null {
|
|
271
|
+
const hit = pool.accounts.find(
|
|
272
|
+
(a) => a.slot !== exceptSlot && sameClineAccount(a, identity),
|
|
273
|
+
);
|
|
274
|
+
return hit ? hit.slot : null;
|
|
275
|
+
}
|
|
276
|
+
|
|
241
277
|
/**
|
|
242
278
|
* Save (or replace) one login slot. The token must carry the `workos:` prefix.
|
|
243
279
|
* Throws on bad input — the message never echoes the token.
|
|
244
280
|
* Extras carry the device-login grant (refresh token, expiry, identity);
|
|
245
281
|
* legacy key slots keep calling with two args and load untouched.
|
|
282
|
+
* Refuses an account that another slot already holds.
|
|
246
283
|
*/
|
|
247
284
|
export function addAccount(
|
|
248
285
|
slot: string,
|
|
@@ -255,6 +292,11 @@ export function addAccount(
|
|
|
255
292
|
if (!token.startsWith("workos:") && !isWorkosJwt(token) && !token.startsWith("clp_")) throw new Error("Cline token must be a workos: login grant or a clp_ API key");
|
|
256
293
|
const pool = loadPool();
|
|
257
294
|
const existing = pool.accounts.find((a) => a.slot === cleanSlot);
|
|
295
|
+
const duplicate = findClineAccountSlot(pool, { token, accountId: extras?.accountId, email: extras?.email }, cleanSlot);
|
|
296
|
+
if (duplicate) {
|
|
297
|
+
const who = extras?.email?.trim() || existing?.email?.trim() || "this account";
|
|
298
|
+
throw new Error(`That Cline account (${who}) is already saved as [${duplicate}] — log in with a different account, or use /freeflow cline logout ${duplicate} first`);
|
|
299
|
+
}
|
|
258
300
|
if (existing) {
|
|
259
301
|
existing.token = token;
|
|
260
302
|
if (extras?.refreshToken) existing.refreshToken = extras.refreshToken;
|
package/src/commands.ts
CHANGED
|
@@ -58,7 +58,7 @@ import {
|
|
|
58
58
|
formatRelayPickerItem,
|
|
59
59
|
formatRelayStatusLabel,
|
|
60
60
|
} from "./relay-state.ts";
|
|
61
|
-
import { addAccount, loadPool, redactedToken, removeAccount } from "./cline-accounts.ts";
|
|
61
|
+
import { addAccount, findClineAccountSlot, loadPool, redactedToken, removeAccount } from "./cline-accounts.ts";
|
|
62
62
|
import type { ClinePoolState } from "./cline-accounts.ts";
|
|
63
63
|
import { CLINE_BROWSER_SIGNOUT_URL, pollDeviceToken, registerClineToken, startDeviceAuth, toApiKey } from "./cline-device-auth.ts";
|
|
64
64
|
import { isCertError } from "./system-ca-fetch.ts";
|
|
@@ -369,7 +369,12 @@ function formatClineAccountLines(pool: ClinePoolState): string[] {
|
|
|
369
369
|
const star = a.slot === pool.activeSlot ? "*" : " ";
|
|
370
370
|
const who = a.email || a.accountId;
|
|
371
371
|
const whoPart = who ? ` ${who}` : "";
|
|
372
|
-
|
|
372
|
+
// Two slots on one account share one quota, so rotation between them buys
|
|
373
|
+
// nothing. Mark the later one against the earlier slot it duplicates.
|
|
374
|
+
const earlier = { accounts: pool.accounts.slice(0, idx) };
|
|
375
|
+
const dup = findClineAccountSlot(earlier, a);
|
|
376
|
+
const dupPart = dup ? ` — same account as [${dup}]` : "";
|
|
377
|
+
return `${star} [${idx + 1}] [${a.slot}]${whoPart} key ending ${redactedToken(a.token)}${dupPart}`;
|
|
373
378
|
});
|
|
374
379
|
}
|
|
375
380
|
/** User-safe one-line message for caught values (never echoes secrets). */
|
|
@@ -1475,6 +1480,15 @@ export function createCommandSpec(
|
|
|
1475
1480
|
const deviceTokens = await pollDeviceToken(undefined, started.deviceCode, started.interval, { maxWaitMs: Math.max(60_000, started.expiresIn * 1000) });
|
|
1476
1481
|
const creds = await registerClineToken(undefined, deviceTokens.accessToken, deviceTokens.refreshToken);
|
|
1477
1482
|
const apiKey = toApiKey(creds.access);
|
|
1483
|
+
const dup = findClineAccountSlot(loadPool(), { token: apiKey, accountId: creds.accountId, email: creds.email }, slot);
|
|
1484
|
+
if (dup) {
|
|
1485
|
+
// Same account twice shares one free quota, so it would never add capacity.
|
|
1486
|
+
ctx.ui.notify(
|
|
1487
|
+
`That Cline account${creds.email ? ` (${creds.email})` : ""} is already saved as [${dup}] — nothing added.\nUse a different account: /freeflow cline signout shows the sign-out link. Or drop the existing one first: /freeflow cline logout ${dup}`,
|
|
1488
|
+
"warning",
|
|
1489
|
+
);
|
|
1490
|
+
return;
|
|
1491
|
+
}
|
|
1478
1492
|
addAccount(slot, apiKey, {
|
|
1479
1493
|
refreshToken: creds.refresh,
|
|
1480
1494
|
expiresAt: creds.expires,
|