@autonsh/cli 0.16.5 → 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) {
@@ -562,7 +674,7 @@ async function cmdApproval(name, approved, reason) {
562
674
  /** 0.16.14: snapshot JSON c/ timestamp + filtro por turno (--turn N = últimos N turnos);
563
675
  * --follow mantém SSE ao vivo (sem ts, como antes). */
564
676
  async function cmdLogs(name, tail, since, follow = false, turn) {
565
- const agent = name ?? (await loadTaskManifest(process.cwd())).name;
677
+ const agent = name ?? (await resolveAgent("logs", undefined));
566
678
  const params = new URLSearchParams();
567
679
  if (tail !== undefined)
568
680
  params.set("tail", String(tail));
@@ -597,6 +709,7 @@ async function cmdLogs(name, tail, since, follow = false, turn) {
597
709
  }
598
710
  if (!res.body)
599
711
  throw new Error("resposta sem body");
712
+ console.log(dim(`— seguindo ${agent} — Ctrl+C sai —`));
600
713
  const reader = res.body.getReader();
601
714
  const decoder = new TextDecoder();
602
715
  let buf = "";
@@ -619,7 +732,7 @@ async function cmdLogs(name, tail, since, follow = false, turn) {
619
732
  }
620
733
  }
621
734
  async function cmdStatus(name) {
622
- const agent = name ?? (await loadTaskManifest(process.cwd())).name;
735
+ const agent = await resolveAgent("status", name);
623
736
  const res = await fetch(`${API}/v1/agents/${agent}`, {
624
737
  headers: authHeaders(),
625
738
  });
@@ -630,7 +743,7 @@ async function cmdStatus(name) {
630
743
  }
631
744
  async function cmdRestart(name) {
632
745
  // 0.1.1 — reinicia o agente (SIGTERM + respawn, state preservado)
633
- const agent = name ?? (await loadTaskManifest(process.cwd())).name;
746
+ const agent = await resolveAgent("restart", name);
634
747
  const res = await fetch(`${API}/v1/agents/${agent}/restart`, {
635
748
  method: "POST",
636
749
  headers: authHeaders(),
@@ -661,7 +774,7 @@ async function cmdUndeploy(name) {
661
774
  console.log(dim("(nada pra remover)"));
662
775
  return;
663
776
  }
664
- const agent = name ?? (await loadTaskManifest(process.cwd())).name;
777
+ const agent = await resolveAgent("undeploy", name);
665
778
  if (isTTY && !(await promptConfirmUndeploy(agent)))
666
779
  return;
667
780
  const res = await fetch(`${API}/v1/agents/${agent}`, {
@@ -819,7 +932,7 @@ async function cmdInvoke(name, prompt, stream) {
819
932
  /** autonomous context [name] — imprime a memória do trabalho da task:
820
933
  * decisions/findings/artifacts gravadas via ctx.work (KV "work:*"). */
821
934
  async function cmdContext(name) {
822
- const agent = name ?? (await loadTaskManifest(process.cwd())).name;
935
+ const agent = await resolveAgent("context", name);
823
936
  const res = await fetch(`${API}/v1/agents/${agent}/kv`, { headers: authHeaders() });
824
937
  if (!res.ok)
825
938
  throw new Error(`control plane ${res.status}: ${await res.text()}`);
@@ -916,8 +1029,9 @@ async function cmdPs(json) {
916
1029
  a.cron ? "sim" : "-",
917
1030
  String(a.logs),
918
1031
  String(a.kv),
1032
+ a.model ? String(a.model) : "-",
919
1033
  ]);
920
- console.log(table(["AGENTE", "STATUS", "CRON", "LOGS", "KV"], rows));
1034
+ console.log(table(["AGENTE", "STATUS", "CRON", "LOGS", "KV", "MODEL"], rows));
921
1035
  }
922
1036
  async function cmdKv(op, name, key, value) {
923
1037
  const agent = name ?? (await loadTaskManifest(process.cwd())).name;
@@ -967,12 +1081,22 @@ async function cmdLogin(args) {
967
1081
  .find(() => true);
968
1082
  await loginCmd(provider, apiKey);
969
1083
  }
970
- async function cmdLogout() {
971
- await logoutCmd();
1084
+ async function cmdLogout(providerId) {
1085
+ await logoutCmd(providerId);
972
1086
  }
973
1087
  async function main() {
974
1088
  const args = process.argv.slice(2);
975
- 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") {
976
1100
  console.log(USAGE);
977
1101
  return;
978
1102
  }
@@ -980,15 +1104,57 @@ async function main() {
980
1104
  if (isTTY)
981
1105
  console.log(logo());
982
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)})`));
983
1113
  return;
984
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
+ }
985
1128
  switch (args[0]) {
986
1129
  case "login":
987
1130
  await cmdLogin(args.slice(1));
988
1131
  break;
989
1132
  case "logout":
990
- await cmdLogout();
1133
+ await cmdLogout(args[1]);
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
+ }
991
1156
  break;
1157
+ }
992
1158
  case "agent":
993
1159
  if (args[1]) {
994
1160
  const dir = resolve(args[1]);
@@ -1098,6 +1264,17 @@ async function main() {
1098
1264
  await cmdApproval(name, approved, reason);
1099
1265
  break;
1100
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
+ }
1101
1278
  case "ps":
1102
1279
  await cmdPs(args[1] === "--json");
1103
1280
  break;