@inline-chat/hermes-agent-adapter 0.0.4 → 0.0.5-alpha.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inline-chat/hermes-agent-adapter",
3
- "version": "0.0.4",
3
+ "version": "0.0.5-alpha.1",
4
4
  "description": "Hermes Agent platform adapter for Inline, with a native Python plugin and bundled Inline realtime sidecar.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -49,11 +49,12 @@
49
49
  },
50
50
  "scripts": {
51
51
  "build": "mkdir -p plugin/inline/sidecar && bun build ./src/install.ts --outdir dist --entry-naming '[name].js' --target=node --format=esm --packages=bundle && bun build ./src/sidecar/index.ts --outdir plugin/inline/sidecar --entry-naming 'index.mjs' --target=node --format=esm --packages=bundle && tsc -p tsconfig.json --emitDeclarationOnly",
52
- "lint": "bunx oxlint --ignore-path ../.oxlintignore src/install.ts src/sidecar/contract.ts src/sidecar/index.ts tests/adapter-python.test.ts tests/install.test.ts tests/package-artifact.test.ts tests/sidecar-contract.test.ts tests/sidecar-runtime.test.ts vitest.config.ts",
53
- "pretest": "bun run build && rm -rf coverage",
52
+ "lint": "bunx oxlint --ignore-path ../.oxlintignore scripts/release-stage.mjs src/install.ts src/sidecar/contract.ts src/sidecar/index.ts tests/adapter-python.test.ts tests/install.test.ts tests/package-artifact.test.ts tests/sidecar-contract.test.ts tests/sidecar-runtime.test.ts vitest.config.ts",
53
+ "pretest": "bun run build",
54
54
  "prepack": "bun run build",
55
55
  "prepublishOnly": "bun run check",
56
- "release:preflight": "npm publish --dry-run --access public",
56
+ "release:preflight": "node ./scripts/release-stage.mjs --dry-run",
57
+ "release:stage": "node ./scripts/release-stage.mjs --prepare-only",
57
58
  "typecheck": "tsc -p tsconfig.json --noEmit",
58
59
  "test": "vitest run --coverage",
59
60
  "check": "bun run typecheck && bun run lint && bun run test"
@@ -63,10 +64,10 @@
63
64
  "yaml": "2.9.0"
64
65
  },
65
66
  "devDependencies": {
66
- "@types/node": "20.17.19",
67
- "@vitest/coverage-v8": "4.0.16",
67
+ "@types/node": "20.19.43",
68
+ "@vitest/coverage-v8": "4.1.10",
68
69
  "typescript": "5.8.3",
69
- "vitest": "4.0.16"
70
+ "vitest": "4.1.10"
70
71
  },
71
72
  "publishConfig": {
72
73
  "access": "public"
@@ -75,7 +76,7 @@
75
76
  "pluginId": "inline",
76
77
  "pluginPath": "plugin/inline",
77
78
  "minHermesVersion": "0.17.0",
78
- "testedHermesVersion": "0.17.0",
79
- "testedHermesCommit": "824f2279"
79
+ "testedHermesVersion": "0.18.2",
80
+ "testedHermesCommit": "9de9c25"
80
81
  }
81
82
  }
@@ -17,6 +17,7 @@ import re
17
17
  import secrets
18
18
  import shutil
19
19
  import signal
20
+ import socket
20
21
  import subprocess
21
22
  import sys
22
23
  import time
@@ -78,7 +79,6 @@ _INLINE_COMMAND_LIMIT = 100
78
79
  _INLINE_COMMAND_DESCRIPTION_LIMIT = 256
79
80
  _INLINE_COMMAND_RETRY_RATIO = 0.8
80
81
  _INLINE_COMMAND_RE = re.compile(r"^[a-z0-9_]{1,32}$")
81
- _REPLY_THREAD_AUTO_CREATE_MIN_MESSAGES = 50
82
82
  _INLINE_THREADS_COMMAND_DESCRIPTION = "Configure Inline reply-thread routing"
83
83
  _INLINE_THREADS_COMMAND_ARGS = "[status|on|off|auto|reset]"
84
84
  _INLINE_THREADS_ACTION_PREFIX = "th:"
@@ -213,6 +213,14 @@ def _normalize_sidecar_port(value: Any) -> int:
213
213
  return port
214
214
 
215
215
 
216
+ def _available_loopback_port(bind: str) -> int:
217
+ """Choose an unused loopback port for a short-lived standalone sidecar."""
218
+ family = socket.AF_INET6 if ":" in bind else socket.AF_INET
219
+ with socket.socket(family, socket.SOCK_STREAM) as sock:
220
+ sock.bind((bind, 0))
221
+ return int(sock.getsockname()[1])
222
+
223
+
216
224
  def _normalize_positive_float(value: Any, default: float, name: str) -> float:
217
225
  if value is None or str(value).strip() == "":
218
226
  return default
@@ -584,15 +592,19 @@ class InlineAdapter(BasePlatformAdapter):
584
592
  supports_code_blocks = True
585
593
  splits_long_messages = True
586
594
 
587
- def __init__(self, config: PlatformConfig):
595
+ def __init__(self, config: PlatformConfig, *, use_ephemeral_sidecar_port: bool = False):
588
596
  super().__init__(config, Platform("inline"))
589
597
  extra = config.extra or {}
590
598
 
591
599
  self._token = _config_token(config)
592
600
  self._base_url = os.getenv("INLINE_BASE_URL") or extra.get("base_url") or "https://api.inline.chat"
593
- sidecar_port = extra.get("sidecar_port") if "sidecar_port" in extra else os.getenv("INLINE_SIDECAR_PORT")
594
- self._sidecar_port = _normalize_sidecar_port(sidecar_port)
595
601
  self._sidecar_bind = _normalize_sidecar_bind(extra.get("sidecar_bind") or os.getenv("INLINE_SIDECAR_BIND"))
602
+ sidecar_port = extra.get("sidecar_port") if "sidecar_port" in extra else os.getenv("INLINE_SIDECAR_PORT")
603
+ self._sidecar_port = (
604
+ _available_loopback_port(self._sidecar_bind)
605
+ if use_ephemeral_sidecar_port
606
+ else _normalize_sidecar_port(sidecar_port)
607
+ )
596
608
  self._connect_timeout_ms = _normalize_positive_float(
597
609
  extra.get("connect_timeout_ms") if "connect_timeout_ms" in extra else os.getenv("INLINE_CONNECT_TIMEOUT_MS"),
598
610
  _DEFAULT_CONNECT_TIMEOUT_MS,
@@ -860,16 +872,6 @@ class InlineAdapter(BasePlatformAdapter):
860
872
  self._reply_thread_overrides[key] = _reply_thread_mode(value, "auto")
861
873
  self._save_reply_thread_overrides()
862
874
 
863
- @staticmethod
864
- def _positive_int(value: Any) -> Optional[int]:
865
- text = str(value or "").strip()
866
- if not re.fullmatch(r"[1-9]\d*", text):
867
- return None
868
- try:
869
- return int(text)
870
- except ValueError:
871
- return None
872
-
873
875
  @staticmethod
874
876
  def _has_reply_thread_intent(text: str) -> bool:
875
877
  normalized = re.sub(r"\s+", " ", str(text or "").strip())
@@ -884,7 +886,6 @@ class InlineAdapter(BasePlatformAdapter):
884
886
  *,
885
887
  chat_id: str,
886
888
  parent_chat_id: Optional[str],
887
- msg_id: str,
888
889
  text: str,
889
890
  ) -> bool:
890
891
  mode = self._reply_thread_mode_for_chat(chat_id, parent_chat_id)
@@ -894,8 +895,7 @@ class InlineAdapter(BasePlatformAdapter):
894
895
  return True
895
896
  if self._has_reply_thread_intent(text):
896
897
  return True
897
- message_id = self._positive_int(msg_id)
898
- return message_id is not None and message_id >= _REPLY_THREAD_AUTO_CREATE_MIN_MESSAGES
898
+ return False
899
899
 
900
900
  @staticmethod
901
901
  def _id_allowed(entries: set[str], value: str) -> bool:
@@ -978,10 +978,7 @@ class InlineAdapter(BasePlatformAdapter):
978
978
  if mode == "on":
979
979
  behavior = "Top-level replies will start or reuse Inline reply threads."
980
980
  elif mode == "auto":
981
- behavior = (
982
- "Top-level replies stay in the parent chat until "
983
- f"message #{_REPLY_THREAD_AUTO_CREATE_MIN_MESSAGES} or an explicit thread request."
984
- )
981
+ behavior = "Top-level replies stay in the parent chat unless a thread is explicitly requested."
985
982
  else:
986
983
  behavior = "Top-level replies stay in the parent chat."
987
984
  first_line = f"Inline reply threads are {mode} for this chat ({scope})."
@@ -1516,7 +1513,6 @@ class InlineAdapter(BasePlatformAdapter):
1516
1513
  and self._should_create_reply_thread_for_message(
1517
1514
  chat_id=chat_id,
1518
1515
  parent_chat_id=parent_chat_id,
1519
- msg_id=msg_id,
1520
1516
  text=text,
1521
1517
  )
1522
1518
  ):
@@ -3568,7 +3564,9 @@ async def _standalone_send(pconfig: PlatformConfig, chat_id: str, message: str,
3568
3564
  token = _config_token(pconfig)
3569
3565
  if not token:
3570
3566
  return {"error": "Inline token is required in INLINE_TOKEN, INLINE_BOT_TOKEN, or Hermes Inline config"}
3571
- adapter = InlineAdapter(pconfig)
3567
+ # The supervised gateway may already own the configured sidecar port. A
3568
+ # one-shot sender launches its own sidecar, so isolate it on a free port.
3569
+ adapter = InlineAdapter(pconfig, use_ephemeral_sidecar_port=True)
3572
3570
  ok = await adapter.connect()
3573
3571
  if not ok:
3574
3572
  return {"error": "failed to connect Inline adapter"}
@@ -7,33 +7,296 @@ the plugin has already been discovered by Hermes.
7
7
  from __future__ import annotations
8
8
 
9
9
  import argparse
10
+ import json
10
11
  import os
11
12
  import re
12
13
  import shutil
13
14
  import subprocess
15
+ import sys
14
16
 
15
17
  from pathlib import Path
16
18
 
17
19
  _SIDECAR_ENTRY = Path(__file__).parent / "sidecar" / "index.mjs"
18
20
  _MIN_NODE_MAJOR = 20
21
+ _BOT_USERNAME_RE = re.compile(r"^[A-Za-z0-9_]+bot$", re.IGNORECASE)
22
+ _CLI_INSTALL_URL = "https://inline.chat/cli/install.sh"
19
23
 
20
24
 
21
25
  def gateway_setup() -> None:
22
- if _env_token_configured():
23
- print("Inline env token configured from INLINE_TOKEN/INLINE_BOT_TOKEN.")
26
+ """Interactively create or connect an Inline bot.
27
+
28
+ Hermes invokes this from its messaging-platform wizard. Keep credential
29
+ persistence inside Hermes' own config helper so profiles and file
30
+ permissions behave exactly like the built-in Telegram setup.
31
+ """
32
+ from hermes_cli import gateway as hermes_gateway
33
+ from hermes_cli import setup as hermes_setup
34
+
35
+ hermes_setup.print_header("Inline")
36
+ existing = (
37
+ hermes_gateway.get_env_value("INLINE_TOKEN")
38
+ or hermes_gateway.get_env_value("INLINE_BOT_TOKEN")
39
+ )
40
+ if existing:
41
+ hermes_setup.print_info("Inline is already configured.")
42
+ if not hermes_setup.prompt_yes_no("Reconfigure Inline?", False):
43
+ return
44
+
45
+ hermes_setup.print_info("How would you like to connect Hermes to Inline?")
46
+ print()
47
+ hermes_setup.print_info(" [1] Create a bot in Inline and paste its token")
48
+ hermes_setup.print_info(" Go to Settings → Bots → Create a new bot.")
49
+ hermes_setup.print_info(" https://inline.chat/docs/creating-a-bot")
50
+ print()
51
+ hermes_setup.print_info(" [2] Create a bot with the Inline CLI")
52
+ hermes_setup.print_info(" Install or sign in to the CLI, then create the bot here.")
53
+ print()
54
+
55
+ choice = hermes_setup.prompt("Choice [1/2]", default="1").strip()
56
+ owner_user_id: str | None = None
57
+ token: str | None = None
58
+ if choice == "2":
59
+ token, owner_user_id = _create_bot_with_inline_cli(hermes_setup)
60
+ if not token:
61
+ print()
62
+ hermes_setup.print_info("Falling back to an existing bot token...")
63
+
64
+ if not token:
65
+ token = _prompt_existing_token(hermes_setup)
66
+ if not token:
67
+ hermes_setup.print_warning("No token saved. Inline setup was cancelled.")
68
+ return
69
+
70
+ hermes_gateway.save_env_value("INLINE_TOKEN", token)
71
+ hermes_setup.print_success("Inline bot token saved securely by Hermes.")
72
+
73
+ if not owner_user_id:
74
+ owner_user_id = _inline_cli_user_id(shutil.which("inline"))
75
+ _configure_access(hermes_gateway, hermes_setup, owner_user_id)
76
+
77
+ print()
78
+ hermes_setup.print_success("💬 Inline is configured!")
79
+ hermes_setup.print_info("Restart the gateway when prompted, then message your bot in Inline.")
80
+ hermes_setup.print_info("Send /sethome in that chat to use it for cron results and notifications.")
81
+
82
+
83
+ def _create_bot_with_inline_cli(hermes_setup) -> tuple[str | None, str | None]:
84
+ inline_bin = _find_inline_cli()
85
+ if not inline_bin:
86
+ inline_bin = _install_inline_cli(hermes_setup)
87
+ if not inline_bin:
88
+ hermes_setup.print_warning("Automatic bot creation is unavailable because the Inline CLI could not be installed.")
89
+ hermes_setup.print_info("Install it from https://inline.chat/docs/cli, or use an existing bot token.")
90
+ return None, None
91
+
92
+ owner_user_id = _inline_cli_user_id(inline_bin)
93
+ if not owner_user_id:
94
+ print()
95
+ hermes_setup.print_info("Sign in to Inline to create your Hermes bot.")
96
+ if not hermes_setup.prompt_yes_no("Sign in now?", True):
97
+ return None, None
98
+ login = subprocess.run([inline_bin, "auth", "login"], check=False)
99
+ if login.returncode != 0:
100
+ hermes_setup.print_warning("Inline sign-in did not finish successfully.")
101
+ return None, None
102
+ owner_user_id = _inline_cli_user_id(inline_bin)
103
+ if not owner_user_id:
104
+ hermes_setup.print_warning("Inline sign-in could not be verified.")
105
+ return None, None
106
+
107
+ print()
108
+ name = hermes_setup.prompt("Bot name", default="Hermes").strip()
109
+ if not name:
110
+ return None, owner_user_id
111
+
112
+ while True:
113
+ username = hermes_setup.prompt("Bot username (must end in bot)", default="hermesbot").strip().lstrip("@")
114
+ if not _BOT_USERNAME_RE.fullmatch(username):
115
+ hermes_setup.print_warning("Use letters, numbers, or underscores, and end the username with 'bot'.")
116
+ continue
117
+
118
+ payload, error = _run_inline_json(
119
+ inline_bin,
120
+ ["bots", "create", "--name", name, "--username", username],
121
+ )
122
+ token = str((payload or {}).get("token") or "").strip()
123
+ if token:
124
+ bot = (payload or {}).get("bot")
125
+ bot_name = bot.get("name") if isinstance(bot, dict) else None
126
+ hermes_setup.print_success(f"Created {bot_name or name} in Inline.")
127
+ return token, owner_user_id
128
+
129
+ hermes_setup.print_warning(error or "Inline could not create the bot.")
130
+ if not hermes_setup.prompt_yes_no("Try a different username?", True):
131
+ return None, owner_user_id
132
+
133
+
134
+ def _install_inline_cli(hermes_setup) -> str | None:
135
+ print()
136
+ hermes_setup.print_info("The Inline CLI is needed to create your bot automatically.")
137
+ if not hermes_setup.prompt_yes_no("Install the Inline CLI now?", True):
138
+ hermes_setup.print_info("Inline CLI installation skipped.")
139
+ return None
140
+
141
+ brew_bin = shutil.which("brew") if sys.platform == "darwin" else None
142
+ if brew_bin:
143
+ hermes_setup.print_info("Installing the Inline CLI with Homebrew...")
144
+ result = subprocess.run(
145
+ [brew_bin, "install", "--cask", "inline"],
146
+ check=False,
147
+ )
148
+ else:
149
+ curl_bin = shutil.which("curl")
150
+ shell_bin = shutil.which("sh") or "/bin/sh"
151
+ if not curl_bin:
152
+ hermes_setup.print_warning("Automatic installation requires curl.")
153
+ return None
154
+ hermes_setup.print_info("Downloading the official Inline CLI installer...")
155
+ try:
156
+ download = subprocess.run(
157
+ [curl_bin, "-fsSL", _CLI_INSTALL_URL],
158
+ stdout=subprocess.PIPE,
159
+ stderr=subprocess.PIPE,
160
+ timeout=60,
161
+ check=False,
162
+ )
163
+ except (OSError, subprocess.TimeoutExpired) as exc:
164
+ hermes_setup.print_warning(f"Could not download the Inline CLI installer: {exc}")
165
+ return None
166
+ if download.returncode != 0 or not download.stdout:
167
+ hermes_setup.print_warning("Could not download the Inline CLI installer.")
168
+ return None
169
+ try:
170
+ result = subprocess.run(
171
+ [shell_bin, "-s"],
172
+ input=download.stdout,
173
+ timeout=180,
174
+ check=False,
175
+ )
176
+ except (OSError, subprocess.TimeoutExpired) as exc:
177
+ hermes_setup.print_warning(f"Inline CLI installation failed: {exc}")
178
+ return None
179
+
180
+ if result.returncode != 0:
181
+ hermes_setup.print_warning("The Inline CLI installer exited unsuccessfully.")
182
+ return None
183
+
184
+ inline_bin = _find_inline_cli()
185
+ if not inline_bin:
186
+ hermes_setup.print_warning("The Inline CLI was installed but could not be found on PATH.")
187
+ return None
188
+ try:
189
+ verified = subprocess.run(
190
+ [inline_bin, "--version"],
191
+ stdout=subprocess.PIPE,
192
+ stderr=subprocess.PIPE,
193
+ timeout=10,
194
+ check=False,
195
+ )
196
+ except (OSError, subprocess.TimeoutExpired):
197
+ verified = None
198
+ if verified is None or verified.returncode != 0:
199
+ hermes_setup.print_warning("The installed Inline CLI could not be verified.")
200
+ return None
201
+
202
+ hermes_setup.print_success("Inline CLI installed successfully.")
203
+ return inline_bin
204
+
205
+
206
+ def _find_inline_cli() -> str | None:
207
+ discovered = shutil.which("inline")
208
+ if discovered:
209
+ return discovered
210
+ candidates = [
211
+ Path("/opt/homebrew/bin/inline"),
212
+ Path("/usr/local/bin/inline"),
213
+ Path.home() / ".local" / "bin" / "inline",
214
+ ]
215
+ for candidate in candidates:
216
+ if candidate.is_file() and os.access(candidate, os.X_OK):
217
+ return str(candidate)
218
+ return None
219
+
220
+
221
+ def _prompt_existing_token(hermes_setup) -> str | None:
222
+ print()
223
+ hermes_setup.print_info("Go to Inline → Settings → Bots → Create a new bot, then copy its token.")
224
+ hermes_setup.print_info("Guide: https://inline.chat/docs/creating-a-bot")
225
+ token = hermes_setup.prompt("Inline bot token", password=True).strip()
226
+ return token or None
227
+
228
+
229
+ def _configure_access(hermes_gateway, hermes_setup, owner_user_id: str | None) -> None:
230
+ print()
231
+ hermes_setup.print_info("🔒 Choose who can talk to Hermes.")
232
+ allowed: list[str] = []
233
+ if owner_user_id:
234
+ hermes_setup.print_success(f"Detected your Inline user ID: {owner_user_id}")
235
+ if hermes_setup.prompt_yes_no("Allow this Inline account?", True):
236
+ allowed.append(owner_user_id)
237
+
238
+ extra = hermes_setup.prompt("Additional allowed user IDs (comma-separated, optional)").strip()
239
+ for value in extra.replace(" ", "").split(","):
240
+ if value and value not in allowed:
241
+ allowed.append(value)
242
+
243
+ if allowed:
244
+ value = ",".join(allowed)
245
+ hermes_gateway.save_env_value("INLINE_ALLOW_ALL_USERS", "false")
246
+ hermes_gateway.save_env_value("INLINE_ALLOWED_USERS", value)
247
+ hermes_gateway.save_env_value("INLINE_GROUP_ALLOW_FROM", value)
248
+ hermes_gateway.save_env_value("INLINE_DM_POLICY", "allowlist")
249
+ hermes_gateway.save_env_value("INLINE_GROUP_POLICY", "allowlist")
250
+ hermes_setup.print_success("Only the listed Inline users can invoke Hermes.")
251
+ return
252
+
253
+ if hermes_setup.prompt_yes_no("Allow any Inline user who can reach the bot?", False):
254
+ hermes_gateway.save_env_value("INLINE_ALLOW_ALL_USERS", "true")
255
+ hermes_gateway.save_env_value("INLINE_DM_POLICY", "open")
256
+ hermes_gateway.save_env_value("INLINE_GROUP_POLICY", "open")
257
+ hermes_setup.print_warning("Open access enabled. Any reachable Inline user can invoke Hermes.")
24
258
  else:
25
- print("Set INLINE_TOKEN or INLINE_BOT_TOKEN in the Hermes gateway environment.")
26
- print("Alternatively set platforms.inline.token or inline.token in ~/.hermes/config.yaml.")
27
- print("Enable Inline in ~/.hermes/config.yaml:")
28
- print(" platforms:")
29
- print(" inline:")
30
- print(" enabled: true")
31
- print("Then run: inline-hermes doctor --json")
259
+ hermes_gateway.save_env_value("INLINE_ALLOW_ALL_USERS", "false")
260
+ hermes_gateway.save_env_value("INLINE_DM_POLICY", "disabled")
261
+ hermes_gateway.save_env_value("INLINE_GROUP_POLICY", "disabled")
262
+ hermes_setup.print_warning("Messaging is disabled until you add allowed user IDs and re-run setup.")
263
+
264
+
265
+ def _inline_cli_user_id(inline_bin: str | None) -> str | None:
266
+ if not inline_bin:
267
+ return None
268
+ payload, _ = _run_inline_json(inline_bin, ["auth", "me"])
269
+ if not payload:
270
+ return None
271
+ raw = payload.get("id")
272
+ return str(raw).strip() if raw is not None and str(raw).strip() else None
273
+
274
+
275
+ def _run_inline_json(inline_bin: str, args: list[str]) -> tuple[dict | None, str | None]:
276
+ try:
277
+ result = subprocess.run(
278
+ [inline_bin, "--json", "--compact", *args],
279
+ stdout=subprocess.PIPE,
280
+ stderr=subprocess.PIPE,
281
+ text=True,
282
+ timeout=60,
283
+ check=False,
284
+ )
285
+ except (OSError, subprocess.TimeoutExpired) as exc:
286
+ return None, f"Inline CLI failed: {exc}"
287
+ if result.returncode != 0:
288
+ detail = (result.stderr or "").strip().splitlines()
289
+ return None, detail[-1] if detail else "Inline CLI exited unsuccessfully."
290
+ try:
291
+ payload = json.loads(result.stdout)
292
+ except (json.JSONDecodeError, TypeError):
293
+ return None, "Inline CLI returned an unreadable response."
294
+ return payload if isinstance(payload, dict) else None, None
32
295
 
33
296
 
34
297
  def register_cli(parser: argparse.ArgumentParser) -> None:
35
298
  subs = parser.add_subparsers(dest="inline_command", required=False)
36
- subs.add_parser("setup", help="Show Inline setup instructions")
299
+ subs.add_parser("setup", help="Configure Inline interactively")
37
300
  subs.add_parser("status", help="Show Inline adapter status")
38
301
  parser.set_defaults(func=dispatch)
39
302
 
@@ -47,13 +310,12 @@ def dispatch(args) -> int:
47
310
  return 0
48
311
  if command == "status":
49
312
  configured = _env_token_configured()
50
- print(f"Inline env token configured: {'yes' if configured else 'no'}")
51
- print("Hermes config token support: platforms.inline.token or inline.token")
313
+ print(f"Inline configured: {'yes' if configured else 'no'}")
52
314
  print(f"Inline sidecar bundled: {'yes' if _SIDECAR_ENTRY.exists() else 'no'}")
53
315
  print(f"Node available: {_node_status()}")
54
316
  if not configured:
55
- print("Hint: use INLINE_TOKEN/INLINE_BOT_TOKEN for env setup, or platforms.inline.token/inline.token in config.yaml.")
56
- print("Install diagnostics: inline-hermes doctor --json")
317
+ print("Next: run `hermes inline setup` for guided bot setup.")
318
+ print("Advanced diagnostics: inline-hermes doctor --json")
57
319
  return 0
58
320
  raise SystemExit(f"unknown inline command: {command}")
59
321
 
@@ -1,7 +1,7 @@
1
1
  name: inline-platform
2
2
  label: Inline
3
3
  kind: platform
4
- version: 0.0.4
4
+ version: 0.0.5-alpha.1
5
5
  description: >
6
6
  Inline platform adapter for Hermes Agent. The adapter runs as a native
7
7
  Hermes Python platform plugin and supervises a local Node sidecar that uses
@@ -79,7 +79,7 @@ optional_env:
79
79
  prompt: "Free-response Inline chat ids"
80
80
  password: false
81
81
  - name: INLINE_REPLY_THREADS
82
- description: "Reply-thread mode: auto, on, or off. Auto creates child reply threads only for large parent chats or explicit thread requests (default auto)."
82
+ description: "Reply-thread mode: auto, on, or off. Auto creates child reply threads only for explicit thread requests (default auto)."
83
83
  prompt: "Reply-thread mode (auto/on/off)"
84
84
  password: false
85
85
  - name: INLINE_CONTEXT_BACKFILL