agent-devkit 0.3.1 → 0.3.2

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
@@ -30,7 +30,7 @@ agent doctor
30
30
  Expected version for this release:
31
31
 
32
32
  ```text
33
- agent 0.3.1
33
+ agent 0.3.2
34
34
  ```
35
35
 
36
36
  ## Quick Start
@@ -45,10 +45,13 @@ agent llm list
45
45
  agent commands list
46
46
  ```
47
47
 
48
- Agent DevKit `v0.3.1` also includes the embedded Qwen2.5-0.5B mini-brain
48
+ Agent DevKit `v0.3.2` includes the embedded Qwen2.5-0.5B mini-brain
49
49
  contract for local bootstrap conversations without Ollama, Claude, Codex or API
50
50
  keys. The npm package stays small; `agent setup mini-brain --yes` downloads the
51
51
  GGUF into `.agent-devkit/models` after explicit opt-in.
52
+ When you rename the local agent, Agent DevKit now creates a matching command
53
+ alias under `.agent-devkit/bin` and can add that directory to PATH with
54
+ `agent alias path --yes`.
52
55
  The `v0.3.0` deterministic runtime discovery and integration commands remain
53
56
  available:
54
57
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-devkit",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Agent DevKit CLI runtime for specialist AI agents, capabilities and provider-aware automations.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,3 +1,3 @@
1
1
  """Public CLI implementation for AI DevKit."""
2
2
 
3
- __version__ = "0.3.1"
3
+ __version__ = "0.3.2"
@@ -3,9 +3,11 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import json
6
+ import os
6
7
  import re
7
8
  import shutil
8
9
  import sys
10
+ import unicodedata
9
11
  from pathlib import Path
10
12
  from typing import Any
11
13
 
@@ -14,6 +16,8 @@ from cli.aikit.app_home import app_home, ensure_app_home
14
16
 
15
17
  ALIAS_PATTERN = re.compile(r"^[A-Za-z][A-Za-z0-9_-]{1,63}$")
16
18
  RESERVED_ALIASES = {"agent", "aikit", "ai-devkit", "python", "python3"}
19
+ SHELL_BLOCK_BEGIN = "# >>> agent-devkit aliases >>>"
20
+ SHELL_BLOCK_END = "# <<< agent-devkit aliases <<<"
17
21
 
18
22
 
19
23
  def aliases_config_path() -> Path:
@@ -84,6 +88,38 @@ def add_alias(name: str, *, force: bool = False) -> dict[str, Any]:
84
88
  return payload
85
89
 
86
90
 
91
+ def ensure_alias_for_agent_name(agent_name: str, *, force: bool = False) -> dict[str, Any]:
92
+ suggested = suggest_alias_name(agent_name)
93
+ payload: dict[str, Any] = {
94
+ "kind": "alias",
95
+ "requested_name": agent_name,
96
+ "suggested_name": suggested,
97
+ "created": False,
98
+ }
99
+ if not suggested:
100
+ return {
101
+ **payload,
102
+ "status": "invalid",
103
+ "message": "Agent name cannot be converted to a safe command alias.",
104
+ "path_status": alias_path_status(),
105
+ }
106
+ try:
107
+ added = add_alias(suggested, force=force)
108
+ except ValueError as exc:
109
+ return {
110
+ **payload,
111
+ "name": suggested,
112
+ "status": "blocked",
113
+ "message": str(exc),
114
+ "path_status": alias_path_status(suggested),
115
+ }
116
+ added["created"] = True
117
+ added["requested_name"] = agent_name
118
+ added["suggested_name"] = suggested
119
+ added["path_status"] = alias_path_status(suggested)
120
+ return added
121
+
122
+
87
123
  def remove_alias(name: str) -> dict[str, Any]:
88
124
  alias = validate_alias_name(name)
89
125
  config = load_aliases()
@@ -138,6 +174,7 @@ def alias_payload(alias: str, item: dict[str, Any]) -> dict[str, Any]:
138
174
  "cmd_path": str(aliases_bin_dir() / f"{alias}.cmd"),
139
175
  "ps1_path": str(aliases_bin_dir() / f"{alias}.ps1"),
140
176
  "created_by": item.get("created_by"),
177
+ "path_status": alias_path_status(alias),
141
178
  }
142
179
 
143
180
 
@@ -194,3 +231,147 @@ def alias_executable_path(alias: str) -> Path:
194
231
 
195
232
  def alias_paths(alias: str) -> list[Path]:
196
233
  return [alias_executable_path(alias), aliases_bin_dir() / f"{alias}.cmd", aliases_bin_dir() / f"{alias}.ps1"]
234
+
235
+
236
+ def suggest_alias_name(agent_name: str) -> str | None:
237
+ cleaned = " ".join(str(agent_name or "").split()).strip()
238
+ if not cleaned:
239
+ return None
240
+ try:
241
+ return validate_alias_name(cleaned)
242
+ except ValueError:
243
+ pass
244
+ ascii_name = unicodedata.normalize("NFKD", cleaned).encode("ascii", "ignore").decode("ascii")
245
+ alias = re.sub(r"[^A-Za-z0-9_-]+", "-", ascii_name).strip("-_")
246
+ if alias and not alias[0].isalpha():
247
+ alias = f"agent-{alias}"
248
+ if len(alias) == 1:
249
+ alias = f"{alias}-agent"
250
+ alias = alias[:64].strip("-_")
251
+ if not alias:
252
+ return None
253
+ try:
254
+ return validate_alias_name(alias)
255
+ except ValueError:
256
+ return None
257
+
258
+
259
+ def alias_path_status(alias: str | None = None) -> dict[str, Any]:
260
+ bin_dir = aliases_bin_dir()
261
+ path_entries = os.environ.get("PATH", "").split(os.pathsep) if os.environ.get("PATH") else []
262
+ bin_dir_in_path = any(paths_equal(Path(entry), bin_dir) for entry in path_entries if entry)
263
+ found = shutil.which(alias) if alias else None
264
+ available = False
265
+ if alias and found:
266
+ try:
267
+ available = Path(found).resolve() in {path.resolve() for path in alias_paths(alias)}
268
+ except OSError:
269
+ available = False
270
+ plan = alias_path_setup_plan()
271
+ return {
272
+ "bin_dir": str(bin_dir),
273
+ "bin_dir_in_path": bin_dir_in_path,
274
+ "alias_available": available,
275
+ "found": found,
276
+ "setup_required": not bin_dir_in_path,
277
+ "setup": plan,
278
+ }
279
+
280
+
281
+ def paths_equal(left: Path, right: Path) -> bool:
282
+ try:
283
+ return left.expanduser().resolve() == right.expanduser().resolve()
284
+ except OSError:
285
+ return left.expanduser() == right.expanduser()
286
+
287
+
288
+ def alias_path_setup_plan() -> dict[str, Any]:
289
+ bin_dir = aliases_bin_dir()
290
+ if os.name == "nt":
291
+ return {
292
+ "platform": "windows",
293
+ "target": "user PATH",
294
+ "command": f'agent alias path --yes',
295
+ "description": f"Add {bin_dir} to the current user's PATH.",
296
+ }
297
+ profile = detect_shell_profile()
298
+ return {
299
+ "platform": "posix",
300
+ "target": str(profile),
301
+ "command": "agent alias path --yes",
302
+ "description": f"Add {bin_dir} to PATH for future shell sessions.",
303
+ }
304
+
305
+
306
+ def setup_alias_path(*, yes: bool = False) -> dict[str, Any]:
307
+ status = alias_path_status()
308
+ payload: dict[str, Any] = {
309
+ "kind": "alias-path",
310
+ "bin_dir": status["bin_dir"],
311
+ "path_status": status,
312
+ "executed": False,
313
+ }
314
+ if not status["setup_required"]:
315
+ return {**payload, "status": "ok", "message": "Agent DevKit alias bin directory is already on PATH."}
316
+ if not yes:
317
+ return {
318
+ **payload,
319
+ "status": "needs-confirmation",
320
+ "message": "Run `agent alias path --yes` to make configured aliases available as shell commands.",
321
+ }
322
+ if os.name == "nt":
323
+ return install_windows_user_path(payload)
324
+ return install_posix_shell_path(payload)
325
+
326
+
327
+ def install_posix_shell_path(payload: dict[str, Any]) -> dict[str, Any]:
328
+ profile = detect_shell_profile()
329
+ bin_dir = aliases_bin_dir()
330
+ profile.parent.mkdir(parents=True, exist_ok=True)
331
+ current = profile.read_text(encoding="utf-8") if profile.exists() else ""
332
+ export_line = f'export PATH="{bin_dir}:$PATH"'
333
+ block = f"\n{SHELL_BLOCK_BEGIN}\n{export_line}\n{SHELL_BLOCK_END}\n"
334
+ if str(bin_dir) not in current:
335
+ profile.write_text(current.rstrip() + block, encoding="utf-8")
336
+ return {
337
+ **payload,
338
+ "status": "updated",
339
+ "executed": True,
340
+ "profile": str(profile),
341
+ "message": "Alias PATH configured for future shell sessions. Restart the terminal or source the profile.",
342
+ }
343
+
344
+
345
+ def install_windows_user_path(payload: dict[str, Any]) -> dict[str, Any]:
346
+ bin_dir = str(aliases_bin_dir())
347
+ try:
348
+ import winreg
349
+
350
+ with winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Environment", 0, winreg.KEY_READ | winreg.KEY_WRITE) as key:
351
+ try:
352
+ current, value_type = winreg.QueryValueEx(key, "Path")
353
+ except FileNotFoundError:
354
+ current, value_type = "", winreg.REG_EXPAND_SZ
355
+ entries = [entry for entry in str(current).split(os.pathsep) if entry]
356
+ if not any(paths_equal(Path(entry), aliases_bin_dir()) for entry in entries):
357
+ new_value = os.pathsep.join([bin_dir, *entries])
358
+ winreg.SetValueEx(key, "Path", 0, value_type, new_value)
359
+ except OSError as exc:
360
+ return {**payload, "status": "failed", "executed": False, "message": str(exc)}
361
+ return {
362
+ **payload,
363
+ "status": "updated",
364
+ "executed": True,
365
+ "profile": "HKCU\\Environment\\Path",
366
+ "message": "Alias PATH configured for future Windows terminal sessions.",
367
+ }
368
+
369
+
370
+ def detect_shell_profile() -> Path:
371
+ shell = Path(os.environ.get("SHELL", "")).name
372
+ home = Path.home()
373
+ if shell == "zsh":
374
+ return home / ".zshrc"
375
+ if shell == "bash":
376
+ return home / ".bashrc"
377
+ return home / ".profile"
@@ -7,7 +7,7 @@ from pathlib import Path
7
7
  from typing import Any
8
8
 
9
9
  from cli.aikit import __version__
10
- from cli.aikit.aliases import add_alias, list_aliases, remove_alias, sync_aliases
10
+ from cli.aikit.aliases import add_alias, list_aliases, remove_alias, setup_alias_path, sync_aliases
11
11
  from cli.aikit.agentic_commands import agentic_execute, agentic_plan
12
12
  from cli.aikit.app_home import app_home_status, migrate_default_home
13
13
  from cli.aikit.architecture import architecture_contract
@@ -1530,6 +1530,10 @@ def dispatch_alias(args: argparse.Namespace) -> dict[str, Any]:
1530
1530
  if args.name:
1531
1531
  raise DevKitError("alias sync does not accept a name")
1532
1532
  return sync_aliases()
1533
+ if args.action == "path":
1534
+ if args.name:
1535
+ raise DevKitError("alias path does not accept a name")
1536
+ return setup_alias_path(yes=args.yes)
1533
1537
  except ValueError as exc:
1534
1538
  raise DevKitError(str(exc)) from exc
1535
1539
  raise DevKitError(f"unsupported alias action: {args.action}")
@@ -251,9 +251,10 @@ def build_parser(prog: str | None = None) -> argparse.ArgumentParser:
251
251
 
252
252
  alias_parser = subparsers.add_parser("alias", help="manage local command aliases for agent")
253
253
  alias_parser.add_argument("--json", action="store_true", default=argparse.SUPPRESS, help=argparse.SUPPRESS)
254
- alias_parser.add_argument("action", nargs="?", default="list", choices=["add", "list", "remove", "sync"])
254
+ alias_parser.add_argument("action", nargs="?", default="list", choices=["add", "list", "remove", "sync", "path"])
255
255
  alias_parser.add_argument("name", nargs="?")
256
256
  alias_parser.add_argument("--force", action="store_true", help="allow replacing an existing local alias file")
257
+ alias_parser.add_argument("--yes", action="store_true", help="confirm alias PATH setup")
257
258
 
258
259
  session_parser = subparsers.add_parser("session", help="manage local conversation sessions")
259
260
  session_parser.add_argument("--json", action="store_true", default=argparse.SUPPRESS, help=argparse.SUPPRESS)
@@ -115,6 +115,8 @@ def print_human(result: dict[str, Any]) -> None:
115
115
  print_aliases(result)
116
116
  elif kind == "alias":
117
117
  print_alias(result)
118
+ elif kind == "alias-path":
119
+ print_alias_path(result)
118
120
  elif kind == "sessions":
119
121
  print_sessions(result)
120
122
  elif kind == "session":
@@ -630,6 +632,9 @@ def print_architecture(result: dict[str, Any]) -> None:
630
632
  def print_agent_response(result: dict[str, Any]) -> None:
631
633
  if result.get("status") == "ok":
632
634
  print(result.get("response", ""))
635
+ alias = result.get("alias") if isinstance(result.get("alias"), dict) else None
636
+ if alias:
637
+ print_alias_path_hint(alias.get("path_status") if isinstance(alias.get("path_status"), dict) else None)
633
638
  return
634
639
  print(result.get("message") or result.get("response") or "Agent execution did not complete.")
635
640
  question = result.get("next_question") or ((result.get("setup_wizard") or {}).get("next_question") if isinstance(result.get("setup_wizard"), dict) else None)
@@ -815,6 +820,12 @@ def print_personality(result: dict[str, Any]) -> None:
815
820
  print("Setup questions:")
816
821
  for question in result["questions"]:
817
822
  print(f"- {question}")
823
+ alias = result.get("alias") if isinstance(result.get("alias"), dict) else None
824
+ if alias:
825
+ print(f"Alias: {alias.get('name') or alias.get('suggested_name') or '-'} ({alias.get('status')})")
826
+ if alias.get("path"):
827
+ print(f"Alias path: {alias['path']}")
828
+ print_alias_path_hint(alias.get("path_status") if isinstance(alias.get("path_status"), dict) else None)
818
829
 
819
830
 
820
831
  def print_aliases(result: dict[str, Any]) -> None:
@@ -824,6 +835,7 @@ def print_aliases(result: dict[str, Any]) -> None:
824
835
  return
825
836
  for item in result["items"]:
826
837
  print(f"- {item['name']}: {item['path']}")
838
+ print_alias_path_hint(item.get("path_status") if isinstance(item.get("path_status"), dict) else None, indent=" ")
827
839
 
828
840
 
829
841
  def print_alias(result: dict[str, Any]) -> None:
@@ -835,6 +847,26 @@ def print_alias(result: dict[str, Any]) -> None:
835
847
  for path in result["removed_paths"]:
836
848
  print(f"- {path}")
837
849
  print(f"Config: {result['config_path']}")
850
+ print_alias_path_hint(result.get("path_status") if isinstance(result.get("path_status"), dict) else None)
851
+
852
+
853
+ def print_alias_path(result: dict[str, Any]) -> None:
854
+ print(f"Alias PATH: {result.get('status')}")
855
+ print(f"Bin: {result.get('bin_dir') or '-'}")
856
+ if result.get("profile"):
857
+ print(f"Profile: {result['profile']}")
858
+ if result.get("message"):
859
+ print(result["message"])
860
+
861
+
862
+ def print_alias_path_hint(path_status: dict[str, Any] | None, *, indent: str = "") -> None:
863
+ if not path_status or not path_status.get("setup_required"):
864
+ return
865
+ setup = path_status.get("setup") if isinstance(path_status.get("setup"), dict) else {}
866
+ print(f"{indent}PATH: {path_status.get('bin_dir')} is not active in this shell.")
867
+ command = setup.get("command")
868
+ if command:
869
+ print(f"{indent}Run: {command}")
838
870
 
839
871
 
840
872
  def print_sessions(result: dict[str, Any]) -> None:
@@ -2,11 +2,13 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import os
5
6
  import sys
6
7
  from typing import Any
7
8
 
8
9
  from cli.aikit.core.requests import AgentPromptRequest
9
10
  from cli.aikit.core.runtime import run_agent_prompt
11
+ from cli.aikit.aliases import setup_alias_path
10
12
  from cli.aikit.llm import BACKENDS, configure_backend
11
13
  from cli.aikit.mini_brain import DEFAULT_OLLAMA_MODEL
12
14
  from cli.aikit.ollama import ollama_status
@@ -15,6 +17,30 @@ from cli.aikit.personality import load_personality, update_personality
15
17
  from cli.aikit.runtime_paths import ROOT
16
18
  from cli.aikit.wizard_state import WizardStateError, answer_wizard, cancel_wizard, show_wizard
17
19
 
20
+ ONBOARDING_MODE_OPTIONS = [
21
+ {
22
+ "id": "minimal",
23
+ "number": "1",
24
+ "label": "minimo",
25
+ "description": "identidade, mini-cerebro local embarcado e memoria",
26
+ "recommended": True,
27
+ },
28
+ {
29
+ "id": "complete",
30
+ "number": "2",
31
+ "label": "completo",
32
+ "description": "minimo + toolchain, sources, notificacoes, knowledge e memorias",
33
+ "recommended": False,
34
+ },
35
+ {
36
+ "id": "skip",
37
+ "number": "3",
38
+ "label": "pular",
39
+ "description": "",
40
+ "recommended": False,
41
+ },
42
+ ]
43
+
18
44
 
19
45
  def maybe_run_interactive_wizard(result: dict[str, Any]) -> dict[str, Any]:
20
46
  if not sys.stdin.isatty() or not sys.stdout.isatty():
@@ -126,18 +152,148 @@ def run_interactive_onboarding(result: dict[str, Any]) -> dict[str, Any]:
126
152
 
127
153
 
128
154
  def choose_onboarding_mode() -> str:
155
+ selected = choose_onboarding_mode_with_arrows()
156
+ if selected:
157
+ return selected
158
+ print("\nModos de onboarding:")
159
+ for option in ONBOARDING_MODE_OPTIONS:
160
+ print(format_onboarding_option(option, selected=False, include_selector=False))
161
+ answer = ask_text("Escolha o modo:").strip().lower()
162
+ return parse_onboarding_mode_answer(answer)
163
+
164
+
165
+ def choose_onboarding_mode_with_arrows() -> str | None:
166
+ if not sys.stdin.isatty() or not sys.stdout.isatty() or os.environ.get("TERM") == "dumb":
167
+ return None
168
+ try:
169
+ return read_onboarding_mode_selection()
170
+ except KeyboardInterrupt:
171
+ print()
172
+ return "skip"
173
+ except (OSError, ValueError):
174
+ return None
175
+
176
+
177
+ def read_onboarding_mode_selection() -> str:
178
+ selected_index = 0
179
+ typed_answer = ""
129
180
  print("\nModos de onboarding:")
130
- print("1. minimo: identidade, mini-cerebro local embarcado e memoria")
131
- print("2. completo: minimo + toolchain, sources, notificacoes, knowledge e memorias")
132
- print("3. pular")
133
- answer = ask_text("Escolha o modo", default="minimo").strip().lower()
181
+ render_onboarding_options(selected_index)
182
+ print_onboarding_prompt(typed_answer)
183
+ while True:
184
+ key = read_key()
185
+ if key in {"\x03", "\x04"}:
186
+ raise KeyboardInterrupt
187
+ if key in {"\r", "\n"}:
188
+ if typed_answer:
189
+ parsed = parse_onboarding_mode_answer(typed_answer.strip().lower(), default="")
190
+ if parsed:
191
+ return parsed
192
+ typed_answer = ""
193
+ rerender_onboarding_options(selected_index, typed_answer)
194
+ continue
195
+ return str(ONBOARDING_MODE_OPTIONS[selected_index]["id"])
196
+ if key in {"\x1b[A", "k"}:
197
+ typed_answer = ""
198
+ selected_index = (selected_index - 1) % len(ONBOARDING_MODE_OPTIONS)
199
+ rerender_onboarding_options(selected_index, typed_answer)
200
+ continue
201
+ if key in {"\x1b[B", "j"}:
202
+ typed_answer = ""
203
+ selected_index = (selected_index + 1) % len(ONBOARDING_MODE_OPTIONS)
204
+ rerender_onboarding_options(selected_index, typed_answer)
205
+ continue
206
+ if key in {"\x7f", "\b"}:
207
+ typed_answer = typed_answer[:-1]
208
+ rerender_onboarding_options(selected_index, typed_answer)
209
+ continue
210
+ parsed = parse_onboarding_mode_answer(key.strip().lower(), default="")
211
+ if parsed:
212
+ print()
213
+ return parsed
214
+ if key.isprintable():
215
+ typed_answer += key
216
+ rerender_onboarding_options(selected_index, typed_answer)
217
+
218
+
219
+ def render_onboarding_options(selected_index: int) -> None:
220
+ for index, option in enumerate(ONBOARDING_MODE_OPTIONS):
221
+ print(format_onboarding_option(option, selected=index == selected_index, include_selector=True))
222
+
223
+
224
+ def rerender_onboarding_options(selected_index: int, typed_answer: str) -> None:
225
+ lines_to_move = len(ONBOARDING_MODE_OPTIONS) + 1
226
+ sys.stdout.write(f"\x1b[{lines_to_move}A")
227
+ for index, option in enumerate(ONBOARDING_MODE_OPTIONS):
228
+ sys.stdout.write("\x1b[2K")
229
+ sys.stdout.write(format_onboarding_option(option, selected=index == selected_index, include_selector=True) + "\n")
230
+ sys.stdout.write("\x1b[2K")
231
+ sys.stdout.write(onboarding_prompt_line(typed_answer) + "\n")
232
+ sys.stdout.flush()
233
+
234
+
235
+ def format_onboarding_option(option: dict[str, Any], *, selected: bool, include_selector: bool) -> str:
236
+ selector = "> " if selected else " "
237
+ prefix = selector if include_selector else ""
238
+ description = f": {option['description']}" if option.get("description") else ""
239
+ recommended = " (Recomendado)" if option.get("recommended") else ""
240
+ return f"{prefix}{option['number']}. {option['label']}{description}{recommended}"
241
+
242
+
243
+ def parse_onboarding_mode_answer(answer: str, *, default: str = "minimal") -> str:
244
+ if not answer:
245
+ return default
134
246
  if answer in {"1", "minimo", "mínimo", "minimal"}:
135
247
  return "minimal"
136
248
  if answer in {"2", "completo", "complete", "full"}:
137
249
  return "complete"
138
250
  if answer in {"3", "pular", "skip", "cancelar", "cancel"}:
139
251
  return "skip"
140
- return "minimal"
252
+ return default
253
+
254
+
255
+ def print_onboarding_prompt(typed_answer: str) -> None:
256
+ print(onboarding_prompt_line(typed_answer))
257
+
258
+
259
+ def onboarding_prompt_line(typed_answer: str) -> str:
260
+ suffix = f" {typed_answer}" if typed_answer else ""
261
+ return f"Escolha o modo:{suffix}"
262
+
263
+
264
+ def read_key() -> str:
265
+ if os.name == "nt":
266
+ import msvcrt
267
+
268
+ char = msvcrt.getwch()
269
+ if char in {"\x00", "\xe0"}:
270
+ code = msvcrt.getwch()
271
+ if code == "H":
272
+ return "\x1b[A"
273
+ if code == "P":
274
+ return "\x1b[B"
275
+ return char
276
+
277
+ import termios
278
+ import tty
279
+ import select
280
+
281
+ fd = sys.stdin.fileno()
282
+ old_settings = termios.tcgetattr(fd)
283
+ try:
284
+ tty.setraw(fd)
285
+ char = sys.stdin.read(1)
286
+ if char == "\x1b":
287
+ sequence = ""
288
+ for _ in range(2):
289
+ ready, _, _ = select.select([sys.stdin], [], [], 0.01)
290
+ if not ready:
291
+ break
292
+ sequence += sys.stdin.read(1)
293
+ char += sequence
294
+ return char
295
+ finally:
296
+ termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
141
297
 
142
298
 
143
299
  def configure_personality_interactively(agent: dict[str, Any]) -> None:
@@ -149,13 +305,27 @@ def configure_personality_interactively(agent: dict[str, Any]) -> None:
149
305
  language = ask_text("Idioma padrao das respostas?", default=str(agent.get("language") or "pt-BR"))
150
306
  tone = ask_text("Tom das respostas?", default=current_tone)
151
307
  detail_level = ask_text("Nivel de detalhe?", default=current_detail)
152
- update_personality(
308
+ payload = update_personality(
153
309
  agent_name=agent_name,
154
310
  user_name=user_name,
155
311
  language=language,
156
312
  tone=tone,
157
313
  detail_level=detail_level,
158
314
  )
315
+ alias = payload.get("alias") if isinstance(payload.get("alias"), dict) else None
316
+ if not alias:
317
+ return
318
+ if alias.get("status") == "added":
319
+ print(f"Comando local criado: {alias.get('name')}")
320
+ elif alias.get("message"):
321
+ print(f"Alias nao configurado automaticamente: {alias['message']}")
322
+ path_status = alias.get("path_status") if isinstance(alias.get("path_status"), dict) else {}
323
+ if path_status.get("setup_required"):
324
+ bin_dir = path_status.get("bin_dir")
325
+ print(f"O comando foi criado em {bin_dir}, mas essa pasta ainda nao esta no PATH.")
326
+ if ask_yes_no("Deseja habilitar aliases do Agent DevKit no shell para proximas sessoes?", default=True):
327
+ setup = setup_alias_path(yes=True)
328
+ print(setup.get("message") or "PATH de aliases atualizado.")
159
329
 
160
330
 
161
331
  def configure_llm_interactively() -> None:
@@ -62,6 +62,21 @@ def clean_requested_name(value: str) -> str | None:
62
62
  return cleaned[:80]
63
63
 
64
64
 
65
+ def rename_response(updated: dict[str, Any]) -> str:
66
+ name = updated.get("agent_name")
67
+ alias = updated.get("alias") if isinstance(updated.get("alias"), dict) else {}
68
+ alias_name = alias.get("name") or alias.get("suggested_name")
69
+ path_status = alias.get("path_status") if isinstance(alias.get("path_status"), dict) else {}
70
+ if alias_name and path_status.get("setup_required"):
71
+ return (
72
+ f"Pronto. Meu nome local agora e {name}, e criei o comando {alias_name}. "
73
+ "Para chama-lo diretamente em novas sessoes do shell, execute `agent alias path --yes`."
74
+ )
75
+ if alias_name:
76
+ return f"Pronto. Meu nome local agora e {name}, e voce tambem pode me chamar com o comando {alias_name}."
77
+ return f"Pronto. Meu nome local agora e {name}."
78
+
79
+
65
80
  def is_capabilities_help_prompt(prompt: str) -> bool:
66
81
  normalized = normalize_text(prompt)
67
82
  if normalized in {"ajuda", "help", "o que voce faz", "o que voce consegue fazer"}:
@@ -220,7 +235,8 @@ def run_agent_prompt_request(request: AgentPromptRequest) -> dict[str, Any]:
220
235
  "prompt_length": len(prompt),
221
236
  "identity": {"name": updated.get("agent_name"), "source": "local"},
222
237
  "action": "rename",
223
- "response": f"Pronto. Meu nome local agora e {updated.get('agent_name')}.",
238
+ "response": rename_response(updated),
239
+ "alias": updated.get("alias"),
224
240
  }
225
241
  return finalize_agent_session(result, session, prompt, backend=request.llm)
226
242
  if is_identity_question(prompt):
@@ -6,6 +6,7 @@ import re
6
6
  from pathlib import Path
7
7
  from typing import Any
8
8
 
9
+ from cli.aikit.aliases import ensure_alias_for_agent_name
9
10
  from cli.aikit.identity import DEFAULT_PUBLIC_NAME
10
11
  from cli.aikit.memory import MEMORY_FILE_TEMPLATES, ensure_memory, memory_home
11
12
 
@@ -60,6 +61,8 @@ def update_personality(
60
61
  path.write_text(render_personality(values), encoding="utf-8")
61
62
  payload = load_personality()
62
63
  payload["status"] = "updated"
64
+ if agent_name is not None and payload.get("agent_name") != DEFAULT_PUBLIC_NAME:
65
+ payload["alias"] = ensure_alias_for_agent_name(str(payload["agent_name"]))
63
66
  return payload
64
67
 
65
68
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schema_version": "ai-devkit.release-catalog-snapshot/v1",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "summary": {
5
5
  "agents": 48,
6
6
  "capabilities": 397,