agentful 0.4.2 → 0.4.4

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/index.cjs +115 -16
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -3044,7 +3044,7 @@ var VERSION, brand, useColor, wrap, paint, tuiDesign, sym, ui;
3044
3044
  var init_branding = __esm({
3045
3045
  "src/branding.ts"() {
3046
3046
  "use strict";
3047
- VERSION = "0.4.2" ? "0.4.2" : null.version;
3047
+ VERSION = "0.4.4" ? "0.4.4" : null.version;
3048
3048
  brand = {
3049
3049
  name: "agentful",
3050
3050
  // the command users type
@@ -3664,7 +3664,17 @@ const plugin = {
3664
3664
  }
3665
3665
 
3666
3666
  // \u2500\u2500 prompt \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
3667
- /** Left half of the line under the input; replaces the engine's plain cwd. */
3667
+ /**
3668
+ * Left half of the line under the input; replaces the engine's plain cwd.
3669
+ * Without a sessionID (the home screen) it falls back to the process
3670
+ * directory.
3671
+ *
3672
+ * The home screen needs it for a second reason: that line is
3673
+ * space-between, so with nothing on the left the engine's key hints
3674
+ * ("tab agents \xB7 ctrl+p commands") sit at column 0 \u2014 the one column the
3675
+ * clip below takes, which cost them their first character up to 0.4.2.
3676
+ * A hint on the left pushes them back to the right edge.
3677
+ */
3668
3678
  function Hint(props) {
3669
3679
  const directory = () => safe(
3670
3680
  () => api.state.session.get(props.sessionID).directory,
@@ -3689,16 +3699,34 @@ const plugin = {
3689
3699
  * left, which puts the rail outside the visible area. Everything else
3690
3700
  * keeps working because it is only a shift.
3691
3701
  *
3692
- * \u26A0\uFE0F One column is the limit. The status line under the input (our hint
3693
- * on the left) sits OUTSIDE that padded box and carries a margin of just
3694
- * 1, so a wider clip eats the start of the working directory \u2014
3695
- * "/Users/\u2026" came out as "sers/\u2026" at -3. The rendering check asserts the
3696
- * working directory for exactly this reason.
3702
+ * \u26A0\uFE0F The clip must NOT cover the whole component. api.ui.Prompt returns
3703
+ * TWO siblings: its own box, and the "/" + "@" completion list, which is
3704
+ * positioned absolutely ABOVE the input (a negative "top" against its
3705
+ * parent). A clip around both swallowed that list \u2014 the popup rendered
3706
+ * outside the clipped area, so it was never drawn and both triggers
3707
+ * looked dead while the keystrokes still went through (shipped in 0.4.2,
3708
+ * fixed here). So we take the children apart: the first goes inside the
3709
+ * clip, everything after it stays outside. The popup then anchors on the
3710
+ * outer box, which sits one column right of the shifted input \u2014 exactly
3711
+ * where its left edge belongs.
3712
+ *
3713
+ * \u26A0\uFE0F One column is the limit for the shift. The status line under the
3714
+ * input (our hint on the left) sits OUTSIDE that padded box and carries a
3715
+ * margin of just 1, so a wider clip eats the start of the working
3716
+ * directory \u2014 "/Users/\u2026" came out as "sers/\u2026" at -3. The rendering check
3717
+ * asserts the working directory, and the popup, for exactly this reason.
3697
3718
  */
3698
3719
  function Field(props) {
3720
+ // Read once: in the engine's Solid runtime this getter runs the child
3721
+ // component, and each node may only be inserted into one parent.
3722
+ const parts = props.children
3723
+ const list = Array.isArray(parts) ? parts : [parts]
3699
3724
  return (
3700
- <box width="100%" overflow="hidden">
3701
- <box marginLeft={-1} width="100%">{props.children}</box>
3725
+ <box width="100%">
3726
+ <box width="100%" overflow="hidden">
3727
+ <box marginLeft={-1} width="100%">{list[0]}</box>
3728
+ </box>
3729
+ {list.slice(1)}
3702
3730
  </box>
3703
3731
  )
3704
3732
  }
@@ -3728,6 +3756,7 @@ const plugin = {
3728
3756
  <Prompt
3729
3757
  ref={data.ref}
3730
3758
  placeholders={PLACEHOLDERS}
3759
+ hint={<Hint />}
3731
3760
  right={<HostSlot name="home_prompt_right" />}
3732
3761
  />
3733
3762
  </Field>
@@ -7387,6 +7416,21 @@ function skillsInstructionFor(framework, rootDir) {
7387
7416
  }
7388
7417
 
7389
7418
  // src/lib/models.ts
7419
+ var OAUTH_BYOK_PROVIDER = "github-copilot";
7420
+ async function fetchCopilotAccountModels(auth) {
7421
+ try {
7422
+ const res = await request(
7423
+ `${PB_URL}/api/llm/copilot/models`,
7424
+ { token: auth.pb_token, timeoutMs: 15e3 }
7425
+ );
7426
+ return (res.models || []).map((m) => m?.id).filter((id) => !!id);
7427
+ } catch {
7428
+ return null;
7429
+ }
7430
+ }
7431
+ function supportsHostedModelInLocalEngine(id) {
7432
+ return !/^gemini-3(?:[.-]|$)/i.test(id.trim());
7433
+ }
7390
7434
  async function fetchModelCatalog(auth, fallback) {
7391
7435
  const regionDefaultsToBedrock = fallback.ai_region === "eu" || fallback.ai_region === "de";
7392
7436
  const isAgenticCapable = (id) => id !== "auto" && !id.includes("bedrock");
@@ -7400,8 +7444,8 @@ async function fetchModelCatalog(auth, fallback) {
7400
7444
  }
7401
7445
  if (!catalog?.models?.length) {
7402
7446
  return {
7403
- models: fallback.models.map((id) => ({ id, name: id })),
7404
- defaultModel: fallback.default_model,
7447
+ models: fallback.models.filter(supportsHostedModelInLocalEngine).map((id) => ({ id, name: id })),
7448
+ defaultModel: supportsHostedModelInLocalEngine(fallback.default_model) ? fallback.default_model : fallback.models.find(supportsHostedModelInLocalEngine) || "auto",
7405
7449
  byokProviders: [],
7406
7450
  canSelectModel: true,
7407
7451
  inactiveByok: []
@@ -7411,6 +7455,7 @@ async function fetchModelCatalog(auth, fallback) {
7411
7455
  let hasGenericByok = false;
7412
7456
  for (const m of catalog.models) {
7413
7457
  if (!m?.id) continue;
7458
+ if (!supportsHostedModelInLocalEngine(m.id)) continue;
7414
7459
  if (m.id === "byok") {
7415
7460
  if (m.has_active_keys) hasGenericByok = true;
7416
7461
  continue;
@@ -7419,6 +7464,7 @@ async function fetchModelCatalog(auth, fallback) {
7419
7464
  }
7420
7465
  const byokProviders = [];
7421
7466
  const inactiveByok = [];
7467
+ let byokNotice;
7422
7468
  if (catalog.byok_enabled && catalog.has_byok_keys) {
7423
7469
  try {
7424
7470
  const res = await request(`${PB_URL}/api/llm/byok`, {
@@ -7431,8 +7477,23 @@ async function fetchModelCatalog(auth, fallback) {
7431
7477
  inactiveByok.push({ provider: key.provider_type, models: key.models || [] });
7432
7478
  continue;
7433
7479
  }
7434
- byokProviders.push(key.provider_type);
7435
- for (const model of key.models || []) {
7480
+ const selection = key.models || [];
7481
+ let models = selection;
7482
+ if (key.provider_type === OAUTH_BYOK_PROVIDER) {
7483
+ const live = await fetchCopilotAccountModels(auth);
7484
+ if (live === null) {
7485
+ byokNotice = selection.length ? `${key.provider_type}: account model list unavailable \u2014 showing your stored selection.` : `${key.provider_type}: account model list unavailable \u2014 no own-key model in this session.`;
7486
+ } else if (!selection.length) {
7487
+ models = live;
7488
+ } else {
7489
+ models = selection.filter((m) => live.includes(m));
7490
+ if (!models.length) {
7491
+ byokNotice = `${key.provider_type}: none of your selected models is served by the account right now \u2014 re-select them in Settings \u2192 BYOK.`;
7492
+ }
7493
+ }
7494
+ }
7495
+ if (models.length) byokProviders.push(key.provider_type);
7496
+ for (const model of models) {
7436
7497
  entries.push({
7437
7498
  id: `byok/${key.provider_type}/${model}`,
7438
7499
  name: `${model} \xB7 your key (${key.provider_type})`,
@@ -7457,7 +7518,8 @@ async function fetchModelCatalog(auth, fallback) {
7457
7518
  defaultModel,
7458
7519
  byokProviders,
7459
7520
  canSelectModel: catalog.can_select_model !== false,
7460
- inactiveByok
7521
+ inactiveByok,
7522
+ byokNotice
7461
7523
  };
7462
7524
  }
7463
7525
 
@@ -7776,6 +7838,20 @@ async function listEngineModels(binPath, xdg, provider) {
7776
7838
  });
7777
7839
  return parseEngineModelsOutput(stdout);
7778
7840
  }
7841
+ async function refreshEngineModelCache(binPath, xdg) {
7842
+ await execFileAsync(binPath, ["models", "--refresh"], {
7843
+ env: {
7844
+ ...process.env,
7845
+ XDG_CONFIG_HOME: xdg.configHome,
7846
+ XDG_DATA_HOME: xdg.dataHome,
7847
+ XDG_STATE_HOME: xdg.stateHome,
7848
+ OPENCODE_DISABLE_AUTOUPDATE: "true"
7849
+ },
7850
+ encoding: "utf8",
7851
+ maxBuffer: 8 * 1024 * 1024,
7852
+ timeout: 6e4
7853
+ });
7854
+ }
7779
7855
  async function assertEngineModelAvailable(binPath, xdg, model, listModels = listEngineModels) {
7780
7856
  const provider = modelProvider(model);
7781
7857
  const models = await listModels(binPath, xdg, provider);
@@ -8170,6 +8246,9 @@ async function tuiCommand(opts = {}) {
8170
8246
  "Resume model metadata is not readable locally; the engine keeps the session model unchanged."
8171
8247
  ));
8172
8248
  }
8249
+ if (catalog.byokNotice) {
8250
+ ui.warn(catalog.byokNotice);
8251
+ }
8173
8252
  if (catalog.byokProviders.length) {
8174
8253
  ui.info(paint.dim(
8175
8254
  `Includes your own keys (${catalog.byokProviders.join(", ")}): billed by that provider` + (session.ai_region && session.ai_region !== "all" ? `, outside your "${session.ai_region}" region setting.` : ".")
@@ -8446,6 +8525,10 @@ async function licensesCommand() {
8446
8525
 
8447
8526
  // src/commands/models.ts
8448
8527
  init_branding();
8528
+ function describeInactiveModels(key) {
8529
+ if (key.models.length) return key.models.join(", ");
8530
+ return key.provider === OAUTH_BYOK_PROVIDER ? "all account models" : "(no model)";
8531
+ }
8449
8532
  async function modelsCommand(opts) {
8450
8533
  console.log(banner());
8451
8534
  const auth = await ensureAuth();
@@ -8463,18 +8546,30 @@ async function modelsCommand(opts) {
8463
8546
  const decision = decideLocalProviders(policy);
8464
8547
  const resolved = resolveLocalProviders(policy);
8465
8548
  const configured = Object.keys(readLocalProviders());
8549
+ if (catalog.byokNotice) {
8550
+ console.log("");
8551
+ ui.warn(catalog.byokNotice);
8552
+ }
8466
8553
  if (catalog.inactiveByok.length) {
8467
8554
  console.log("");
8468
8555
  console.log(paint.dim(" Configured but inactive (one own key can be active at a time \u2014"));
8469
8556
  console.log(paint.dim(" switch it in Settings \u2192 BYOK to use another):"));
8470
8557
  for (const k of catalog.inactiveByok) {
8471
- console.log(paint.dim(` \u25CB ${k.models.join(", ") || "(no model)"} \xB7 ${k.provider}`));
8558
+ console.log(paint.dim(` \u25CB ${describeInactiveModels(k)} \xB7 ${k.provider}`));
8472
8559
  }
8473
8560
  }
8474
8561
  const xdg = engineXdg();
8475
8562
  const connectedProviders = readConnectedEngineProviders(xdg);
8476
8563
  if (connectedProviders.length) {
8477
8564
  const binPath = await ensureEngine();
8565
+ if (opts.refresh) {
8566
+ try {
8567
+ await refreshEngineModelCache(binPath, xdg);
8568
+ ui.step("Refreshed the engine model cache from models.dev.");
8569
+ } catch {
8570
+ ui.warn("Engine model cache could not be refreshed \u2014 listing the cached catalog.");
8571
+ }
8572
+ }
8478
8573
  console.log("");
8479
8574
  console.log(paint.bold("Direct TUI connections") + paint.dim(" (provider billing/terms; outside Agentful credits and region controls)"));
8480
8575
  for (const provider of connectedProviders) {
@@ -8489,6 +8584,10 @@ async function modelsCommand(opts) {
8489
8584
  console.log(` ${paint.warn("!")} ${provider} ${paint.dim("\u2014 catalog could not be read")}`);
8490
8585
  }
8491
8586
  }
8587
+ } else if (opts.refresh) {
8588
+ console.log("");
8589
+ ui.info("No provider is connected directly in the TUI, so nothing is cached \u2014");
8590
+ ui.info("the Agentful catalog and your own keys are read live on every run.");
8492
8591
  }
8493
8592
  console.log("");
8494
8593
  console.log(paint.bold("Local providers") + paint.dim(" (your key, direct to the provider)"));
@@ -8606,7 +8705,7 @@ program2.command("tui").description("Start the Agentful coding TUI with your Age
8606
8705
  program2.command("publish <subdomain>").description("Publish the built project at https://<subdomain>.agentful.dev").action(run(publishCommand));
8607
8706
  program2.command("share").description("Make the live preview public (anyone with the link)").option("--private", "make the preview owner-only again").action(run((opts) => shareCommand(opts)));
8608
8707
  program2.command("pull").description("Download the cloud workspace into this directory").option("--force", "overwrite files in a non-empty directory").action(run((opts) => pullCommand(opts)));
8609
- program2.command("models").description("List available models and manage local providers with your own key").option("--add-local", "create/show the local provider file to edit").action(run((opts) => modelsCommand(opts)));
8708
+ program2.command("models").description("List available models and manage local providers with your own key").option("--add-local", "create/show the local provider file to edit").option("--refresh", "refresh the engine model cache from models.dev").action(run((opts) => modelsCommand(opts)));
8610
8709
  program2.command("upgrade").description("Update the CLI to the latest version").action(run(upgradeCommand));
8611
8710
  program2.command("licenses").description("Show licenses of the software distributed with this CLI").action(run(licensesCommand));
8612
8711
  program2.parseAsync(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentful",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "description": "Agentful in your terminal — local development with push-to-cloud previews",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://agentful.dev",