@hanamorilabs/tab 0.1.5 → 0.1.7

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/README.md CHANGED
@@ -38,6 +38,29 @@ is keyed, and keeps it in the config file so `tab claude` is one command.
38
38
  Set `FLOCKTAB_PROXY_URL`, `FLOCKTAB_KEY` and `FLOCKTAB_UNLOCK` instead when
39
39
  you would rather not have a file or a folder, for example in CI.
40
40
 
41
+ ## On a subscription (Claude Max, ChatGPT)
42
+
43
+ ```
44
+ tab subscribe claude --plan "Claude Max 5x" --price 100 # Claude Code on your Max plan
45
+ tab subscribe codex # Codex on your ChatGPT plan
46
+ tab claude # your own Claude login through the tab
47
+ tab codex # your own ChatGPT login (codex login once, inside tab codex)
48
+ tab grok # still metered: the flock's xAI key, charged to the cap
49
+ tab quota # 5h / 7d windows the vendors reported on the last call
50
+ tab unsubscribe codex # Codex back on the meter
51
+ ```
52
+
53
+ A subscription belongs to the harness, not the Agent: Claude Code can be on
54
+ Max while Codex and Grok on the same Agent stay on the meter. The plan pays
55
+ for the tokens; the tab still gates every call (kill switch, velocity, model
56
+ and tool policy) and records it at list price as a `SUBSCRIPTION` row that
57
+ never touches the cap. Claude Code gets
58
+ `ANTHROPIC_BASE_URL=<proxy>/t/<key>/anthropic` and no API key; Codex gets a
59
+ home whose `chatgpt_base_url` is `<proxy>/t/<key>/openai/backend-api/`, with
60
+ your existing `~/.codex/auth.json` copied in once (never written back). The
61
+ rows are read on every launch, so the console's **Subscriptions** panel and
62
+ `tab subscribe` agree. `docs/subscriptions.md` has the wire.
63
+
41
64
  ## Self-hosted
42
65
 
43
66
  The proxy runs on your box and holds the provider keys; the ledger, tabs and
@@ -59,6 +82,9 @@ Codex shows `FlockTab - Self-hosted` as its provider.
59
82
  tab login approve this machine in the console, once
60
83
  tab <agent> [args] claude, codex, grok, kimi, gemini, or any command, on the tab
61
84
  tab use pick or change the Agent this folder runs as (.flocktab)
85
+ tab subscribe claude|codex [agent] that harness on your own plan (--plan, --price)
86
+ tab unsubscribe claude|codex [agent] back on the metered tab for that harness
87
+ tab quota [agent] what the plans have left: 5h and 7d windows
62
88
  tab alias setup <name>... make plain `codex` run `tab codex` (shims in ~/.flocktab/bin)
63
89
  tab alias remove <name>... undo that; `tab alias list` shows them
64
90
  tab status flock, folder Agent, proxy health, provider key state
@@ -76,7 +102,7 @@ session from `tab login`. An Agent is named by slug or name; left out, it is
76
102
  this folder's Agent. Every read takes `--json`.
77
103
 
78
104
  ```
79
- tab list every Agent: state, spent of cap, window, project
105
+ tab list every Agent: state, spent of cap, window, plans (claude, codex), project
80
106
  tab close my-project tab open my-project the kill switch (402 tab_closed on the next call)
81
107
  tab cap my-project 25 --window week cap in dollars, and the window
82
108
  tab rename my-project "My project (nightly)" the slug stays
@@ -121,5 +147,6 @@ proxy http://127.0.0.1:8787 (self-hosted) up
121
147
  flock Hanamori Labs
122
148
  folder flocktab-codex
123
149
  plan team
150
+ plans none; every harness on the metered tab
124
151
  byok provider keys on this box
125
152
  ```
package/dist/cli.js CHANGED
@@ -7,6 +7,9 @@
7
7
  * tab codex [...args] Codex on the tab
8
8
  * tab <cmd> [...args] anything else, both APIs pointed at the tab
9
9
  * tab use pick (or change) the Agent this folder runs as
10
+ * tab subscribe claude this folder's Agent runs Claude Code on your Claude Max plan
11
+ * tab subscribe codex ...and Codex on your ChatGPT plan; tab unsubscribe undoes it
12
+ * tab quota what the plans have left (5h / 7d windows) for this Agent
10
13
  * tab alias setup codex make plain `codex` run `tab codex` (shims in ~/.flocktab/bin)
11
14
  * tab alias remove codex undo that; `tab alias list` shows them
12
15
  * tab status which flock, which Agent here, is the proxy up
@@ -25,7 +28,7 @@ import { hostname } from "node:os";
25
28
  import { createInterface } from "node:readline/promises";
26
29
  import { stdin, stdout } from "node:process";
27
30
  import { bold, box, cyan, dim, green, heading, line, rows, underline, yellow } from "./ui.js";
28
- import { envFor, knownClients } from "./clients.js";
31
+ import { clientFor, envFor, knownClients } from "./clients.js";
29
32
  import { prepareCodexHome } from "./codex-home.js";
30
33
  import { ConsoleApiError, createAgent, issueAgentKey, listAgents } from "./console-api.js";
31
34
  import { consoleUrlFor, DeviceLoginError, startDeviceLogin, waitForApproval } from "./device-login.js";
@@ -53,6 +56,9 @@ function usage() {
53
56
  cmd(others.join(" | tab "), "the same, for those agents"),
54
57
  cmd("<command> [args]", "any other agent, both APIs pointed at the tab"),
55
58
  cmd("use", "pick or change the Agent this folder runs as (.flocktab)"),
59
+ cmd("subscribe claude|codex", "run that harness on your own plan (--plan \"Max 5x\" --price 100)"),
60
+ cmd("unsubscribe claude|codex", "back on the metered tab for that harness"),
61
+ cmd("quota [agent]", "what the plans have left: 5h and 7d windows"),
56
62
  cmd("alias setup <name>...", "make plain `codex` run `tab codex` (shims in ~/.flocktab/bin)"),
57
63
  cmd("alias remove <name>...", "undo that; `tab alias list` shows them"),
58
64
  cmd("status", "which flock, which Agent here, is the proxy up"),
@@ -480,6 +486,8 @@ async function status() {
480
486
  }
481
487
  else {
482
488
  pairs.push(["plan", me.flock.plan]);
489
+ const plans = (me.subscriptions ?? []).map((p) => (p === "anthropic" ? "claude" : p === "openai" ? "codex" : p));
490
+ pairs.push(["plans", plans.length > 0 ? `${bold(plans.join(", "))} ${dim("on your own login; everything else metered")}` : dim("none; every harness on the metered tab")]);
483
491
  const byok = !me.byok
484
492
  ? dim("no provider key on this flock")
485
493
  : me.unlock === "none"
@@ -506,9 +514,21 @@ async function runAgent(name, args) {
506
514
  const agent = await resolveAgent(config);
507
515
  if (!agent)
508
516
  return 1;
509
- const key = presentedKey({ key: agent.key, unlock: config.unlock });
517
+ // Which harness is this, and is it on the person's own plan? The
518
+ // subscription belongs to the harness (Claude Code: anthropic, Codex:
519
+ // openai), read fresh on every launch so `tab subscribe` or the console
520
+ // applies to the next run. Any other harness is metered.
521
+ const base = clientFor(name);
522
+ const vendor = base.shape === "anthropic" ? "anthropic" : base.isolatedHome === "codex" ? "openai" : undefined;
523
+ const me = await whoami(config.proxyUrl, { key: agent.key, unlock: config.unlock });
524
+ const auth = vendor && !("error" in me) && (me.subscriptions ?? []).includes(vendor) ? "subscription" : "key";
525
+ // On a subscription there is no provider key to unlock; the bare key rides in the URL.
526
+ const key = auth === "subscription" ? agent.key : presentedKey({ key: agent.key, unlock: config.unlock });
510
527
  reportFolder(config.proxyUrl, key);
511
- const { spec, env } = envFor({ name, proxyUrl: config.proxyUrl, presentedKey: key });
528
+ const { spec, env } = envFor({ name, proxyUrl: config.proxyUrl, presentedKey: key, auth });
529
+ if (auth === "subscription") {
530
+ say(dim(`${spec.label} runs on your own ${vendor === "openai" ? "ChatGPT" : "Claude"} login; FlockTab keeps the record and the kill switch.`));
531
+ }
512
532
  // An alias shim named like the agent must not be what we spawn.
513
533
  env.PATH = pathWithoutShims();
514
534
  if (spec.isolatedHome === "codex") {
@@ -517,6 +537,7 @@ async function runAgent(name, args) {
517
537
  proxyUrl: config.proxyUrl,
518
538
  presentedKey: key,
519
539
  mode: config.mode,
540
+ auth,
520
541
  });
521
542
  }
522
543
  const child = spawn(spec.command, [...(spec.args ?? []), ...args], { stdio: "inherit", env });
@@ -720,6 +741,12 @@ async function managed(command, rest) {
720
741
  return await manage.outside(config, flags);
721
742
  case "live":
722
743
  return await manage.live(config, flags);
744
+ case "quota":
745
+ return await manage.quota(config, flags);
746
+ case "subscribe":
747
+ return await manage.subscribe(config, flags, true);
748
+ case "unsubscribe":
749
+ return await manage.subscribe(config, flags, false);
723
750
  default:
724
751
  return await manage.web(config, flags);
725
752
  }
@@ -769,6 +796,9 @@ async function main(argv) {
769
796
  case "spend":
770
797
  case "outside":
771
798
  case "live":
799
+ case "quota":
800
+ case "subscribe":
801
+ case "unsubscribe":
772
802
  case "web":
773
803
  return managed(command, rest);
774
804
  case "status":
package/dist/clients.js CHANGED
@@ -72,11 +72,27 @@ export function openaiBase(proxyUrl, provider) {
72
72
  * overridden on purpose: a stray `OPENAI_BASE_URL` from another tool would
73
73
  * otherwise route around the tab, which is the one thing this must not do.
74
74
  */
75
+ /** `<proxy>/t/<key>/<provider>`: the subscription passthrough base for a vendor. */
76
+ export function passthroughBase(proxyUrl, key, provider) {
77
+ return `${proxyUrl}/t/${key}/${provider}`;
78
+ }
75
79
  export function envFor(input) {
76
- const spec = clientFor(input.name);
80
+ const base = clientFor(input.name);
77
81
  const known = input.name in KNOWN;
82
+ const auth = input.auth ?? "key";
78
83
  const env = { ...(input.base ?? process.env) };
84
+ // Codex: pinned to the API key on a metered tab, to the ChatGPT login on a subscription.
85
+ const spec = base.isolatedHome === "codex"
86
+ ? { ...base, args: ["-c", auth === "subscription" ? 'preferred_auth_method="chatgpt"' : 'preferred_auth_method="apikey"'] }
87
+ : base;
79
88
  const setAnthropic = () => {
89
+ if (auth === "subscription") {
90
+ // Claude Code signs in with its own Claude account; FlockTab only moves the base URL.
91
+ env.ANTHROPIC_BASE_URL = passthroughBase(input.proxyUrl, input.presentedKey, "anthropic");
92
+ delete env.ANTHROPIC_API_KEY;
93
+ delete env.ANTHROPIC_AUTH_TOKEN;
94
+ return;
95
+ }
80
96
  env.ANTHROPIC_BASE_URL = input.proxyUrl;
81
97
  env.ANTHROPIC_API_KEY = input.presentedKey;
82
98
  // Claude Code prefers an auth token when one is set; make sure a stale
@@ -84,6 +100,13 @@ export function envFor(input) {
84
100
  delete env.ANTHROPIC_AUTH_TOKEN;
85
101
  };
86
102
  const setOpenAI = () => {
103
+ if (auth === "subscription") {
104
+ // Only Codex has a subscription route (its own home points at it); a
105
+ // plain OpenAI key would bypass the plan, so none is set.
106
+ delete env.OPENAI_BASE_URL;
107
+ delete env.OPENAI_API_KEY;
108
+ return;
109
+ }
87
110
  env.OPENAI_BASE_URL = openaiBase(input.proxyUrl, spec.provider);
88
111
  env.OPENAI_API_KEY = input.presentedKey;
89
112
  };
@@ -99,5 +122,6 @@ export function envFor(input) {
99
122
  }
100
123
  // So the child, and anything it spawns, can tell it is on a tab.
101
124
  env.FLOCKTAB_PROXY_URL = input.proxyUrl;
125
+ env.FLOCKTAB_AUTH_MODE = auth;
102
126
  return { spec, env };
103
127
  }
@@ -10,8 +10,10 @@
10
10
  *
11
11
  * The person's own `~/.codex` is never read or written.
12
12
  */
13
- import { chmod, mkdir, writeFile } from "node:fs/promises";
13
+ import { chmod, copyFile, mkdir, readFile, unlink, writeFile } from "node:fs/promises";
14
+ import { homedir } from "node:os";
14
15
  import path from "node:path";
16
+ import { passthroughBase } from "./clients.js";
15
17
  export function codexAuthJson(presentedKey) {
16
18
  return `${JSON.stringify({ auth_mode: "apikey", OPENAI_API_KEY: presentedKey })}\n`;
17
19
  }
@@ -19,6 +21,20 @@ export function codexAuthJson(presentedKey) {
19
21
  export function codexProviderId(mode) {
20
22
  return mode === "self-hosted" ? "FlockTab - Self-hosted" : "FlockTab - Hosted";
21
23
  }
24
+ /**
25
+ * On a ChatGPT subscription Codex talks to `chatgpt_base_url` with its own
26
+ * login, so that is what moves: to the passthrough, where the tab keeps the
27
+ * record. No provider block; the default OpenAI provider is the ChatGPT one.
28
+ */
29
+ export function codexSubscriptionToml(proxyUrl, presentedKey, model) {
30
+ const lines = [
31
+ `preferred_auth_method = "chatgpt"`,
32
+ `chatgpt_base_url = ${JSON.stringify(`${passthroughBase(proxyUrl, presentedKey, "openai")}/backend-api/`)}`,
33
+ ...(model ? [`model = ${JSON.stringify(model)}`] : []),
34
+ ``,
35
+ ];
36
+ return lines.join("\n");
37
+ }
22
38
  export function codexConfigToml(proxyUrl, model, mode = "hosted") {
23
39
  const id = codexProviderId(mode);
24
40
  const lines = [
@@ -41,9 +57,46 @@ export async function prepareCodexHome(input) {
41
57
  await chmod(home, 0o700);
42
58
  const auth = path.join(home, "auth.json");
43
59
  const config = path.join(home, "config.toml");
44
- await writeFile(auth, codexAuthJson(input.presentedKey), { mode: 0o600 });
45
- await writeFile(config, codexConfigToml(input.proxyUrl, input.model, input.mode ?? "hosted"), { mode: 0o600 });
46
- await chmod(auth, 0o600);
60
+ if (input.auth === "subscription") {
61
+ await adoptChatGptLogin(auth, input.userCodexHome ?? path.join(homedir(), ".codex"));
62
+ await writeFile(config, codexSubscriptionToml(input.proxyUrl, input.presentedKey, input.model), { mode: 0o600 });
63
+ }
64
+ else {
65
+ await writeFile(auth, codexAuthJson(input.presentedKey), { mode: 0o600 });
66
+ await chmod(auth, 0o600);
67
+ await writeFile(config, codexConfigToml(input.proxyUrl, input.model, input.mode ?? "hosted"), { mode: 0o600 });
68
+ }
47
69
  await chmod(config, 0o600);
48
70
  return home;
49
71
  }
72
+ /**
73
+ * The ChatGPT login for the tab's Codex home. A login already there (from
74
+ * `codex login` run through `tab codex`) stays. Otherwise the person's own
75
+ * `~/.codex/auth.json` is copied, owner-only: same machine, same person,
76
+ * and `~/.codex` itself is still never written. A leftover API-key auth
77
+ * file from metered mode is replaced; with nothing to copy, Codex asks the
78
+ * person to sign in and keeps that login in the tab home.
79
+ */
80
+ async function adoptChatGptLogin(auth, userCodexHome) {
81
+ const isApiKey = async (file) => {
82
+ try {
83
+ const parsed = JSON.parse(await readFile(file, "utf8"));
84
+ return parsed.auth_mode === "apikey" || (typeof parsed.OPENAI_API_KEY === "string" && parsed.OPENAI_API_KEY.startsWith("ft_"));
85
+ }
86
+ catch {
87
+ return undefined;
88
+ }
89
+ };
90
+ const existing = await isApiKey(auth);
91
+ if (existing === false)
92
+ return "kept";
93
+ if (existing === true)
94
+ await unlink(auth).catch(() => undefined);
95
+ const theirs = path.join(userCodexHome, "auth.json");
96
+ if ((await isApiKey(theirs)) === false) {
97
+ await copyFile(theirs, auth);
98
+ await chmod(auth, 0o600);
99
+ return "copied";
100
+ }
101
+ return "none";
102
+ }
package/dist/manage.js CHANGED
@@ -59,6 +59,12 @@ export function parseFlags(argv) {
59
59
  }
60
60
  return flags;
61
61
  }
62
+ /** `anthropic` is Claude Code, `openai` is Codex: say the harness, not the vendor. */
63
+ const HARNESS = { anthropic: "claude", openai: "codex" };
64
+ const plans = (t) => {
65
+ const subs = t.subscriptions ?? [];
66
+ return subs.length === 0 ? dim("-") : subs.map((s) => `${yellow(HARNESS[s.provider] ?? s.provider)}${s.plan ? dim(` ${s.plan}`) : ""}`).join(", ");
67
+ };
62
68
  async function tabsOf(call) {
63
69
  return (await call("GET", "/api/cli/tabs")).tabs;
64
70
  }
@@ -92,7 +98,7 @@ export async function list(config, flags) {
92
98
  const tabs = await tabsOf(api(config));
93
99
  emit(flags.json, { tabs }, () => tabs.length === 0
94
100
  ? dim("No Agents yet. Run tab claude or tab codex in a project folder.")
95
- : table(["agent", "state", "spent", "cap", "used", "window", "project"], tabs.map((t) => [t.name === t.slug ? t.slug : `${t.name} ${dim(t.slug)}`, state(t.state), money(t.spentCents), money(t.capCents), pct(t.spentCents, t.capCents), t.window, t.projectName ?? dim("-")]), { right: [2, 3, 4] }));
101
+ : table(["agent", "state", "spent", "cap", "used", "window", "plans", "project"], tabs.map((t) => [t.name === t.slug ? t.slug : `${t.name} ${dim(t.slug)}`, state(t.state), money(t.spentCents), money(t.capCents), pct(t.spentCents, t.capCents), t.window, plans(t), t.projectName ?? dim("-")]), { right: [2, 3, 4] }));
96
102
  return 0;
97
103
  }
98
104
  export async function setState(config, flags, next) {
@@ -139,6 +145,59 @@ export async function archive(config, flags, ask) {
139
145
  emit(flags.json, { tab, archived: true }, () => `${bold(tab.name)} archived.`);
140
146
  return 0;
141
147
  }
148
+ /**
149
+ * `tab subscribe claude [--plan "Claude Max 5x"] [--price 100]` and
150
+ * `tab unsubscribe codex`: which harness of an Agent runs on the person's
151
+ * own plan. The subscription belongs to the harness, so Claude Code can be
152
+ * on Max while Codex and Grok on the same Agent stay on the meter. On a
153
+ * plan the vendor pays; the tab still gates every call and records it at
154
+ * list price as a shadow row, and the seat's monthly price sits beside the
155
+ * meter in the project rollup.
156
+ */
157
+ export async function subscribe(config, flags, on) {
158
+ const call = api(config);
159
+ const verb = on ? "subscribe" : "unsubscribe";
160
+ const [harness, given] = flags.args;
161
+ if (!harness)
162
+ throw new ManageError(`tab ${verb} claude|codex [agent]${on ? ' [--plan "Claude Max 5x"] [--price 100]' : ""}`);
163
+ const slug = await resolveSlug(call, given);
164
+ const path = `/api/cli/tabs/${encodeURIComponent(slug)}/subscriptions/${encodeURIComponent(harness.toLowerCase())}`;
165
+ const body = {};
166
+ if (flags.opts.plan !== undefined)
167
+ body.plan = flags.opts.plan;
168
+ if (flags.opts.price !== undefined)
169
+ body.monthlyDollars = flags.opts.price.replace(/^\$/, "");
170
+ const { tab } = await call(on ? "PUT" : "DELETE", path, on ? body : undefined);
171
+ const provider = harness.toLowerCase() === "codex" || harness.toLowerCase() === "chatgpt" || harness.toLowerCase() === "openai" ? "openai" : "anthropic";
172
+ const seat = (tab.subscriptions ?? []).find((s) => s.provider === provider);
173
+ const label = HARNESS[provider] ?? provider;
174
+ emit(flags.json, { tab }, () => on
175
+ ? `${bold(tab.name)}: ${bold(label)} runs on your own ${provider === "openai" ? "ChatGPT" : "Claude"} login${seat?.plan ? ` (${seat.plan}${seat.monthlyCents && seat.monthlyCents !== "0" ? `, ${money(seat.monthlyCents)}/mo` : ""})` : ""}.\n` +
176
+ `${dim(`tab ${label} uses that login from the next run. The kill switch and policies still apply; calls land in the ledger at list price, never on the tab's spend. Other harnesses stay metered.`)}`
177
+ : `${bold(tab.name)}: ${bold(label)} is back on the metered tab: the flock's provider key, charged to the cap.`);
178
+ return 0;
179
+ }
180
+ /** `tab quota [agent]`: what the vendor said the plan has left, on the last call. */
181
+ export async function quota(config, flags) {
182
+ const call = api(config);
183
+ const slug = await resolveSlug(call, flags.args[0]);
184
+ const { tab, quota: windows } = await call("GET", `/api/cli/tabs/${encodeURIComponent(slug)}/quota`);
185
+ emit(flags.json, { tab, quota: windows }, () => {
186
+ if ((tab.subscriptions ?? []).length === 0)
187
+ return `${bold(tab.name)} has no harness on a plan; ${dim("tab subscribe claude")} or ${dim("tab subscribe codex")} puts one there.`;
188
+ if (windows.length === 0)
189
+ return `${bold(tab.name)}: no quota reported yet. It shows after the first call through tab claude or tab codex.`;
190
+ const left = (w) => {
191
+ const pctLeft = Math.max(0, 100 - w.usedPct);
192
+ const paint = pctLeft <= 10 ? red : pctLeft <= 30 ? yellow : green;
193
+ return paint(`${pctLeft}% left`);
194
+ };
195
+ const when = (iso) => (iso ? new Date(iso).toLocaleString() : dim("-"));
196
+ return (`${bold(tab.name)} ${plans(tab)}\n` +
197
+ table(["provider", "window", "used", "left", "resets", "seen"], windows.map((w) => [w.provider, w.window, `${w.usedPct}%`, left(w), when(w.resetsAt), when(w.observedAt)]), { right: [2] }));
198
+ });
199
+ return 0;
200
+ }
142
201
  function listOpt(raw) {
143
202
  if (raw === undefined)
144
203
  return undefined;
package/dist/proxy-bin.js CHANGED
@@ -13,7 +13,7 @@ import { createRequire } from "node:module";
13
13
  import path from "node:path";
14
14
  import { configDir } from "./config.js";
15
15
  /** The proxy release `tab up` fetches. Bump with each proxy tag. */
16
- export const PROXY_VERSION = "0.1.1";
16
+ export const PROXY_VERSION = "0.1.4";
17
17
  export const RELEASES = "https://github.com/joseairosa/flocktab/releases/download";
18
18
  export function targetFor(platform = process.platform, arch = process.arch) {
19
19
  if (platform === "darwin")
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  /** Written by scripts/write-version.mjs from package.json at build; `tab version` prints it. */
2
- export const TAB_VERSION = "0.1.5";
2
+ export const TAB_VERSION = "0.1.7";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanamorilabs/tab",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Run any AI agent on a FlockTab tab: tab claude, tab codex, tab <command>.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -22,11 +22,11 @@
22
22
  "prepublishOnly": "pnpm build"
23
23
  },
24
24
  "optionalDependencies": {
25
- "@hanamorilabs/flocktab-proxy-darwin-arm64": "0.1.1",
26
- "@hanamorilabs/flocktab-proxy-darwin-x64": "0.1.1",
27
- "@hanamorilabs/flocktab-proxy-linux-x64": "0.1.1",
28
- "@hanamorilabs/flocktab-proxy-linux-arm64": "0.1.1",
29
- "@hanamorilabs/flocktab-proxy-win-x64": "0.1.1"
25
+ "@hanamorilabs/flocktab-proxy-darwin-arm64": "0.1.4",
26
+ "@hanamorilabs/flocktab-proxy-darwin-x64": "0.1.4",
27
+ "@hanamorilabs/flocktab-proxy-linux-x64": "0.1.4",
28
+ "@hanamorilabs/flocktab-proxy-linux-arm64": "0.1.4",
29
+ "@hanamorilabs/flocktab-proxy-win-x64": "0.1.4"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@types/node": "^22.18.6",