pennyrouter 0.3.32 → 0.3.34
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 +4 -0
- package/package.json +1 -1
- package/src/cli.js +37 -14
- package/src/launch.js +63 -1
- package/standalone/pane.py +30 -18
- package/standalone/penny-compare +4 -2
package/README.md
CHANGED
|
@@ -97,6 +97,7 @@ directory to the user PATH:
|
|
|
97
97
|
|
|
98
98
|
```bash
|
|
99
99
|
penny claude
|
|
100
|
+
penny claude --stats
|
|
100
101
|
penny claude desktop
|
|
101
102
|
penny claude desktop gateway
|
|
102
103
|
penny claude desktop native
|
|
@@ -108,6 +109,9 @@ penny codex desktop native
|
|
|
108
109
|
penny opencode
|
|
109
110
|
```
|
|
110
111
|
|
|
112
|
+
`penny claude --stats` launches Claude Code with Penny Compare's live single-session token and
|
|
113
|
+
cost readout. Other arguments still pass through, so `penny claude --stats --resume` works.
|
|
114
|
+
|
|
111
115
|
Arguments are passed through to the native harness, so commands such as `penny claude --resume`,
|
|
112
116
|
`penny codex resume --last`, and `penny opencode --continue` work normally. By default, bare
|
|
113
117
|
`claude`, `codex`, and `opencode` remain
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -1595,9 +1595,7 @@ async function selectCodexFundingProviders(flags, selected) {
|
|
|
1595
1595
|
|
|
1596
1596
|
console.log("PennyRouter can use your ChatGPT subscription, an OpenAI API key, an OpenAI-compatible proxy, or an OpenRouter API key to run Codex.");
|
|
1597
1597
|
console.log("Choose one or more providers. PennyRouter will try them in order when available.");
|
|
1598
|
-
const options = CODEX_PROVIDER_OPTIONS
|
|
1599
|
-
({ id }) => id !== "subscription" || hasChatgptLogin,
|
|
1600
|
-
);
|
|
1598
|
+
const options = CODEX_PROVIDER_OPTIONS;
|
|
1601
1599
|
return promptFundingProviderCheckboxes(
|
|
1602
1600
|
options,
|
|
1603
1601
|
"Which funding source should PennyRouter use for Codex?",
|
|
@@ -1703,7 +1701,7 @@ async function configureUserFunding(flags, selected, claudeAnthropicAuth,
|
|
|
1703
1701
|
|| process.env.PENNYROUTER_ANTHROPIC_PROXY_API_KEY
|
|
1704
1702
|
|| process.env.PENNYROUTER_PROXY_API_KEY || "",
|
|
1705
1703
|
).trim();
|
|
1706
|
-
|
|
1704
|
+
let codexSubscriptionAuth = wantsCodex && codexProviders.has("subscription")
|
|
1707
1705
|
? await detectCodexChatgptAuth()
|
|
1708
1706
|
: false;
|
|
1709
1707
|
|
|
@@ -1729,9 +1727,15 @@ async function configureUserFunding(flags, selected, claudeAnthropicAuth,
|
|
|
1729
1727
|
}
|
|
1730
1728
|
}
|
|
1731
1729
|
if (wantsCodex && codexProviders.has("subscription") && !codexSubscriptionAuth) {
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1730
|
+
console.log("Codex needs to sign in to your ChatGPT account. Starting `codex login`...");
|
|
1731
|
+
const login = spawnSync("codex", ["login"], {
|
|
1732
|
+
stdio: "inherit",
|
|
1733
|
+
shell: process.platform === "win32",
|
|
1734
|
+
});
|
|
1735
|
+
if (login.error || login.status !== 0 || !(await detectCodexChatgptAuth())) {
|
|
1736
|
+
throw new Error("Codex login did not complete. Run `codex login`, then rerun `npx pennyrouter install`.");
|
|
1737
|
+
}
|
|
1738
|
+
codexSubscriptionAuth = true;
|
|
1735
1739
|
}
|
|
1736
1740
|
if (wantsCodex && codexProviders.has("openai-api") && !openaiApiKey) {
|
|
1737
1741
|
if (!interactive) throw new Error("OpenAI API was selected, but no key was provided.");
|
|
@@ -2129,14 +2133,17 @@ async function selectHarnesses(flags) {
|
|
|
2129
2133
|
}
|
|
2130
2134
|
|
|
2131
2135
|
const detected = [];
|
|
2136
|
+
const available = [];
|
|
2132
2137
|
for (const id of DEFAULT_INSTALL_HARNESS_IDS) {
|
|
2133
2138
|
const harness = HARNESS_BY_ID[id];
|
|
2134
|
-
|
|
2139
|
+
const installed = await safeDetect(harness);
|
|
2140
|
+
if (installed) detected.push(harness);
|
|
2141
|
+
available.push({ ...harness, installed });
|
|
2135
2142
|
}
|
|
2136
2143
|
|
|
2137
2144
|
if (flags.yes) return detected;
|
|
2138
2145
|
|
|
2139
|
-
return promptHarnessCheckboxes(detected);
|
|
2146
|
+
return promptHarnessCheckboxes(available, detected);
|
|
2140
2147
|
}
|
|
2141
2148
|
|
|
2142
2149
|
async function printAdditionalHarnessesHint(selected) {
|
|
@@ -2153,10 +2160,14 @@ async function printAdditionalHarnessesHint(selected) {
|
|
|
2153
2160
|
console.log(`To use PennyRouter with ${names}, install ${available.length === 1 ? "it" : "them"} and re-run \`npx pennyrouter install\`.`);
|
|
2154
2161
|
}
|
|
2155
2162
|
|
|
2156
|
-
async function promptHarnessCheckboxes(detected) {
|
|
2163
|
+
async function promptHarnessCheckboxes(available, detected) {
|
|
2157
2164
|
const detectedIds = new Set(detected.map((harness) => harness.id));
|
|
2158
2165
|
const selectedIds = new Set(detectedIds);
|
|
2159
|
-
const harnesses = orderedHarnessesForPrompt(
|
|
2166
|
+
const harnesses = orderedHarnessesForPrompt(new Set(available.map((harness) => harness.id)))
|
|
2167
|
+
.map((harness) => ({
|
|
2168
|
+
...harness,
|
|
2169
|
+
installed: available.find((item) => item.id === harness.id)?.installed ?? false,
|
|
2170
|
+
}));
|
|
2160
2171
|
if (!input.isTTY || !output.isTTY) return promptHarnessCheckboxesByText(harnesses, selectedIds);
|
|
2161
2172
|
|
|
2162
2173
|
return promptHarnessCheckboxesByKeys(harnesses, selectedIds);
|
|
@@ -2178,7 +2189,8 @@ async function promptHarnessCheckboxesByKeys(harnesses, selectedIds) {
|
|
|
2178
2189
|
harnesses.forEach((harness, index) => {
|
|
2179
2190
|
const pointer = index === cursor ? color(">", "cyan") : " ";
|
|
2180
2191
|
const checked = selectedIds.has(harness.id) ? color("x", "green") : " ";
|
|
2181
|
-
const
|
|
2192
|
+
const label = harness.installed ? harness.name : `${harness.name} (install ${harness.name} first)`;
|
|
2193
|
+
const name = harness.installed && index === cursor ? color(label, "cyan") : label;
|
|
2182
2194
|
output.write(` ${pointer} [${checked}] ${name}\n`);
|
|
2183
2195
|
});
|
|
2184
2196
|
output.write("\nEnter to continue.\n");
|
|
@@ -2215,7 +2227,12 @@ async function promptHarnessCheckboxesByKeys(harnesses, selectedIds) {
|
|
|
2215
2227
|
}
|
|
2216
2228
|
|
|
2217
2229
|
if (key.name === "space") {
|
|
2218
|
-
const
|
|
2230
|
+
const harness = harnesses[cursor];
|
|
2231
|
+
if (!harness.installed) {
|
|
2232
|
+
render();
|
|
2233
|
+
return;
|
|
2234
|
+
}
|
|
2235
|
+
const id = harness.id;
|
|
2219
2236
|
if (selectedIds.has(id)) selectedIds.delete(id);
|
|
2220
2237
|
else selectedIds.add(id);
|
|
2221
2238
|
render();
|
|
@@ -2242,7 +2259,8 @@ async function promptHarnessCheckboxesByText(harnesses, selectedIds) {
|
|
|
2242
2259
|
console.log(color("Where should PennyRouter be installed?", "bold"));
|
|
2243
2260
|
for (const harness of harnesses) {
|
|
2244
2261
|
const checked = selectedIds.has(harness.id) ? color("x", "green") : " ";
|
|
2245
|
-
|
|
2262
|
+
const label = harness.installed ? harness.name : `${harness.name} (install ${harness.name} first)`;
|
|
2263
|
+
console.log(` [${checked}] ${label}`);
|
|
2246
2264
|
}
|
|
2247
2265
|
console.log("");
|
|
2248
2266
|
const answer = await rl.question(
|
|
@@ -2261,6 +2279,11 @@ async function promptHarnessCheckboxesByText(harnesses, selectedIds) {
|
|
|
2261
2279
|
console.log(`Unknown tool "${token}".`);
|
|
2262
2280
|
continue;
|
|
2263
2281
|
}
|
|
2282
|
+
if (!harnesses.find((harness) => harness.id === id)?.installed) {
|
|
2283
|
+
const unavailable = HARNESS_BY_ID[id];
|
|
2284
|
+
console.log(`Install ${unavailable.name} first to select it.`);
|
|
2285
|
+
continue;
|
|
2286
|
+
}
|
|
2264
2287
|
if (selectedIds.has(id)) selectedIds.delete(id);
|
|
2265
2288
|
else selectedIds.add(id);
|
|
2266
2289
|
}
|
package/src/launch.js
CHANGED
|
@@ -1240,6 +1240,25 @@ export function extractProfileArgs(args) {
|
|
|
1240
1240
|
return { profile, args: rest };
|
|
1241
1241
|
}
|
|
1242
1242
|
|
|
1243
|
+
/**
|
|
1244
|
+
* Remove the launcher-only stats flag while preserving every native harness argument.
|
|
1245
|
+
* A bare `--` ends Penny's option parsing, so a later `--stats` still reaches the harness.
|
|
1246
|
+
*/
|
|
1247
|
+
export function extractStatsArgs(args) {
|
|
1248
|
+
const rest = [];
|
|
1249
|
+
let stats = false;
|
|
1250
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
1251
|
+
const arg = args[i];
|
|
1252
|
+
if (arg === "--") {
|
|
1253
|
+
rest.push(...args.slice(i));
|
|
1254
|
+
break;
|
|
1255
|
+
}
|
|
1256
|
+
if (arg === "--stats") stats = true;
|
|
1257
|
+
else rest.push(arg);
|
|
1258
|
+
}
|
|
1259
|
+
return { stats, args: rest };
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1243
1262
|
function assertNoConflictingArgs(harness, args) {
|
|
1244
1263
|
if (harness === "claude" && args.some((arg) => arg === "--settings" || arg.startsWith("--settings="))) {
|
|
1245
1264
|
throw new Error("penny claude manages --settings; remove that flag or run native claude.");
|
|
@@ -1416,23 +1435,63 @@ async function waitForChild(child) {
|
|
|
1416
1435
|
});
|
|
1417
1436
|
}
|
|
1418
1437
|
|
|
1438
|
+
/** Launch Penny Claude inside Penny Compare's single-session token readout. */
|
|
1439
|
+
export async function runPennyClaudeStats(args, profile) {
|
|
1440
|
+
if (!(process.platform === "darwin" || process.platform === "linux")) {
|
|
1441
|
+
throw new Error("penny claude --stats is supported on macOS and Linux.");
|
|
1442
|
+
}
|
|
1443
|
+
const panePath = join(pennyCompareDir(), "pane.py");
|
|
1444
|
+
try {
|
|
1445
|
+
await stat(panePath);
|
|
1446
|
+
} catch {
|
|
1447
|
+
throw new Error(
|
|
1448
|
+
"Penny's stats display is not installed. Run `npx pennyrouter compare` and try again.",
|
|
1449
|
+
);
|
|
1450
|
+
}
|
|
1451
|
+
const child = spawn("python3", [
|
|
1452
|
+
panePath,
|
|
1453
|
+
"penny-claude",
|
|
1454
|
+
"--side",
|
|
1455
|
+
"single",
|
|
1456
|
+
"--cwd",
|
|
1457
|
+
process.cwd(),
|
|
1458
|
+
"--harness-args-json",
|
|
1459
|
+
JSON.stringify(["--penny-profile", profile, ...args]),
|
|
1460
|
+
], {
|
|
1461
|
+
cwd: process.cwd(),
|
|
1462
|
+
env: process.env,
|
|
1463
|
+
stdio: "inherit",
|
|
1464
|
+
});
|
|
1465
|
+
const result = await waitForChild(child);
|
|
1466
|
+
if (result.signal) return 128 + (osConstants.signals[result.signal] || 0);
|
|
1467
|
+
return result.code ?? 1;
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1419
1470
|
export async function runPenny(argv = process.argv.slice(2)) {
|
|
1420
1471
|
const harness = argv[0];
|
|
1421
1472
|
if (!harness || harness === "help" || harness === "--help" || harness === "-h") {
|
|
1422
1473
|
console.log("Usage: penny <claude|codex|opencode> [--penny-profile <name>] [native arguments...]");
|
|
1423
1474
|
console.log(" --private / --cloud select the like-named profile.");
|
|
1475
|
+
console.log(" penny claude --stats adds the live single-session token readout.");
|
|
1424
1476
|
return 0;
|
|
1425
1477
|
}
|
|
1426
1478
|
if (!["claude", "codex", "opencode"].includes(harness)) {
|
|
1427
1479
|
throw new Error(`Unknown harness "${harness}". Try penny claude, penny codex, or penny opencode.`);
|
|
1428
1480
|
}
|
|
1429
|
-
const
|
|
1481
|
+
const statsSelection = extractStatsArgs(argv.slice(1));
|
|
1482
|
+
if (statsSelection.stats && harness !== "claude") {
|
|
1483
|
+
throw new Error("--stats is currently supported only by penny claude.");
|
|
1484
|
+
}
|
|
1485
|
+
const selection = extractProfileArgs(statsSelection.args);
|
|
1430
1486
|
argv = [harness, ...selection.args];
|
|
1431
1487
|
const config = await loadPennyConfig({
|
|
1432
1488
|
profile: selection.profile,
|
|
1433
1489
|
requireExisting: Boolean(selection.profile),
|
|
1434
1490
|
});
|
|
1435
1491
|
if (harness === "claude" && argv[1]?.toLowerCase() === "desktop") {
|
|
1492
|
+
if (statsSelection.stats) {
|
|
1493
|
+
throw new Error("--stats applies to Claude Code sessions, not Claude Desktop.");
|
|
1494
|
+
}
|
|
1436
1495
|
const option = argv[2]?.toLowerCase();
|
|
1437
1496
|
if (option === "--restore" || option === "restore") {
|
|
1438
1497
|
if (argv.length > 3) {
|
|
@@ -1469,6 +1528,9 @@ export async function runPenny(argv = process.argv.slice(2)) {
|
|
|
1469
1528
|
}
|
|
1470
1529
|
return runClaudeDesktop(config, { logs, mode });
|
|
1471
1530
|
}
|
|
1531
|
+
if (statsSelection.stats) {
|
|
1532
|
+
return runPennyClaudeStats(argv.slice(1), config.profile);
|
|
1533
|
+
}
|
|
1472
1534
|
if (harness === "codex" && argv[1]?.toLowerCase() === "desktop") {
|
|
1473
1535
|
const option = argv[2]?.toLowerCase();
|
|
1474
1536
|
const mode = option === "native" || option === "--native" ? "native" : "gateway";
|
package/standalone/pane.py
CHANGED
|
@@ -51,7 +51,7 @@ NATIVE_PROXY_TOKEN_ENV = "PENNY_COMPARE_NATIVE_AUTH_TOKEN"
|
|
|
51
51
|
class Readout:
|
|
52
52
|
"""Paints the fixed block at the top of the pane."""
|
|
53
53
|
|
|
54
|
-
def __init__(self, harness, title, delta_path=None,
|
|
54
|
+
def __init__(self, harness, title, delta_path=None, label=None,
|
|
55
55
|
session_id=None, side=None, variant=None, pricing_profile="standard"):
|
|
56
56
|
self.harness = harness
|
|
57
57
|
# The pane's fixed role ("NATIVE"/"PENNY"). It keys the files the two panes use to
|
|
@@ -60,8 +60,6 @@ class Readout:
|
|
|
60
60
|
# What the readout actually shows, which the user may rename.
|
|
61
61
|
self.label = label or title
|
|
62
62
|
self.delta_path = delta_path
|
|
63
|
-
# For Codex, whether this pane's session should be the PennyRouter-routed one.
|
|
64
|
-
self.provider = provider
|
|
65
63
|
# Claude accepts a caller-supplied session id, so its transcript can be selected exactly
|
|
66
64
|
# instead of inferred from timing, model names, or Penny's global title handoff files.
|
|
67
65
|
self.session_id = session_id
|
|
@@ -142,13 +140,14 @@ class Readout:
|
|
|
142
140
|
def resolve(self):
|
|
143
141
|
"""Pin this pane to the transcript its own harness created.
|
|
144
142
|
|
|
145
|
-
|
|
146
|
-
|
|
143
|
+
Claude is launched with an explicit session id, making its transcript path deterministic.
|
|
144
|
+
Codex and OpenCode are selected from files created after this pane launched, narrowed by
|
|
145
|
+
cwd, with pane claims preventing two local panes from adopting the same transcript.
|
|
147
146
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
147
|
+
Do not filter Codex by provider here. Subscription-backed Penny sessions deliberately
|
|
148
|
+
record ``model_provider = openai``, and a hosted gateway cannot create a claim file on
|
|
149
|
+
this machine. That filter rejects the correct local JSONL for both penny-session and the
|
|
150
|
+
normal compare layout, where cwd/time/claiming already identify it.
|
|
152
151
|
"""
|
|
153
152
|
if self.path and os.path.exists(self.path):
|
|
154
153
|
return self.path
|
|
@@ -163,8 +162,7 @@ class Readout:
|
|
|
163
162
|
# session, so require one created afterwards rather than adopting the newest on disk.
|
|
164
163
|
taken = self._peers() | self._preexisting
|
|
165
164
|
candidate = newest_session(self.harness, os.getcwd(),
|
|
166
|
-
since=self.started, exclude=taken
|
|
167
|
-
provider=self.provider)
|
|
165
|
+
since=self.started, exclude=taken)
|
|
168
166
|
if candidate and not self._claim(candidate):
|
|
169
167
|
return None
|
|
170
168
|
self.path = candidate
|
|
@@ -346,6 +344,8 @@ def main():
|
|
|
346
344
|
help="start both panes on one model so the comparison is like for like")
|
|
347
345
|
parser.add_argument("--label", default=None,
|
|
348
346
|
help="name shown on this pane's readout (default: NATIVE or PENNY)")
|
|
347
|
+
parser.add_argument("--cwd", default=None,
|
|
348
|
+
help="working directory selected by the compare launcher")
|
|
349
349
|
parser.add_argument("--side", choices=("left", "right", "single"),
|
|
350
350
|
help="stable pane position used by the final report")
|
|
351
351
|
parser.add_argument("--pricing-profile", choices=("standard", "govcloud"),
|
|
@@ -353,8 +353,26 @@ def main():
|
|
|
353
353
|
parser.add_argument("--native-proxy", action="store_true",
|
|
354
354
|
help=f"route the NATIVE pane through {NATIVE_PROXY_URL_ENV} / "
|
|
355
355
|
f"{NATIVE_PROXY_TOKEN_ENV} instead of going direct")
|
|
356
|
+
parser.add_argument("--harness-args-json", default="[]",
|
|
357
|
+
help=argparse.SUPPRESS)
|
|
356
358
|
args = parser.parse_args()
|
|
357
359
|
|
|
360
|
+
try:
|
|
361
|
+
harness_args = json.loads(args.harness_args_json)
|
|
362
|
+
if not isinstance(harness_args, list) or not all(
|
|
363
|
+
isinstance(value, str) for value in harness_args):
|
|
364
|
+
raise ValueError("expected a JSON array of strings")
|
|
365
|
+
except (json.JSONDecodeError, ValueError) as error:
|
|
366
|
+
sys.stderr.write(f"penny-compare: invalid harness arguments: {error}\n")
|
|
367
|
+
return 2
|
|
368
|
+
|
|
369
|
+
if args.cwd:
|
|
370
|
+
try:
|
|
371
|
+
os.chdir(os.path.expanduser(args.cwd))
|
|
372
|
+
except OSError as error:
|
|
373
|
+
sys.stderr.write(f"penny-compare: cannot enter {args.cwd}: {error}\n")
|
|
374
|
+
return 1
|
|
375
|
+
|
|
358
376
|
harness, command, title = VARIANTS[args.variant]
|
|
359
377
|
session_id = None
|
|
360
378
|
if harness == "claude-code":
|
|
@@ -364,20 +382,14 @@ def main():
|
|
|
364
382
|
# Passed to the harness rather than set in the environment, so Penny's own model
|
|
365
383
|
# configuration is not overridden on the penny side.
|
|
366
384
|
command = command + ["--model", args.model]
|
|
385
|
+
command = command + harness_args
|
|
367
386
|
|
|
368
387
|
rows = shutil.get_terminal_size().lines
|
|
369
388
|
# Reserve the top rows for the readout and confine scrolling to what is left.
|
|
370
389
|
sys.stdout.write(f"\033[2J\033[{hud.ROWS + 1};{rows}r\033[{hud.ROWS + 1};1H")
|
|
371
390
|
sys.stdout.flush()
|
|
372
391
|
|
|
373
|
-
# Codex and OpenCode have no caller-assigned session id, so they identify their transcript by the provider
|
|
374
|
-
# recorded in the session header instead.
|
|
375
|
-
is_penny = command[0] == "penny"
|
|
376
392
|
readout = Readout(harness, title, args.delta_file,
|
|
377
|
-
# OpenCode owns provider/model selection inside its own TUI. The
|
|
378
|
-
# penny-opencode spelling is only a launch convenience, not a reliable
|
|
379
|
-
# provider identity like Codex's session header is.
|
|
380
|
-
provider=is_penny if harness == "codex" else None,
|
|
381
393
|
label=args.label,
|
|
382
394
|
session_id=session_id,
|
|
383
395
|
side=args.side,
|
package/standalone/penny-compare
CHANGED
|
@@ -236,6 +236,8 @@ workdir_left="${dir_left:-$PWD/native}"
|
|
|
236
236
|
workdir_left="${workdir_left/#\~/$HOME}"
|
|
237
237
|
workdir_right="${dir_right:-$PWD/penny}"
|
|
238
238
|
workdir_right="${workdir_right/#\~/$HOME}"
|
|
239
|
+
workdir_left_arg=" --cwd $(printf '%q' "$workdir_left")"
|
|
240
|
+
workdir_right_arg=" --cwd $(printf '%q' "$workdir_right")"
|
|
239
241
|
|
|
240
242
|
# --native-proxy only means anything on a NATIVE Claude or Codex pane;
|
|
241
243
|
# a penny-* pane already owns its own proxy routing and ignores the flag either way.
|
|
@@ -265,10 +267,10 @@ wire_args="${wire:+ $wire}${wire_both:+ $wire_both}${wire_https:+ $wire_https}"
|
|
|
265
267
|
|
|
266
268
|
tmux new-session -d -s "$session" -c "$workdir_left" -x "$(tput cols)" -y "$(tput lines)" \
|
|
267
269
|
-e PENNY_COMPARE_STATE="$state" \
|
|
268
|
-
"$here/pane.py $left --side left --pricing-profile $pricing_profile${model:+ --model $model}${native_proxy_left:+ $native_proxy_left}${label_left_arg}${wire_args}"
|
|
270
|
+
"$here/pane.py $left --side left --pricing-profile $pricing_profile${workdir_left_arg}${model:+ --model $model}${native_proxy_left:+ $native_proxy_left}${label_left_arg}${wire_args}"
|
|
269
271
|
tmux split-window -h -t "$session" -c "$workdir_right" \
|
|
270
272
|
-e PENNY_COMPARE_STATE="$state" \
|
|
271
|
-
"$here/pane.py $right --side right --pricing-profile $pricing_profile --delta-file $delta${model:+ --model $model}${native_proxy_right:+ $native_proxy_right}${label_right_arg}${wire_args}"
|
|
273
|
+
"$here/pane.py $right --side right --pricing-profile $pricing_profile --delta-file $delta${workdir_right_arg}${model:+ --model $model}${native_proxy_right:+ $native_proxy_right}${label_right_arg}${wire_args}"
|
|
272
274
|
|
|
273
275
|
# The comparison belongs to neither pane, so a small watcher derives it from both.
|
|
274
276
|
tmux new-window -d -t "$session" -n delta "$here/delta.py '$state' '$delta'"
|