arp-tui 0.0.7 → 0.0.9

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/dist/index.js CHANGED
@@ -1822,6 +1822,9 @@ var RelayWs = class {
1822
1822
  };
1823
1823
 
1824
1824
  // src/state/store.ts
1825
+ function attentionConfirmTarget(channels, channelId) {
1826
+ return channels.find((c) => c.id === channelId)?.name ?? channelId.slice(0, 8);
1827
+ }
1825
1828
  function filterChannels(channels, filter) {
1826
1829
  const f = filter.trim().toLowerCase();
1827
1830
  if (!f) return channels;
@@ -2234,7 +2237,7 @@ function createStore(opts) {
2234
2237
  set({ mode: "attention_confirm", attentionDraft: "", error: null });
2235
2238
  }
2236
2239
  function setAttentionDraft(value) {
2237
- const max = state.mode === "attention_level" ? 32 : state.mode === "attention_confirm" ? 36 : 1024;
2240
+ const max = state.mode === "attention_level" ? 32 : state.mode === "attention_confirm" ? 256 : 1024;
2238
2241
  if (Buffer.byteLength(value, "utf8") <= max) set({ attentionDraft: value });
2239
2242
  }
2240
2243
  function submitAttentionInput() {
@@ -2260,8 +2263,9 @@ function createStore(opts) {
2260
2263
  return;
2261
2264
  }
2262
2265
  if (state.mode === "attention_confirm") {
2263
- if (state.attentionDraft !== state.activeChannelId) {
2264
- set({ error: "confirmation did not match the channel UUID" });
2266
+ if (!state.activeChannelId) return;
2267
+ if (state.attentionDraft !== attentionConfirmTarget(state.channels, state.activeChannelId)) {
2268
+ set({ error: "confirmation did not match the channel name" });
2265
2269
  return;
2266
2270
  }
2267
2271
  if (pendingAttentionAck) void performAttentionAck(pendingAttentionAck);
@@ -2909,18 +2913,20 @@ var LEVEL_COLOR = {
2909
2913
  critical: "red"
2910
2914
  };
2911
2915
  function AttentionPanel({
2912
- channelId,
2916
+ channelName,
2917
+ channelKind,
2913
2918
  items,
2914
2919
  selected,
2915
2920
  mode,
2916
2921
  draft,
2917
2922
  busy
2918
2923
  }) {
2919
- const prompt = mode === "attention_level" ? "level (info | action_required | critical)" : mode === "attention_reason" ? "reason" : mode === "attention_confirm" ? `type ${channelId} to confirm` : null;
2924
+ const prompt = mode === "attention_level" ? "level (info | action_required | critical)" : mode === "attention_reason" ? "reason" : mode === "attention_confirm" ? `type ${sanitizeForTty(channelName)} to confirm` : null;
2920
2925
  return /* @__PURE__ */ jsxs3(Box2, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
2921
2926
  /* @__PURE__ */ jsxs3(Text3, { bold: true, children: [
2922
- "ATTENTION channel:",
2923
- sanitizeForTty(channelId)
2927
+ "ATTENTION ",
2928
+ channelSigil(channelKind),
2929
+ sanitizeLabel(channelName)
2924
2930
  ] }),
2925
2931
  items.length === 0 ? /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "no active attention" }) : items.map((item, index) => /* @__PURE__ */ jsxs3(Box2, { flexDirection: "column", children: [
2926
2932
  /* @__PURE__ */ jsxs3(Text3, { children: [
@@ -3490,7 +3496,8 @@ function App({ store: store2 }) {
3490
3496
  state.mode.startsWith("attention") && state.activeChannelId ? /* @__PURE__ */ jsx12(
3491
3497
  AttentionPanel,
3492
3498
  {
3493
- channelId: state.activeChannelId,
3499
+ channelName: attentionConfirmTarget(state.channels, state.activeChannelId),
3500
+ channelKind: activeChannel?.kind,
3494
3501
  items: state.channelAttention,
3495
3502
  selected: state.attentionSelected,
3496
3503
  mode: state.mode,
@@ -3612,6 +3619,7 @@ unknown profile command "${args2.sub}".`);
3612
3619
  import { spawn as spawn2 } from "child_process";
3613
3620
  var DEFAULT_BRIDGE_CMD = "npx -y @snowyroad/braid";
3614
3621
  var ENROLL_TIMEOUT_MS = 12e4;
3622
+ var STATUS_TIMEOUT_MS = 6e4;
3615
3623
  var STDERR_TAIL_CHARS = 600;
3616
3624
  function resolveBridgeCmd(flag, env = process.env) {
3617
3625
  return flag ?? env["ARP_TUI_BRIDGE_CMD"] ?? DEFAULT_BRIDGE_CMD;
@@ -3619,14 +3627,30 @@ function resolveBridgeCmd(flag, env = process.env) {
3619
3627
  function splitCmd(cmd) {
3620
3628
  return cmd.trim().split(/\s+/).filter(Boolean);
3621
3629
  }
3622
- function enrollArgv(bridgeCmd) {
3623
- return [...splitCmd(bridgeCmd), "enroll", "--invite-stdin", "--json"];
3630
+ function enrollArgv(bridgeCmd, opts) {
3631
+ return [
3632
+ ...splitCmd(bridgeCmd),
3633
+ "enroll",
3634
+ "--invite-stdin",
3635
+ "--json",
3636
+ ...opts?.provider ? ["--provider", opts.provider] : [],
3637
+ ...opts?.model ? ["--model", opts.model] : []
3638
+ ];
3639
+ }
3640
+ function startArgv(bridgeCmd, name, provider, model) {
3641
+ return [
3642
+ ...splitCmd(bridgeCmd),
3643
+ "start",
3644
+ name,
3645
+ ...provider ? ["--provider", provider] : [],
3646
+ ...model ? ["--model", model] : []
3647
+ ];
3624
3648
  }
3625
- function startArgv(bridgeCmd, name, provider) {
3626
- return [...splitCmd(bridgeCmd), "start", name, ...provider ? ["--provider", provider] : []];
3649
+ function statusArgv(bridgeCmd) {
3650
+ return [...splitCmd(bridgeCmd), "status", "--json"];
3627
3651
  }
3628
3652
  function enrollAgent(opts) {
3629
- const [cmd, ...argv] = enrollArgv(opts.bridgeCmd);
3653
+ const [cmd, ...argv] = enrollArgv(opts.bridgeCmd, { provider: opts.provider, model: opts.model });
3630
3654
  if (!cmd) {
3631
3655
  return Promise.resolve({ ok: false, exitCode: null, message: "empty bridge command", stderrTail: "" });
3632
3656
  }
@@ -3715,8 +3739,60 @@ function parseReceipt(stdout) {
3715
3739
  }
3716
3740
  return null;
3717
3741
  }
3742
+ function fetchLocalPosture(opts) {
3743
+ const [cmd, ...argv] = statusArgv(opts.bridgeCmd);
3744
+ if (!cmd) return Promise.resolve(null);
3745
+ return new Promise((resolve) => {
3746
+ const child = spawn2(cmd, argv, { stdio: ["pipe", "pipe", "pipe"] });
3747
+ let stdout = "";
3748
+ let settled = false;
3749
+ const finish = (r) => {
3750
+ if (settled) return;
3751
+ settled = true;
3752
+ clearTimeout(timer);
3753
+ resolve(r);
3754
+ };
3755
+ const timer = setTimeout(() => {
3756
+ finish(null);
3757
+ child.kill("SIGKILL");
3758
+ }, opts.timeoutMs ?? STATUS_TIMEOUT_MS);
3759
+ child.on("error", () => finish(null));
3760
+ child.stdout.setEncoding("utf8");
3761
+ child.stdout.on("data", (d) => stdout += d);
3762
+ child.stderr.resume();
3763
+ child.stdin.on("error", () => {
3764
+ });
3765
+ child.stdin.end();
3766
+ child.on("close", (code) => {
3767
+ if (code !== 0) {
3768
+ finish(null);
3769
+ return;
3770
+ }
3771
+ const agents = parseStatusAgents(stdout);
3772
+ finish(agents?.find((a) => a.name === opts.agentName) ?? null);
3773
+ });
3774
+ });
3775
+ }
3776
+ function parseStatusAgents(stdout) {
3777
+ const lines = stdout.split("\n").map((l) => l.trim()).filter(Boolean);
3778
+ for (let i = lines.length - 1; i >= 0; i--) {
3779
+ if (!lines[i].startsWith("{")) continue;
3780
+ for (const candidate of [lines[i], lines.slice(i).join("\n")]) {
3781
+ try {
3782
+ const data = JSON.parse(candidate);
3783
+ if (data && typeof data === "object" && Array.isArray(data.agents)) {
3784
+ return data.agents.filter(
3785
+ (a) => !!a && typeof a === "object" && typeof a.name === "string"
3786
+ );
3787
+ }
3788
+ } catch {
3789
+ }
3790
+ }
3791
+ }
3792
+ return null;
3793
+ }
3718
3794
  function startAgentProcess(opts) {
3719
- const [cmd, ...argv] = startArgv(opts.bridgeCmd, opts.name, opts.provider);
3795
+ const [cmd, ...argv] = startArgv(opts.bridgeCmd, opts.name, opts.provider, opts.model);
3720
3796
  if (!cmd) return Promise.resolve(1);
3721
3797
  return new Promise((resolve) => {
3722
3798
  const child = spawn2(cmd, argv, { stdio: "inherit" });
@@ -3731,15 +3807,17 @@ var AGENT_USAGE = `usage: braid agent <add|status|reissue|revoke|remove> <name>
3731
3807
 
3732
3808
  add <name> create a personal agent on Braid, enroll it on this machine
3733
3809
  via the local bridge, and start it (Ctrl-C stops it)
3734
- status <name> detail card: presence, credential, health, service mode
3810
+ status <name> detail card: presence, credential, health, service mode,
3811
+ plus this machine's local provider/model posture (via the bridge)
3735
3812
  reissue <name> mint a fresh credential and re-enroll here \u2014 recovers an
3736
3813
  agent whose credential is broken or revoked
3737
3814
  revoke <name> revoke the agent's credential \u2014 it cannot connect until reissued
3738
3815
  remove <name> permanently delete the agent from Braid
3739
3816
 
3740
3817
  --provider <p> agent CLI the bridge runs (claude-code, codex, gemini, \u2026)
3818
+ --model <name> model pin for the provider; persists to the enrolled agent
3741
3819
  --no-start enroll only \u2014 prints the exact start command instead
3742
- --bridge-cmd <cmd> bridge command to spawn (default: npx -y arp-bridge@latest;
3820
+ --bridge-cmd <cmd> bridge command to spawn (default: ${DEFAULT_BRIDGE_CMD};
3743
3821
  also ARP_TUI_BRIDGE_CMD env)
3744
3822
  --yes skip the confirmation prompt (required with --json on
3745
3823
  revoke/remove)
@@ -3796,6 +3874,7 @@ async function runAgentCli(instance2, args2, overrides = {}) {
3796
3874
  runLogin: async () => (await login(instance2)).credentials,
3797
3875
  enroll: enrollAgent,
3798
3876
  startAgent: startAgentProcess,
3877
+ fetchPosture: fetchLocalPosture,
3799
3878
  env: process.env,
3800
3879
  out: (line) => console.log(line),
3801
3880
  err: (line) => console.error(line),
@@ -3872,7 +3951,12 @@ async function confirmDestructive(args2, deps, name, actionLine) {
3872
3951
  return true;
3873
3952
  }
3874
3953
  async function enrollAndMaybeStart(args2, deps, opts) {
3875
- const result = await deps.enroll({ bridgeCmd: opts.bridgeCmd, connectBlob: opts.connectBlob });
3954
+ const result = await deps.enroll({
3955
+ bridgeCmd: opts.bridgeCmd,
3956
+ connectBlob: opts.connectBlob,
3957
+ provider: args2.provider,
3958
+ model: args2.model
3959
+ });
3876
3960
  if (!result.ok) {
3877
3961
  const msg = sanitizeForTty(result.message);
3878
3962
  if (args2.json) deps.out(JSON.stringify({ ok: false, error: msg }));
@@ -3895,11 +3979,18 @@ async function enrollAndMaybeStart(args2, deps, opts) {
3895
3979
  }
3896
3980
  deps.err(opts.successLine(sanitizeLabel(agentName)));
3897
3981
  if (!started) {
3898
- deps.err(` start it with: ${sanitizeForTty(startArgv(opts.bridgeCmd, agentName, args2.provider).join(" "))}`);
3982
+ deps.err(
3983
+ ` start it with: ${sanitizeForTty(startArgv(opts.bridgeCmd, agentName, args2.provider, args2.model).join(" "))}`
3984
+ );
3899
3985
  return 0;
3900
3986
  }
3901
3987
  deps.err(`\u2192 starting ${sanitizeLabel(agentName)} now \u2014 press Ctrl-C to stop it`);
3902
- return deps.startAgent({ bridgeCmd: opts.bridgeCmd, name: agentName, provider: args2.provider });
3988
+ return deps.startAgent({
3989
+ bridgeCmd: opts.bridgeCmd,
3990
+ name: agentName,
3991
+ provider: args2.provider,
3992
+ model: args2.model
3993
+ });
3903
3994
  }
3904
3995
  async function runAgentAdd(instance2, args2, deps) {
3905
3996
  const name = requireName2(args2, deps);
@@ -3929,8 +4020,12 @@ async function runAgentStatus(instance2, args2, deps) {
3929
4020
  const creds = await ensureSignedIn(deps);
3930
4021
  const agent = await findMyAgent(oneShotRest(instance2, creds), name, args2, deps);
3931
4022
  if (!agent) return 1;
4023
+ const posture = await deps.fetchPosture({
4024
+ bridgeCmd: resolveBridgeCmd(args2.bridgeCmd, deps.env),
4025
+ agentName: name
4026
+ });
3932
4027
  if (args2.json) {
3933
- deps.out(JSON.stringify(agent, null, 2));
4028
+ deps.out(JSON.stringify({ ...agent, localPosture: posture }, null, 2));
3934
4029
  return 0;
3935
4030
  }
3936
4031
  const row = (label, value) => deps.out(`${(label + ":").padEnd(14)}${value}`);
@@ -3950,6 +4045,22 @@ async function runAgentStatus(instance2, args2, deps) {
3950
4045
  );
3951
4046
  if (agent.runtimeVersion) row("runtime", sanitizeLabel(agent.runtimeVersion, 40));
3952
4047
  if (agent.daemonPosture) row("daemon", sanitizeLabel(agent.daemonPosture, 40));
4048
+ const knobRow = (label, knob, emptyValue = "") => {
4049
+ const value = sanitizeLabel(knob?.value ?? "", 48) || emptyValue;
4050
+ row(label, `${value} (${sanitizeLabel(knob?.origin ?? "unknown", 16)})`);
4051
+ };
4052
+ if (posture) {
4053
+ knobRow("provider", posture.provider);
4054
+ knobRow("model", posture.model, "(provider default)");
4055
+ knobRow("tools", posture.tools);
4056
+ knobRow("auth", posture.auth);
4057
+ row(
4058
+ "sandbox",
4059
+ `${posture.sandbox?.enabled ? "on" : "off"} (${sanitizeLabel(posture.sandbox?.origin ?? "unknown", 16)})`
4060
+ );
4061
+ } else {
4062
+ row("local", "not enrolled on this machine (or bridge unavailable)");
4063
+ }
3953
4064
  return 0;
3954
4065
  }
3955
4066
  async function runAgentReissue(instance2, args2, deps) {
@@ -4765,7 +4876,7 @@ commands:
4765
4876
  profile manage saved instances (list|add|switch|remove; run \`arp-tui profile\`)
4766
4877
  agent add <name> create a personal agent on Braid, enroll it on this machine
4767
4878
  via the local bridge, and start it (--no-start to skip;
4768
- --bridge-cmd/--provider/--json \u2014 run \`arp-tui agent\` for details)
4879
+ --bridge-cmd/--provider/--model/--json \u2014 run \`arp-tui agent\` for details)
4769
4880
  agent status|reissue|revoke|remove <name>
4770
4881
  inspect or recover an agent: detail card, fresh credential +
4771
4882
  re-enroll, credential revoke, permanent delete (revoke/remove
@@ -4825,6 +4936,7 @@ function parseCliArgs(argv) {
4825
4936
  "clerk-template": { type: "string" },
4826
4937
  "clerk-oauth-client-id": { type: "string" },
4827
4938
  provider: { type: "string" },
4939
+ model: { type: "string" },
4828
4940
  "no-start": { type: "boolean" },
4829
4941
  "bridge-cmd": { type: "string" },
4830
4942
  channel: { type: "string" },
@@ -4852,6 +4964,7 @@ function parseCliArgs(argv) {
4852
4964
  clerkTemplate: parsed.values["clerk-template"],
4853
4965
  clerkOAuthClientId: parsed.values["clerk-oauth-client-id"],
4854
4966
  provider: parsed.values.provider,
4967
+ model: parsed.values.model,
4855
4968
  noStart: parsed.values["no-start"] === true,
4856
4969
  bridgeCmd: parsed.values["bridge-cmd"],
4857
4970
  channel: parsed.values.channel,
@@ -4970,6 +5083,7 @@ if (args.cmd === "login") {
4970
5083
  sub: args.sub,
4971
5084
  name: args.name,
4972
5085
  provider: args.provider,
5086
+ model: args.model,
4973
5087
  noStart: args.noStart,
4974
5088
  bridgeCmd: args.bridgeCmd,
4975
5089
  yes: args.yes,
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "arp-tui",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "arp-tui",
9
- "version": "0.0.7",
9
+ "version": "0.0.9",
10
10
  "license": "SEE LICENSE IN LICENSE.md",
11
11
  "dependencies": {
12
12
  "ink": "^5.2.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arp-tui",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "description": "Human-facing terminal client for Agent Relay Protocol. Tail channels and post messages from your terminal.",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "author": "SnowyRoad",