maesttro 0.3.0 → 0.4.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/README.md CHANGED
@@ -155,7 +155,12 @@ thinking blocks shown. The two providers replace yours of the same name; the
155
155
  rest (`mcp`, `plugin`, other providers) is merged. Removed secrets are listed
156
156
  at launch by path. `{env:X}` / `$X` references are kept.
157
157
 
158
- Claude Code gets no Maesttro config and uses its own defaults and login.
158
+ Claude Code keeps its own login and defaults. It is pointed at envoy's
159
+ `$MAESTTRO_URL/claude-code` (`ANTHROPIC_BASE_URL`), with Maesttro added to the
160
+ model picker as a custom option (`maesttro`), with its context window read
161
+ from envoy at launch. Anthropic models pass
162
+ through to Anthropic on your own subscription. These env entries are merged
163
+ into the runner's `~/.claude/settings.json` at every start.
159
164
 
160
165
  ---
161
166
 
@@ -172,6 +177,9 @@ config, keeping your own keys. So `maesttro update` has two steps:
172
177
  Your session state (in the `maesttro-home` volume) and your project files are
173
178
  untouched by an update.
174
179
 
180
+ On start, `maesttro` checks for a newer published version and offers to update.
181
+ Declining only skips it for that run; the next start asks again.
182
+
175
183
  ---
176
184
 
177
185
  ## What's mounted, and why
package/dist/maesttro.js CHANGED
@@ -461,6 +461,29 @@ var ORCHESTRATOR_MODEL = "maesttro";
461
461
  function maesttroUrl(env = process.env) {
462
462
  return (env.MAESTTRO_URL?.trim() || DEFAULT_MAESTTRO_URL).replace(/\/+$/, "");
463
463
  }
464
+ function contextWindowOf(card) {
465
+ const { max_input_tokens: input, max_tokens: output } = card;
466
+ return typeof input === "number" && typeof output === "number" ? input + output : undefined;
467
+ }
468
+ async function fetchOrchestratorWindow({
469
+ url,
470
+ fetch: fetch2 = globalThis.fetch,
471
+ timeoutMs = 2000
472
+ }) {
473
+ try {
474
+ const response = await fetch2(`${url}/claude-code/v1/models`, {
475
+ signal: AbortSignal.timeout(timeoutMs)
476
+ });
477
+ if (!response.ok)
478
+ return;
479
+ const body = await response.json();
480
+ const cards = Array.isArray(body.data) ? body.data : [];
481
+ const card = cards.find(({ id }) => id === `claude-${ORCHESTRATOR_MODEL}`);
482
+ return card ? contextWindowOf(card) : undefined;
483
+ } catch {
484
+ return;
485
+ }
486
+ }
464
487
 
465
488
  // src/provider-configs.ts
466
489
  var PROVIDER_ID = "maesttro";
@@ -531,6 +554,20 @@ function opencodeConfig(base, url) {
531
554
  }
532
555
  };
533
556
  }
557
+ function claudeSettings({
558
+ url,
559
+ orchestratorWindow
560
+ }) {
561
+ return {
562
+ env: {
563
+ ANTHROPIC_BASE_URL: `${url}/claude-code`,
564
+ ANTHROPIC_CUSTOM_MODEL_OPTION: ORCHESTRATOR_MODEL,
565
+ ANTHROPIC_CUSTOM_MODEL_OPTION_NAME: "Maesttro",
566
+ ANTHROPIC_CUSTOM_MODEL_OPTION_DESCRIPTION: "Maesttro orchestrator",
567
+ ...orchestratorWindow ? { CLAUDE_CODE_MAX_CONTEXT_TOKENS: String(orchestratorWindow) } : {}
568
+ }
569
+ };
570
+ }
534
571
 
535
572
  // src/secrets.ts
536
573
  var SECRET_KEY = /api[_-]?key|token|secret|passw(or)?d|credential|authorization|bearer|private[_-]?key|cookie/i;
@@ -617,6 +654,8 @@ var OPENCODE_HOST_CONFIGS = [
617
654
  ".config/opencode/opencode.jsonc"
618
655
  ];
619
656
  var OPENCODE_CONFIG = ".config/opencode/opencode.json";
657
+ var CLAUDE_SETTINGS = ".claude/settings.json";
658
+ var MERGED_INTO_HOME = [CLAUDE_SETTINGS];
620
659
  var OPENCODE_TUI_STATE = ".local/state/opencode/kv.json";
621
660
  var CURATED_OPENCODE_TUI_STATE = {
622
661
  thinking_mode: "show",
@@ -652,11 +691,15 @@ function json(value) {
652
691
  }
653
692
  function buildClientConfigs({
654
693
  host,
655
- url
694
+ url,
695
+ orchestratorWindow
656
696
  }) {
657
697
  const opencode = redactSecrets(opencodeHostBase(host.opencode));
658
698
  return {
659
- files: { [OPENCODE_CONFIG]: json(opencodeConfig(opencode.value, url)) },
699
+ files: {
700
+ [OPENCODE_CONFIG]: json(opencodeConfig(opencode.value, url)),
701
+ [CLAUDE_SETTINGS]: json(claudeSettings({ url, orchestratorWindow }))
702
+ },
660
703
  removed: opencode.removed.map((path) => `opencode ${path}`)
661
704
  };
662
705
  }
@@ -679,7 +722,19 @@ function installClientConfigs({
679
722
  }) {
680
723
  if (!existsSync(source))
681
724
  return false;
682
- cpSync(source, home, { recursive: true, force: true });
725
+ const merged = MERGED_INTO_HOME.map((file) => join2(source, file));
726
+ cpSync(source, home, {
727
+ recursive: true,
728
+ force: true,
729
+ filter: (path) => !merged.includes(path)
730
+ });
731
+ for (const file of MERGED_INTO_HOME) {
732
+ const staged = join2(source, file);
733
+ if (!existsSync(staged))
734
+ continue;
735
+ const target = join2(home, file);
736
+ writeJson(target, mergeJson(readJson(target), readJson(staged)));
737
+ }
683
738
  return true;
684
739
  }
685
740
  function injectOpencodeTuiState({ home }) {
@@ -2123,9 +2178,15 @@ async function launch({
2123
2178
  if (github.notice)
2124
2179
  console.log(github.notice);
2125
2180
  const hostHome = homedir4();
2181
+ const url = maesttroUrl();
2182
+ const orchestratorWindow = await fetchOrchestratorWindow({ url });
2183
+ if (orchestratorWindow === undefined) {
2184
+ console.log("Couldn't read Maesttro's context window; Claude Code will assume its default.");
2185
+ }
2126
2186
  const clientConfigs = buildClientConfigs({
2127
2187
  host: readHostClientConfigs({ home: hostHome }),
2128
- url: maesttroUrl()
2188
+ url,
2189
+ orchestratorWindow
2129
2190
  });
2130
2191
  if (clientConfigs.removed.length > 0) {
2131
2192
  console.log(`Kept out of the runner as secrets: ${clientConfigs.removed.join(", ")}`);
@@ -2382,16 +2443,60 @@ async function setup({ args }) {
2382
2443
 
2383
2444
  // src/update.ts
2384
2445
  import { execSync as execSync5 } from "node:child_process";
2385
- import { readFileSync as readFileSync8 } from "node:fs";
2446
+
2447
+ // src/version-check.ts
2448
+ import { execFile } from "node:child_process";
2449
+ import { existsSync as existsSync6, readFileSync as readFileSync8 } from "node:fs";
2386
2450
  import { join as join12 } from "node:path";
2387
- function installedPackageName() {
2388
- const manifest = JSON.parse(readFileSync8(join12(PACKAGE_ROOT, "package.json"), "utf8"));
2389
- return manifest.name;
2451
+ var LOOKUP_TIMEOUT_MS = 3000;
2452
+ function installedManifest() {
2453
+ return JSON.parse(readFileSync8(join12(PACKAGE_ROOT, "package.json"), "utf8"));
2454
+ }
2455
+ function canOfferUpdate({
2456
+ interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY),
2457
+ inRunner = existsSync6(RUNNER_MARKER)
2458
+ } = {}) {
2459
+ return interactive && !inRunner;
2390
2460
  }
2461
+ function releaseNumbers(version) {
2462
+ const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(version.trim());
2463
+ return match ? match.slice(1).map(Number) : undefined;
2464
+ }
2465
+ function isNewer({
2466
+ candidate,
2467
+ current
2468
+ }) {
2469
+ const candidateNumbers = releaseNumbers(candidate);
2470
+ const currentNumbers = releaseNumbers(current);
2471
+ if (!candidateNumbers || !currentNumbers)
2472
+ return false;
2473
+ for (let index = 0;index < candidateNumbers.length; index++) {
2474
+ const difference = (candidateNumbers[index] ?? 0) - (currentNumbers[index] ?? 0);
2475
+ if (difference !== 0)
2476
+ return difference > 0;
2477
+ }
2478
+ return false;
2479
+ }
2480
+ function publishedVersion(name) {
2481
+ return new Promise((resolve, reject) => execFile("npm", ["view", name, "version"], { timeout: LOOKUP_TIMEOUT_MS }, (error, stdout) => error ? reject(error) : resolve(stdout.trim())));
2482
+ }
2483
+ async function findNewerVersion({
2484
+ manifest = installedManifest(),
2485
+ lookupLatest = publishedVersion
2486
+ } = {}) {
2487
+ try {
2488
+ const latest = await lookupLatest(manifest.name);
2489
+ return isNewer({ candidate: latest, current: manifest.version }) ? { name: manifest.name, current: manifest.version, latest } : undefined;
2490
+ } catch {
2491
+ return;
2492
+ }
2493
+ }
2494
+
2495
+ // src/update.ts
2391
2496
  function update() {
2392
2497
  console.log(`== maesttro update ==
2393
2498
  `);
2394
- const packageName = installedPackageName();
2499
+ const packageName = installedManifest().name;
2395
2500
  console.log(`-> Updating ${packageName} package...`);
2396
2501
  execSync5(`npm i -g ${packageName}@latest`, { stdio: "inherit" });
2397
2502
  console.log(`
@@ -2414,8 +2519,27 @@ var defaultCommands = {
2414
2519
  login,
2415
2520
  logout,
2416
2521
  status,
2417
- print: (text) => console.log(text)
2522
+ print: (text) => console.log(text),
2523
+ findNewerVersion: () => canOfferUpdate() ? findNewerVersion() : Promise.resolve(undefined),
2524
+ ask
2418
2525
  };
2526
+ var SKIPS_UPDATE_CHECK = new Set(["update", "list", "help", "--help", "-h"]);
2527
+ async function updatedInstead({
2528
+ command,
2529
+ commands
2530
+ }) {
2531
+ if (command !== undefined && SKIPS_UPDATE_CHECK.has(command))
2532
+ return false;
2533
+ const available = await commands.findNewerVersion();
2534
+ if (!available)
2535
+ return false;
2536
+ const answer = await commands.ask(`${available.name} ${available.latest} is available (you have ${available.current}). Update now? [Y/n] `);
2537
+ if (!/^(y(es)?)?$/i.test(answer))
2538
+ return false;
2539
+ await commands.update();
2540
+ commands.print("Re-run maesttro to continue.");
2541
+ return true;
2542
+ }
2419
2543
  var MODE_FLAGS = {
2420
2544
  "--strict": true,
2421
2545
  "--no-strict": false
@@ -2455,6 +2579,8 @@ async function main(argv, commands = defaultCommands) {
2455
2579
  commands.print(`--strict and --no-strict apply to launching a session, not to \`${command}\`.`);
2456
2580
  return;
2457
2581
  }
2582
+ if (await updatedInstead({ command, commands }))
2583
+ return;
2458
2584
  switch (command) {
2459
2585
  case undefined:
2460
2586
  await commands.launch({ args: [], ...mode });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "maesttro",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "Isolated Docker runner for local coding agents (opencode, claude, ...) under a dedicated bot GitHub identity, with curated configs baked in per client.",
5
5
  "type": "module",
6
6
  "bin": {