@autonsh/cli 0.16.4 → 0.17.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/dist/index.js CHANGED
@@ -15,7 +15,11 @@ import { SingleBar, Presets } from "cli-progress";
15
15
  import pc from "picocolors";
16
16
  import { ok, fail, info, dim, bold, url, spinner, logo, tagLine, statusColor, table, box, isTTY, } from "./ui.js";
17
17
  import { cmdLogin as loginCmd, cmdLogout as logoutCmd, credentialEnv } from "./login.js";
18
- import { TEMPLATES, promptInit, promptConfirmUndeploy, promptGateApproval, } from "./prompts.js";
18
+ import { cmdModel, modelLs, modelSet, modelTest } from "./model.js";
19
+ import { cmdChat } from "./chat.js";
20
+ import { resolveAgent } from "./picker.js";
21
+ import { getDefaultModel, getActiveProvider } from "./config.js";
22
+ import { TEMPLATES, promptInit, promptConfirmUndeploy, promptGateApproval, promptGateQuestion, parseGateLine, } from "./prompts.js";
19
23
  import { loadTaskManifest, MODES, SKILLS, EXIT, ExitError, } from "./task.js";
20
24
  import { runVerify } from "./verify.js";
21
25
  const SRC = dirname(fileURLToPath(import.meta.url));
@@ -168,38 +172,72 @@ function authHeaders(extra) {
168
172
  h.Authorization = `Bearer ${token}`;
169
173
  return h;
170
174
  }
175
+ /** Nomes dos agentes deployados (0.17.0 — `auton <nome>` abre sessão). */
176
+ async function listAgentNames() {
177
+ try {
178
+ const res = await fetch(`${API}/v1/agents`, { headers: authHeaders() });
179
+ if (!res.ok)
180
+ return [];
181
+ return (await res.json()).map((a) => a.name);
182
+ }
183
+ catch {
184
+ return [];
185
+ }
186
+ }
171
187
  const USAGE = `
172
188
  auton — Tasks that start themselves
173
189
 
174
190
  Usage:
175
- auton run [goal] Roda a task do diretório atual com o objetivo
176
- (usa task.yaml: goal default; gates de aprovação
177
- perguntam em linha — TTY; verify: roda antes — use
178
- --skip-verify pra pular)
179
- auton verify [dir] [--json] Roda gates mecânicos do verify: (task.yaml)
180
- auton inspect [dir] [--json] Inspeciona task + evidência (goal/mode/verify/traces)
181
- auton continue [name] [goal] Retoma a última task (sem redeploy)
182
- auton init [nome] [template] Scaffold de task (support|with-approval|cron-digester|webhook-echo)
183
- auton ps Lista tasks/agentes + status
184
- auton logs [name] Stream de logs da task (SSE)
185
- auton status [name] Status da task
191
+ auton <comando> [argumentos]
192
+
193
+ Modelos:
194
+ auton model Seletor interativo do modelo default
195
+ (local ollama + providers configurados)
196
+ auton model ls [--json] Lista modelos locais + providers
197
+ auton model set <spec> Define default (ex: ollama:qwen3)
198
+ auton model test Testa latência dos providers
199
+ auton login [provider] [--apikey X]
200
+ Conecta provider (gemini/groq/ollama)
201
+ auton logout [id] Remove provider configurado
202
+
203
+ Agentes:
204
+ auton [nome] Sessão de trabalho (estilo opencode): dá
205
+ tasks, acompanha o agente ao vivo, gates
206
+ inline. Sem nome: cwd/picker.
207
+ auton chat [name] Alias da sessão de trabalho
208
+ auton agent [dir] Deploy do agente no diretório
209
+ auton ps Lista agentes + status
210
+ auton logs [name] [--tail N] [--follow] [--since X] [--turn N]
211
+ Stream de logs do agente (SSE)
212
+ auton status [name] Status do agente
213
+ auton restart [name] Reinicia o agente
186
214
  auton invoke [name] [--stream] [-- prompt]
187
- Roda 1 turno (legado v0)
188
- auton kv get|set|ls [name] [key] [value]
189
- Inspeciona o storage KV
190
- auton context [name] Lista work context: decisions/findings/artifacts
215
+ Roda 1 turno (legado v0)
191
216
  auton approve|reject [name] [-- razão]
192
- Decide o gate de aprovação (approval: true)
193
- auton undeploy [name] Remove a task
194
- auton login TODO: auth com control plane (dpk_...)
217
+ Decide o gate de aprovação
218
+ auton answer [name] [-- texto] Responde pergunta pendente (ctx.ask)
219
+ auton context [name] Work context: decisions/findings/artifacts
220
+ auton undeploy [name] Remove o agente (--all remove tudo)
221
+
222
+ Tasks:
223
+ auton run [goal] [--model X] [--skip-verify]
224
+ Roda a task do diretório atual
225
+ auton verify [dir] [--json] Roda gates mecânicos do verify
226
+ auton inspect [dir] [--json] Inspeciona task + evidência
227
+ auton continue [name] [goal] Retoma a última task (sem redeploy)
228
+ auton init [nome] [template] Scaffold de task (support|with-approval|...)
229
+
230
+ Infra:
231
+ auton kv get|set|ls [name] [key] [value]
232
+ Inspeciona o storage KV
233
+ auton env set|get|ls [name] [key] [value]
234
+ Env vars por agente
195
235
 
196
236
  Options:
197
237
  --help, -h Mostra ajuda
198
- --version, -v Mostra versão
238
+ --version, -v Mostra versão + modelo default
199
239
  AUTON_API=http://... Base do control plane (default localhost:8080)
200
240
 
201
- Nota: commandos legado v0 (deploy agent . / deploy invoke) continuam funcionando.
202
-
203
241
  Exit codes (0.14):
204
242
  0 ok · 1 erro genérico · 2 uso inválido · 3 verificação falhou
205
243
  4 abortado pelo usuário (gate rejeitado) · 5 timeout (invoke)
@@ -414,12 +452,28 @@ async function cmdRun(dir, goalArg, skipVerify, model) {
414
452
  const manifest = await loadTaskManifest(dir);
415
453
  if (!skipVerify)
416
454
  await verifyPreFlight(dir, manifest);
417
- const goal = goalArg ?? manifest.goal;
455
+ let goal = goalArg ?? manifest.goal;
418
456
  if (!goal) {
419
- throw new Error(`task ${manifest.name} sem objetivo — passe "${bold('auton run "<objetivo>"')}" ou defina 'goal' no task.yaml`);
457
+ if (isTTY) {
458
+ // 0.17: run sem goal pergunta (estilo `ollama run` sem modelo)
459
+ const { text, isCancel } = await import("@clack/prompts");
460
+ const answered = (await text({
461
+ message: `qual o objetivo da task ${manifest.name}?`,
462
+ validate: (v) => (!v || v.trim() === "" ? "objetivo não pode ser vazio" : undefined),
463
+ }));
464
+ if (isCancel(answered)) {
465
+ console.log(dim("cancelado"));
466
+ return;
467
+ }
468
+ goal = answered.trim();
469
+ }
470
+ else {
471
+ throw new Error(`task ${manifest.name} sem objetivo — passe "${bold('auton run "<objetivo>"')}" ou defina 'goal' no task.yaml`);
472
+ }
420
473
  }
421
- // 0.16.13: modelo efetivo = --model > task.yaml > env; valida no provider antes do turno
422
- await precheckModel(model ?? manifest.models ?? process.env.AUTON_MODEL ?? "");
474
+ // 0.17: modelo efetivo = --model > task.yaml > env > config default (auton model)
475
+ const defaultModel = getDefaultModel();
476
+ await precheckModel(model ?? manifest.models ?? process.env.AUTON_MODEL ?? defaultModel);
423
477
  // 0.15.1: credencial do `auton login` injetada no deploy (key/base);
424
478
  // --model (0.14.3) sobrescreve models: do task.yaml
425
479
  await cmdDeploy(dir, { ...credentialEnv(), ...(model ? { AUTON_MODEL: model } : {}) });
@@ -431,7 +485,10 @@ async function cmdRun(dir, goalArg, skipVerify, model) {
431
485
  async function precheckModel(model) {
432
486
  if (!model)
433
487
  return;
434
- const [provider, name] = model.split(":", 2);
488
+ // 0.17.1 — separa provider do nome SEM cortar a tag (ollama:deepseek-coder:1.3b)
489
+ const sep = model.indexOf(":");
490
+ const provider = sep === -1 ? model : model.slice(0, sep);
491
+ const name = sep === -1 ? undefined : model.slice(sep + 1);
435
492
  if (!name)
436
493
  return;
437
494
  if (provider === "ollama") {
@@ -462,30 +519,37 @@ async function precheckModel(model) {
462
519
  return;
463
520
  }
464
521
  }
465
- /** Roda o turno resolvendo gates pendentes em linha (TTY pergunta; pipe reporta). */
522
+ /** Roda o turno resolvendo gates pendentes em linha (TTY pergunta; pipe reporta).
523
+ * 0.17.0: além de aprovação, resolve perguntas de clarificação (ctx.ask). */
466
524
  async function runWithGates(name, goal) {
467
525
  let lines = await cmdInvoke(name, goal, false);
468
526
  for (let i = 0; i < 10; i++) {
469
- const gate = lines.find((l) => l.startsWith("[APPROVAL:REQ]"));
527
+ const gate = lines.find((l) => l.startsWith("[APPROVAL:REQ]") || l.startsWith("[QUESTION:REQ]"));
470
528
  if (!gate)
471
529
  return lines;
530
+ const g = parseGateLine(gate);
531
+ if (!g)
532
+ return lines;
533
+ if (g.kind === "question") {
534
+ if (!isTTY) {
535
+ console.log(info("pergunta pendente — rode `auton answer <nome> -- \"resposta\"`"));
536
+ return lines;
537
+ }
538
+ const answer = await promptGateQuestion(String(g.payload?.question ?? "pergunta"));
539
+ if (answer === undefined)
540
+ return lines;
541
+ lines = await answerPost(name, answer);
542
+ continue;
543
+ }
472
544
  if (!isTTY) {
473
545
  console.log(info("aprovação pendente — rode `auton approve` ou `auton reject`"));
474
546
  return lines;
475
547
  }
476
- let payload;
477
- try {
478
- payload = gate.slice("[APPROVAL:REQ]".length).trim();
479
- JSON.parse(payload);
480
- }
481
- catch {
482
- payload = "{}";
483
- }
484
- const reason = JSON.parse(payload).reason ?? "ação";
548
+ const reason = g.payload?.reason ?? "ação";
485
549
  const approved = await promptGateApproval(reason);
486
550
  lines = await cmdInvokeApproval(name, approved, undefined);
487
551
  }
488
- throw new Error(`loop de aprovação não convergiu (10 gates)`);
552
+ throw new Error(`loop de gates não convergiu (10)`);
489
553
  }
490
554
  /** POST de controle: resolve o gate pendente no worker (sem novo trigger). */
491
555
  async function cmdInvokeApproval(name, approved, reason) {
@@ -506,6 +570,62 @@ async function cmdInvokeApproval(name, approved, reason) {
506
570
  console.log(line);
507
571
  return out.lines;
508
572
  }
573
+ /** 0.17.0 — POST de controle de pergunta: resolve ctx.ask no worker (sem trigger novo). */
574
+ async function answerPost(name, answer) {
575
+ const res = await fetch(`${API}/v1/agents/${name}/invoke`, {
576
+ method: "POST",
577
+ headers: authHeaders({ "Content-Type": "application/json" }),
578
+ body: JSON.stringify({ question: { answer } }),
579
+ });
580
+ if (!res.ok) {
581
+ const body = await res.text();
582
+ if (res.status === 409 && body.includes("sem pergunta pendente")) {
583
+ throw new Error(`task ${name} sem pergunta pendente`);
584
+ }
585
+ throw new Error(`control plane ${res.status}: ${body}`);
586
+ }
587
+ const out = (await res.json());
588
+ return out.lines;
589
+ }
590
+ /** auton answer [name] [-- texto] — responde pergunta de clarificação pendente. */
591
+ async function cmdAnswer(name, answer) {
592
+ const agent = await resolveAgent("answer", name);
593
+ if (answer === undefined) {
594
+ if (!isTTY) {
595
+ throw new ExitError(EXIT.USAGE, `uso: auton answer <nome> -- "resposta" (pipe)`);
596
+ }
597
+ // mostra a pergunta pendente (logs recentes) e pede a resposta
598
+ let question = "pergunta pendente — responda:";
599
+ const logs = await fetch(`${API}/v1/agents/${agent}/logs?tail=30`, {
600
+ headers: authHeaders({ Accept: "application/json" }),
601
+ });
602
+ if (logs.ok) {
603
+ const entries = (await logs.json());
604
+ const qline = [...entries].reverse().find((e) => e.line.startsWith("[QUESTION:REQ]"));
605
+ if (qline) {
606
+ const g = parseGateLine(qline.line);
607
+ if (g)
608
+ question = String(g.payload?.question ?? question);
609
+ }
610
+ }
611
+ answer = await promptGateQuestion(question);
612
+ if (answer === undefined) {
613
+ console.log(dim("cancelado — agente segue pausado"));
614
+ return;
615
+ }
616
+ }
617
+ const lines = await answerPost(agent, answer);
618
+ console.log(ok(`resposta entregue — task ${bold(agent)} segue.`));
619
+ const done = lines.find((l) => l.startsWith("[TASK:DONE]"));
620
+ if (done) {
621
+ const ev = JSON.parse(done.slice("[TASK:DONE]".length).trim());
622
+ if (ev.result)
623
+ console.log(dim(`resultado: ${ev.result}`));
624
+ }
625
+ else if (lines.some((l) => l.startsWith("[QUESTION:REQ]"))) {
626
+ console.log(info("nova pergunta pendente — `auton answer` de novo."));
627
+ }
628
+ }
509
629
  /** auton continue — retoma a última task sem re-passar o objetivo.
510
630
  * O harness guarda o último input no KV ("ultima-task"); aqui só busca e
511
631
  * invoca de novo (worker dormindo → cold start; KV persiste). */
@@ -531,15 +651,7 @@ async function cmdContinue(dir, nameArg, goalArg) {
531
651
  * 0.12: vira POST de CONTROLE (resolve gate pendente no worker vivo);
532
652
  * gate de legado (template com approval:) também é resolvido assim. */
533
653
  async function cmdApproval(name, approved, reason) {
534
- let agent = name;
535
- if (!agent) {
536
- try {
537
- agent = (await loadTaskManifest(process.cwd())).name;
538
- }
539
- catch {
540
- throw new Error(`${approved ? "approve" : "reject"}: nenhum nome passado e cwd não é um agente — rode \`auton ${approved ? "approve" : "reject"} <nome>\``);
541
- }
542
- }
654
+ const agent = await resolveAgent(approved ? "approve" : "reject", name);
543
655
  const lines = await cmdInvokeApproval(agent, approved, reason);
544
656
  const done = lines.find((l) => l.startsWith("[TASK:DONE]"));
545
657
  if (done) {
@@ -559,14 +671,36 @@ async function cmdApproval(name, approved, reason) {
559
671
  if (!approved)
560
672
  process.exitCode = EXIT.ABORT;
561
673
  }
562
- async function cmdLogs(name, tail, since) {
563
- const agent = name ?? (await loadTaskManifest(process.cwd())).name;
674
+ /** 0.16.14: snapshot JSON c/ timestamp + filtro por turno (--turn N = últimos N turnos);
675
+ * --follow mantém SSE ao vivo (sem ts, como antes). */
676
+ async function cmdLogs(name, tail, since, follow = false, turn) {
677
+ const agent = name ?? (await resolveAgent("logs", undefined));
564
678
  const params = new URLSearchParams();
565
679
  if (tail !== undefined)
566
680
  params.set("tail", String(tail));
567
681
  if (since !== undefined)
568
682
  params.set("since", since);
569
683
  const url = `${API}/v1/agents/${agent}/logs${params.toString() ? "?" + params.toString() : ""}`;
684
+ if (!follow) {
685
+ const res = await fetch(url, { headers: authHeaders({ Accept: "application/json" }) });
686
+ if (!res.ok)
687
+ throw new Error(`control plane ${res.status}: ${await res.text()}`);
688
+ let entries = (await res.json());
689
+ if (turn !== undefined && turn > 0) {
690
+ const groups = [];
691
+ for (const e of entries) {
692
+ if (e.line.includes("[runtime] turno iniciado"))
693
+ groups.push([]);
694
+ if (groups.length > 0)
695
+ groups[groups.length - 1].push(e);
696
+ }
697
+ entries = groups.slice(-turn).flat();
698
+ }
699
+ for (const e of entries) {
700
+ console.log(tagLine(`${dim(e.ts.slice(11, 19))} ${e.line}`));
701
+ }
702
+ return;
703
+ }
570
704
  const res = await fetch(url, {
571
705
  headers: authHeaders({ Accept: "text/event-stream" }),
572
706
  });
@@ -575,6 +709,7 @@ async function cmdLogs(name, tail, since) {
575
709
  }
576
710
  if (!res.body)
577
711
  throw new Error("resposta sem body");
712
+ console.log(dim(`— seguindo ${agent} — Ctrl+C sai —`));
578
713
  const reader = res.body.getReader();
579
714
  const decoder = new TextDecoder();
580
715
  let buf = "";
@@ -597,7 +732,7 @@ async function cmdLogs(name, tail, since) {
597
732
  }
598
733
  }
599
734
  async function cmdStatus(name) {
600
- const agent = name ?? (await loadTaskManifest(process.cwd())).name;
735
+ const agent = await resolveAgent("status", name);
601
736
  const res = await fetch(`${API}/v1/agents/${agent}`, {
602
737
  headers: authHeaders(),
603
738
  });
@@ -608,7 +743,7 @@ async function cmdStatus(name) {
608
743
  }
609
744
  async function cmdRestart(name) {
610
745
  // 0.1.1 — reinicia o agente (SIGTERM + respawn, state preservado)
611
- const agent = name ?? (await loadTaskManifest(process.cwd())).name;
746
+ const agent = await resolveAgent("restart", name);
612
747
  const res = await fetch(`${API}/v1/agents/${agent}/restart`, {
613
748
  method: "POST",
614
749
  headers: authHeaders(),
@@ -639,7 +774,7 @@ async function cmdUndeploy(name) {
639
774
  console.log(dim("(nada pra remover)"));
640
775
  return;
641
776
  }
642
- const agent = name ?? (await loadTaskManifest(process.cwd())).name;
777
+ const agent = await resolveAgent("undeploy", name);
643
778
  if (isTTY && !(await promptConfirmUndeploy(agent)))
644
779
  return;
645
780
  const res = await fetch(`${API}/v1/agents/${agent}`, {
@@ -797,7 +932,7 @@ async function cmdInvoke(name, prompt, stream) {
797
932
  /** autonomous context [name] — imprime a memória do trabalho da task:
798
933
  * decisions/findings/artifacts gravadas via ctx.work (KV "work:*"). */
799
934
  async function cmdContext(name) {
800
- const agent = name ?? (await loadTaskManifest(process.cwd())).name;
935
+ const agent = await resolveAgent("context", name);
801
936
  const res = await fetch(`${API}/v1/agents/${agent}/kv`, { headers: authHeaders() });
802
937
  if (!res.ok)
803
938
  throw new Error(`control plane ${res.status}: ${await res.text()}`);
@@ -894,8 +1029,9 @@ async function cmdPs(json) {
894
1029
  a.cron ? "sim" : "-",
895
1030
  String(a.logs),
896
1031
  String(a.kv),
1032
+ a.model ? String(a.model) : "-",
897
1033
  ]);
898
- console.log(table(["AGENTE", "STATUS", "CRON", "LOGS", "KV"], rows));
1034
+ console.log(table(["AGENTE", "STATUS", "CRON", "LOGS", "KV", "MODEL"], rows));
899
1035
  }
900
1036
  async function cmdKv(op, name, key, value) {
901
1037
  const agent = name ?? (await loadTaskManifest(process.cwd())).name;
@@ -945,12 +1081,22 @@ async function cmdLogin(args) {
945
1081
  .find(() => true);
946
1082
  await loginCmd(provider, apiKey);
947
1083
  }
948
- async function cmdLogout() {
949
- await logoutCmd();
1084
+ async function cmdLogout(providerId) {
1085
+ await logoutCmd(providerId);
950
1086
  }
951
1087
  async function main() {
952
1088
  const args = process.argv.slice(2);
953
- if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
1089
+ if (args.length === 0) {
1090
+ // 0.17.0 — `auton` bare: abre sessão de trabalho (estilo opencode):
1091
+ // cwd é agente → sessão nele; senão picker. Pipe: mosta ajuda.
1092
+ if (isTTY) {
1093
+ await cmdChat(undefined);
1094
+ return;
1095
+ }
1096
+ console.log(USAGE);
1097
+ return;
1098
+ }
1099
+ if (args[0] === "--help" || args[0] === "-h") {
954
1100
  console.log(USAGE);
955
1101
  return;
956
1102
  }
@@ -958,15 +1104,57 @@ async function main() {
958
1104
  if (isTTY)
959
1105
  console.log(logo());
960
1106
  console.log(bold(`auton v${VERSION}`));
1107
+ const d = getDefaultModel();
1108
+ const active = getActiveProvider();
1109
+ if (d)
1110
+ console.log(dim(` modelo default: ${d}`));
1111
+ if (active)
1112
+ console.log(dim(` provider: ${active.id} (${active.quando.slice(0, 10)})`));
961
1113
  return;
962
1114
  }
1115
+ // 0.17.0 — `auton <nome-de-agente-deployado>` abre sessão (comandos têm prioridade).
1116
+ const KNOWN = new Set([
1117
+ "login", "logout", "model", "agent", "logs", "status", "restart", "run",
1118
+ "verify", "inspect", "continue", "invoke", "undeploy", "approve", "reject",
1119
+ "ps", "context", "env", "init", "kv", "chat", "answer",
1120
+ ]);
1121
+ if (!KNOWN.has(args[0]) && isTTY) {
1122
+ const deployed = await listAgentNames();
1123
+ if (deployed.includes(args[0])) {
1124
+ await cmdChat(args[0]);
1125
+ return;
1126
+ }
1127
+ }
963
1128
  switch (args[0]) {
964
1129
  case "login":
965
1130
  await cmdLogin(args.slice(1));
966
1131
  break;
967
1132
  case "logout":
968
- await cmdLogout();
1133
+ await cmdLogout(args[1]);
969
1134
  break;
1135
+ case "model": {
1136
+ // auton model [ls|set <spec>|test] — sem sub: seletor TTY
1137
+ const sub = args[1];
1138
+ const json = args.includes("--json");
1139
+ if (sub === "ls") {
1140
+ await modelLs(json);
1141
+ }
1142
+ else if (sub === "set") {
1143
+ if (!args[2])
1144
+ throw new ExitError(EXIT.USAGE, "uso: auton model set <provider:modelo>");
1145
+ await modelSet(args[2]);
1146
+ }
1147
+ else if (sub === "test") {
1148
+ await modelTest();
1149
+ }
1150
+ else if (sub === undefined || sub === "select") {
1151
+ await cmdModel();
1152
+ }
1153
+ else {
1154
+ throw new ExitError(EXIT.USAGE, `uso: auton model [ls|set <spec>|test] — subcomando desconhecido: ${sub}`);
1155
+ }
1156
+ break;
1157
+ }
970
1158
  case "agent":
971
1159
  if (args[1]) {
972
1160
  const dir = resolve(args[1]);
@@ -979,13 +1167,15 @@ async function main() {
979
1167
  }
980
1168
  break;
981
1169
  case "logs":
982
- // args[1..]: [name] [--tail N] [--since RFC3339]
1170
+ // args[1..]: [name] [--tail N] [--since RFC3339] [--follow] [--turn N]
983
1171
  const rest = args.slice(1);
984
1172
  const tailIdx = rest.indexOf("--tail");
985
1173
  const sinceIdx = rest.indexOf("--since");
986
1174
  let name;
987
1175
  let tail;
988
1176
  let since;
1177
+ let follow = false;
1178
+ let turn;
989
1179
  for (let i = 0; i < rest.length; i++) {
990
1180
  const arg = rest[i];
991
1181
  if (arg === "--tail" && i + 1 < rest.length) {
@@ -996,11 +1186,18 @@ async function main() {
996
1186
  since = rest[i + 1];
997
1187
  i++;
998
1188
  }
1189
+ else if (arg === "--turn" && i + 1 < rest.length) {
1190
+ turn = Number(rest[i + 1]);
1191
+ i++;
1192
+ }
1193
+ else if (arg === "--follow") {
1194
+ follow = true;
1195
+ }
999
1196
  else if (!arg.startsWith("-") && !name) {
1000
1197
  name = arg;
1001
1198
  }
1002
1199
  }
1003
- await cmdLogs(name, tail, since);
1200
+ await cmdLogs(name, tail, since, follow, turn);
1004
1201
  break;
1005
1202
  case "status":
1006
1203
  await cmdStatus(args[1]);
@@ -1067,6 +1264,17 @@ async function main() {
1067
1264
  await cmdApproval(name, approved, reason);
1068
1265
  break;
1069
1266
  }
1267
+ case "chat":
1268
+ await cmdChat(args[1]);
1269
+ break;
1270
+ case "answer": {
1271
+ const rest = args.slice(1);
1272
+ const dash = rest.indexOf("--");
1273
+ const name = dash === -1 ? rest[0] : dash === 0 ? undefined : rest[0];
1274
+ const answer = dash !== -1 ? rest.slice(dash + 1).join(" ") : undefined;
1275
+ await cmdAnswer(name, answer);
1276
+ break;
1277
+ }
1070
1278
  case "ps":
1071
1279
  await cmdPs(args[1] === "--json");
1072
1280
  break;