@autonsh/cli 0.17.1 → 0.18.0

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
@@ -19,7 +19,9 @@ import { cmdModel, modelLs, modelSet, modelTest } from "./model.js";
19
19
  import { cmdChat } from "./chat.js";
20
20
  import { resolveAgent } from "./picker.js";
21
21
  import { getDefaultModel, getActiveProvider } from "./config.js";
22
- import { TEMPLATES, promptInit, promptConfirmUndeploy, promptGateApproval, promptGateQuestion, parseGateLine, } from "./prompts.js";
22
+ import { promptInit, promptConfirmUndeploy, promptGateApproval, promptGateQuestion, parseGateLine, listTemplatesForDisplay, } from "./prompts.js";
23
+ import { getTemplateRegistry } from "./template-registry.js";
24
+ import { renderTemplate } from "./template-engine.js";
23
25
  import { loadTaskManifest, MODES, SKILLS, EXIT, ExitError, } from "./task.js";
24
26
  import { runVerify } from "./verify.js";
25
27
  const SRC = dirname(fileURLToPath(import.meta.url));
@@ -49,8 +51,8 @@ function ensureRuntimeLink(dir) {
49
51
  * TTY: barra de progresso com fases do npm. Pipe: silencioso, 1 linha. */
50
52
  async function installRuntime(dir) {
51
53
  const run = (onLine) => new Promise((resolveP, reject) => {
52
- const args = ["install", "@autonsh/runtime", "--no-audit", "--no-fund", "--loglevel=info"];
53
- const child = spawn("npm", args, {
54
+ const realArgs = ["install", "@autonsh/runtime", "--no-audit", "--no-fund", "--loglevel=info"];
55
+ const child = spawn("npm", realArgs, {
54
56
  cwd: dir,
55
57
  stdio: ["ignore", "ignore", "pipe"],
56
58
  shell: process.platform === "win32",
@@ -109,62 +111,185 @@ async function installRuntime(dir) {
109
111
  bar.stop();
110
112
  }
111
113
  }
112
- async function cmdInit(name, template, opts = {}) {
114
+ async function cmdInit(name, templateId, opts = {}) {
115
+ const registry = await getTemplateRegistry();
116
+ const template = await registry.get(templateId);
117
+ if (!template) {
118
+ throw new ExitError(EXIT.USAGE, `template não encontrado: ${templateId}. Rode 'auton template list' para ver disponíveis.`);
119
+ }
113
120
  const plain = name === "." ? basename(process.cwd()) : name;
114
121
  const dir = resolve(name === "." ? process.cwd() : join(process.cwd(), name));
115
122
  if (existsSync(dir) && readdirSync(dir).length > 0) {
116
123
  throw new ExitError(EXIT.USAGE, `diretório não vazio: ${dir}`);
117
124
  }
125
+ // Prepara contexto de renderização
126
+ const context = {
127
+ name: plain,
128
+ ...opts.variables,
129
+ };
130
+ if (opts.dryRun) {
131
+ console.log(info(`[dry-run] Scaffold seria criado em: ${dir}`));
132
+ console.log(info(`[dry-run] Template: ${template.name} (${template.id})`));
133
+ console.log(info(`[dry-run] Variáveis: ${JSON.stringify(context, null, 2)}`));
134
+ return;
135
+ }
118
136
  mkdirSync(dir, { recursive: true });
119
- cpSync(join(TEMPLATES_SRC, template), dir, { recursive: true });
120
- // seta o nome real no manifest (task.yaml desde 0.9.1)
137
+ // Renderiza template via engine (Handlebars)
138
+ const result = await renderTemplate(template, context, dir);
139
+ // Substitui name no task.yaml (backward compat: templates built-in têm name hardcoded)
121
140
  const yamlPath = join(dir, "task.yaml");
122
- const yaml = (await readFile(yamlPath, "utf8")).replace(/^name: .*$/m, `name: ${plain}`);
123
- await writeFile(yamlPath, yaml);
124
- // mode (pivô 0.11): permissões do worker; docs do modo viram agent.md do projeto.
141
+ if (existsSync(yamlPath)) {
142
+ let yaml = await readFile(yamlPath, "utf8");
143
+ yaml = yaml.replace(/^name: .*$/m, `name: ${plain}`);
144
+ await writeFile(yamlPath, yaml);
145
+ }
146
+ // Garante package.json (pode vir do template ou ser gerado)
147
+ const pkgPath = join(dir, "package.json");
148
+ if (!existsSync(pkgPath)) {
149
+ const pkgName = plain.toLowerCase().replace(/[^a-z0-9._-]/g, "-");
150
+ await writeFile(pkgPath, JSON.stringify({
151
+ name: pkgName || "agent",
152
+ version: "0.0.0",
153
+ private: true,
154
+ type: "module",
155
+ dependencies: { "@autonsh/runtime": "^0.11.0" },
156
+ }, null, 2) + "\n");
157
+ }
158
+ // Genesis custom (se fornecido via --genesis ou prompt)
159
+ if (opts.genesis) {
160
+ const yamlPath = join(dir, "task.yaml");
161
+ let yaml = await readFile(yamlPath, "utf8");
162
+ // Substitui ou adiciona bloco genesis
163
+ if (yaml.includes("genesis:")) {
164
+ yaml = yaml.replace(/^genesis:[\s\S]*?(?=\n\S|\n*$)/m, `genesis: |\n ${opts.genesis.replace(/\n/g, "\n ")}\n`);
165
+ }
166
+ else {
167
+ yaml += `\ngenesis: |\n ${opts.genesis.replace(/\n/g, "\n ")}\n`;
168
+ }
169
+ await writeFile(yamlPath, yaml);
170
+ }
171
+ // Mode + skills do template (podem ser sobrescritos por opts)
125
172
  const mode = opts.mode && MODES.includes(opts.mode)
126
173
  ? opts.mode
127
- : "build";
174
+ : template.defaultMode;
175
+ const skills = (opts.skills ?? template.skills).filter((s) => SKILLS.includes(s));
176
+ // Atualiza task.yaml com mode/skills apenas se override explícito
177
+ if (opts.mode || opts.skills) {
178
+ const yamlPath2 = join(dir, "task.yaml");
179
+ let yaml = await readFile(yamlPath2, "utf8");
180
+ yaml = yaml
181
+ .split("\n")
182
+ .filter((line) => !/^(mode|skills):/m.test(line) && !/^\s*-\s/.test(line))
183
+ .join("\n")
184
+ .trimEnd()
185
+ .concat("\n", `mode: ${mode}`, skills.length > 0
186
+ ? `\nskills:\n${skills.map((s) => ` - ${s}`).join("\n")}`
187
+ : "")
188
+ .concat("\n");
189
+ await writeFile(yamlPath2, yaml);
190
+ }
191
+ // Copia agent.md do modo se não existir
128
192
  const modeDoc = join(TEMPLATES_SRC, "modes", mode, "agent.md");
129
- if (existsSync(modeDoc)) {
193
+ if (existsSync(modeDoc) && !existsSync(join(dir, "agent.md"))) {
130
194
  cpSync(modeDoc, join(dir, "agent.md"));
131
195
  }
132
- const skills = (opts.skills ?? []).filter((s) => SKILLS.includes(s));
196
+ // Skills built-in
133
197
  for (const s of skills) {
134
- cpSync(join(TEMPLATES_SRC, "skills", s, "SKILL.md"), join(dir, "skills", s, "SKILL.md"), {
135
- recursive: true,
136
- });
198
+ try {
199
+ cpSync(join(TEMPLATES_SRC, "skills", s, "SKILL.md"), join(dir, "skills", s, "SKILL.md"), {
200
+ recursive: true,
201
+ });
202
+ }
203
+ catch {
204
+ // skill não existe — ignora
205
+ }
137
206
  }
138
- await writeFile(yamlPath, yaml
139
- .split("\n")
140
- .filter((line) => !/^(mode|skills):/m.test(line))
141
- .join("\n")
142
- .trimEnd()
143
- .concat("\n", `mode: ${mode}`, skills.length > 0
144
- ? `\nskills:\n${skills.map((s) => ` - ${s}`).join("\n")}`
145
- : "")
146
- .concat("\n"));
147
- // package.json do projeto (0.0.7): remove o passo manual de criar pacote.
148
- // `npm install` abaixo salva/adiciona a dep se preciso; aqui o pacote já existe.
149
- const pkgName = plain.toLowerCase().replace(/[^a-z0-9._-]/g, "-");
150
- await writeFile(join(dir, "package.json"), JSON.stringify({
151
- name: pkgName || "agent",
152
- version: "0.0.0",
153
- private: true,
154
- type: "module",
155
- dependencies: { "@autonsh/runtime": "^0.11.0" },
156
- }, null, 2) + "\n");
157
207
  ensureRuntimeLink(dir);
158
208
  if (!HAS_LOCAL_RUNTIME) {
159
209
  await installRuntime(dir);
160
210
  }
161
211
  if (isTTY)
162
212
  console.log(logo());
163
- console.log(ok(`${plain} (template: ${template}) em ${dim(dir)}`));
164
- console.log(info(`próximo: ${bold(`auton run "${plain}" <objetivo>`)}`));
213
+ console.log(ok(`${plain} (template: ${template.name}) em ${dim(dir)}`));
214
+ console.log(info(`próximo: ${bold(`cd ${plain} && auton run "<objetivo>"`)}`));
215
+ // Deploy opcional
216
+ if (opts.deploy) {
217
+ console.log(info(`deployando ${bold(plain)}...`));
218
+ await cmdDeploy(dir);
219
+ console.log(ok(`${plain} deployado`));
220
+ }
165
221
  }
166
222
  const VERSION = JSON.parse(readFileSync(resolve(SRC, "../package.json"), "utf8")).version;
167
223
  const API = process.env.AUTON_API ?? "http://localhost:8080";
224
+ let cpStarted = false;
225
+ /** G3: auto-start control plane se não estiver rodando (single-command UX). */
226
+ async function ensureCPRunning() {
227
+ if (cpStarted)
228
+ return;
229
+ try {
230
+ const res = await fetch(`${API}/v1/agents`, {
231
+ method: "GET",
232
+ headers: authHeaders(),
233
+ signal: AbortSignal.timeout(1000),
234
+ });
235
+ if (res.ok) {
236
+ cpStarted = true;
237
+ return;
238
+ }
239
+ }
240
+ catch {
241
+ // CP não responde — tenta iniciar
242
+ }
243
+ // Tenta achar binário do control-plane
244
+ const candidates = [
245
+ // monorepo dev
246
+ resolve(SRC, "../../control-plane/bin/control-plane"),
247
+ // npm install global (se publicado)
248
+ resolve(process.execPath, "../../lib/node_modules/@autonsh/server/bin/control-plane"),
249
+ // fallback PATH
250
+ "control-plane",
251
+ ];
252
+ let cpBin = null;
253
+ for (const c of candidates) {
254
+ try {
255
+ await import("node:fs/promises").then((fs) => fs.access(c, fs.constants.X_OK));
256
+ cpBin = c;
257
+ break;
258
+ }
259
+ catch {
260
+ // não existe ou não executável
261
+ }
262
+ }
263
+ if (!cpBin) {
264
+ console.log(dim("aviso: control-plane não encontrado — assumindo externo"));
265
+ return;
266
+ }
267
+ console.log(info(`iniciando control-plane (${cpBin})...`));
268
+ const child = spawn(cpBin, {
269
+ detached: true,
270
+ stdio: ["ignore", "ignore", "ignore"],
271
+ });
272
+ child.unref();
273
+ // Aguarda CP subir (max 5s)
274
+ for (let i = 0; i < 50; i++) {
275
+ await new Promise((r) => setTimeout(r, 100));
276
+ try {
277
+ const res = await fetch(`${API}/v1/agents`, {
278
+ headers: authHeaders(),
279
+ signal: AbortSignal.timeout(500),
280
+ });
281
+ if (res.ok) {
282
+ cpStarted = true;
283
+ console.log(ok("control-plane iniciado"));
284
+ return;
285
+ }
286
+ }
287
+ catch {
288
+ // ainda subindo
289
+ }
290
+ }
291
+ console.log(dim("aviso: control-plane pode não ter subido a tempo"));
292
+ }
168
293
  function authHeaders(extra) {
169
294
  const h = { ...extra };
170
295
  const token = process.env.AUTON_TOKEN;
@@ -192,51 +317,59 @@ Usage:
192
317
 
193
318
  Modelos:
194
319
  auton model Seletor interativo do modelo default
195
- (local ollama + providers configurados)
320
+ (local ollama + providers configurados)
196
321
  auton model ls [--json] Lista modelos locais + providers
197
322
  auton model set <spec> Define default (ex: ollama:qwen3)
198
323
  auton model test Testa latência dos providers
199
324
  auton login [provider] [--apikey X]
200
- Conecta provider (gemini/groq/ollama)
325
+ Conecta provider (gemini/groq/ollama)
201
326
  auton logout [id] Remove provider configurado
202
327
 
203
328
  Agentes:
204
329
  auton [nome] Sessão de trabalho (estilo opencode): dá
205
- tasks, acompanha o agente ao vivo, gates
206
- inline. Sem nome: cwd/picker.
330
+ tasks, acompanha o agente ao vivo, gates
331
+ inline. Sem nome: cwd/picker.
207
332
  auton chat [name] Alias da sessão de trabalho
208
333
  auton agent [dir] Deploy do agente no diretório
209
334
  auton ps Lista agentes + status
210
335
  auton logs [name] [--tail N] [--follow] [--since X] [--turn N]
211
- Stream de logs do agente (SSE)
336
+ Stream de logs do agente (SSE)
212
337
  auton status [name] Status do agente
213
338
  auton restart [name] Reinicia o agente
214
339
  auton invoke [name] [--stream] [-- prompt]
215
- Roda 1 turno (legado v0)
340
+ Roda 1 turno (legado v0)
216
341
  auton approve|reject [name] [-- razão]
217
- Decide o gate de aprovação
342
+ Decide o gate de aprovação
218
343
  auton answer [name] [-- texto] Responde pergunta pendente (ctx.ask)
219
344
  auton context [name] Work context: decisions/findings/artifacts
220
345
  auton undeploy [name] Remove o agente (--all remove tudo)
221
346
 
222
347
  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|...)
348
+ auton run [goal] [--model X] [--skip-verify] [--async]
349
+ Roda a task do diretório atual (--async = fire-and-forget)
350
+ auton wait <task-id> Aguarda task async terminar (poll logs)
351
+ auton result <task-id> Busca resultado final de task async
352
+ auton verify [dir] [--json] Roda gates mecânicos do verify
353
+ auton inspect [dir] [--json] Inspeciona task + evidência
354
+ auton continue [name] [goal] Retoma a última task (sem redeploy)
355
+ auton init [nome] [template] [--mode=X] [--skills=a,b] [--deploy] [--dry-run] [--genesis] [--var key=value]
356
+ Scaffold de task (templates built-in + custom)
357
+
358
+ Templates:
359
+ auton template list [--json] Lista templates (built-in + custom)
360
+ auton template create <id> Cria template custom (futuro)
229
361
 
230
362
  Infra:
231
363
  auton kv get|set|ls [name] [key] [value]
232
- Inspeciona o storage KV
364
+ Inspeciona o storage KV
233
365
  auton env set|get|ls [name] [key] [value]
234
- Env vars por agente
366
+ Env vars por agente
235
367
 
236
368
  Options:
237
369
  --help, -h Mostra ajuda
238
370
  --version, -v Mostra versão + modelo default
239
371
  AUTON_API=http://... Base do control plane (default localhost:8080)
372
+ --no-auto-cp Desliga auto-start do control plane
240
373
 
241
374
  Exit codes (0.14):
242
375
  0 ok · 1 erro genérico · 2 uso inválido · 3 verificação falhou
@@ -480,6 +613,153 @@ async function cmdRun(dir, goalArg, skipVerify, model) {
480
613
  const lines = await runWithGates(manifest.name, goal);
481
614
  await printTaskDone(manifest.name, goal, lines);
482
615
  }
616
+ /** G2: auton run --async — dispara task e retorna task-id imediatamente. */
617
+ async function cmdRunAsync(dir, goalArg, skipVerify, model) {
618
+ const manifest = await loadTaskManifest(dir);
619
+ if (!skipVerify)
620
+ await verifyPreFlight(dir, manifest);
621
+ let goal = goalArg ?? manifest.goal;
622
+ if (!goal) {
623
+ throw new Error(`task ${manifest.name} sem objetivo — passe "auton run \"<objetivo>\" --async" ou defina 'goal' no task.yaml`);
624
+ }
625
+ const defaultModel = getDefaultModel();
626
+ await precheckModel(model ?? manifest.models ?? process.env.AUTON_MODEL ?? defaultModel);
627
+ await cmdDeploy(dir, { ...credentialEnv(), ...(model ? { AUTON_MODEL: model } : {}) });
628
+ // Gera task-id único: agent-name + timestamp + random
629
+ const taskId = `${manifest.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
630
+ // Dispara invoke sem aguardar (fire-and-forget)
631
+ const body = { prompt: goal };
632
+ const res = await fetch(`${API}/v1/agents/${manifest.name}/invoke`, {
633
+ method: "POST",
634
+ headers: authHeaders({ "Content-Type": "application/json" }),
635
+ body: JSON.stringify(body),
636
+ });
637
+ if (!res.ok) {
638
+ throw new Error(`control plane ${res.status}: ${await res.text()}`);
639
+ }
640
+ // Salva mapping task-id -> agent name para wait/result
641
+ const taskMapKey = `task-map:${taskId}`;
642
+ await fetch(`${API}/v1/agents/${manifest.name}/kv/${taskMapKey}`, {
643
+ method: "PUT",
644
+ headers: authHeaders({ "Content-Type": "application/json" }),
645
+ body: JSON.stringify({ agent: manifest.name, goal, startedAt: new Date().toISOString() }),
646
+ });
647
+ console.log(ok(`task iniciada (async): ${bold(taskId)}`));
648
+ console.log(info(` agente: ${manifest.name}`));
649
+ console.log(info(` objetivo: ${goal}`));
650
+ console.log(info(` acompanhe: ${bold(`auton wait ${taskId}`)} | resultado: ${bold(`auton result ${taskId}`)}`));
651
+ }
652
+ /** G2: auton wait <task-id> — aguarda task terminar (poll logs até TASK:DONE/ERR). */
653
+ async function cmdWait(taskId) {
654
+ if (!taskId)
655
+ throw new ExitError(EXIT.USAGE, "uso: auton wait <task-id>");
656
+ // Busca agent name do task-id via KV de qualquer agente (precisa listar agentes)
657
+ const agentsRes = await fetch(`${API}/v1/agents`, { headers: authHeaders() });
658
+ if (!agentsRes.ok)
659
+ throw new Error(`control plane ${agentsRes.status}: ${await agentsRes.text()}`);
660
+ const agents = (await agentsRes.json());
661
+ let agentName;
662
+ let taskInfo;
663
+ for (const a of agents) {
664
+ const taskMapKey = `task-map:${taskId}`;
665
+ const res = await fetch(`${API}/v1/agents/${a.name}/kv/${taskMapKey}`, { headers: authHeaders() });
666
+ if (res.ok) {
667
+ taskInfo = (await res.json());
668
+ agentName = taskInfo.agent;
669
+ break;
670
+ }
671
+ }
672
+ if (!agentName || !taskInfo) {
673
+ throw new Error(`task-id ${taskId} não encontrado`);
674
+ }
675
+ console.log(info(`aguardando task ${bold(taskId)} (agente: ${agentName})...`));
676
+ const deadline = Date.now() + 30 * 60 * 1000; // 30 min max
677
+ let lastLineCount = 0;
678
+ while (Date.now() < deadline) {
679
+ await new Promise((r) => setTimeout(r, 2000));
680
+ const res = await fetch(`${API}/v1/agents/${agentName}/logs?tail=50`, {
681
+ headers: authHeaders({ Accept: "application/json" }),
682
+ });
683
+ if (!res.ok)
684
+ continue;
685
+ const entries = (await res.json());
686
+ const lines = entries.map((e) => e.line);
687
+ // Verifica se terminou
688
+ const doneLine = lines.find((l) => l.startsWith("[TASK:DONE]"));
689
+ const errLine = lines.find((l) => l.startsWith("[TASK:ERR]"));
690
+ if (doneLine || errLine) {
691
+ console.log(ok(`task ${bold(taskId)} concluída`));
692
+ if (doneLine) {
693
+ const ev = JSON.parse(doneLine.slice("[TASK:DONE]".length).trim());
694
+ if (ev.result)
695
+ console.log(dim(`resultado: ${ev.result}`));
696
+ }
697
+ else {
698
+ console.log(fail(`task falhou: ${errLine.slice("[TASK:ERR]".length).trim()}`));
699
+ process.exitCode = EXIT.ERR;
700
+ }
701
+ return;
702
+ }
703
+ // Progress: mostra novas linhas se houver
704
+ if (lines.length > lastLineCount) {
705
+ for (let i = lastLineCount; i < lines.length; i++) {
706
+ const line = lines[i];
707
+ if (line)
708
+ console.log(tagLine(line));
709
+ }
710
+ lastLineCount = lines.length;
711
+ }
712
+ }
713
+ console.log(info(`timeout aguardando task — veja ${bold(`auton logs ${agentName}`)} ou ${bold(`auton result ${taskId}`)}`));
714
+ process.exitCode = EXIT.TIMEOUT;
715
+ }
716
+ /** G2: auton result <task-id> — busca resultado final da task (sem aguardar). */
717
+ async function cmdResult(taskId) {
718
+ if (!taskId)
719
+ throw new ExitError(EXIT.USAGE, "uso: auton result <task-id>");
720
+ const agentsRes = await fetch(`${API}/v1/agents`, { headers: authHeaders() });
721
+ if (!agentsRes.ok)
722
+ throw new Error(`control plane ${agentsRes.status}: ${await agentsRes.text()}`);
723
+ const agents = (await agentsRes.json());
724
+ let agentName;
725
+ for (const a of agents) {
726
+ const taskMapKey = `task-map:${taskId}`;
727
+ const res = await fetch(`${API}/v1/agents/${a.name}/kv/${taskMapKey}`, { headers: authHeaders() });
728
+ if (res.ok) {
729
+ const info = (await res.json());
730
+ agentName = info.agent;
731
+ break;
732
+ }
733
+ }
734
+ if (!agentName) {
735
+ throw new Error(`task-id ${taskId} não encontrado`);
736
+ }
737
+ // Busca última evidência TASK:DONE nos logs
738
+ const res = await fetch(`${API}/v1/agents/${agentName}/logs?tail=100`, {
739
+ headers: authHeaders({ Accept: "application/json" }),
740
+ });
741
+ if (!res.ok)
742
+ throw new Error(`control plane ${res.status}: ${await res.text()}`);
743
+ const entries = (await res.json());
744
+ const lines = entries.map((e) => e.line);
745
+ const doneLine = [...lines].reverse().find((l) => l.startsWith("[TASK:DONE]"));
746
+ const errLine = [...lines].reverse().find((l) => l.startsWith("[TASK:ERR]"));
747
+ if (doneLine) {
748
+ const ev = JSON.parse(doneLine.slice("[TASK:DONE]".length).trim());
749
+ console.log(ok(`task ${bold(taskId)} concluída`));
750
+ console.log(`resultado: ${ev.result ?? "?"}`);
751
+ if (ev.evidence)
752
+ console.log(dim(`evidência: ${JSON.stringify(ev.evidence)}`));
753
+ return;
754
+ }
755
+ if (errLine) {
756
+ console.log(fail(`task ${bold(taskId)} falhou`));
757
+ console.log(`erro: ${errLine.slice("[TASK:ERR]".length).trim()}`);
758
+ process.exitCode = EXIT.ERR;
759
+ return;
760
+ }
761
+ console.log(info(`task ${bold(taskId)} ainda em andamento — use ${bold(`auton wait ${taskId}`)} para aguardar`));
762
+ }
483
763
  /** 0.16.13 — valida modelo no provider antes do turno (ollama /api/tags; openai-compat /models).
484
764
  * Falha dura só quando o provider respondeu e o modelo não existe; endpoint fora → aviso. */
485
765
  async function precheckModel(model) {
@@ -1072,35 +1352,108 @@ async function cmdKv(op, name, key, value) {
1072
1352
  throw new Error(`control plane ${res.status}: ${await res.text()}`);
1073
1353
  console.log(ok(`${key} = ${bold(value)}`));
1074
1354
  }
1075
- async function cmdLogin(args) {
1355
+ async function cmdLogin(realArgs) {
1076
1356
  // 0.15.1 — auton login [provider] [--apikey X] / auton logout
1077
- const keyIdx = args.indexOf("--apikey");
1078
- const apiKey = keyIdx !== -1 ? args[keyIdx + 1] : undefined;
1079
- const provider = args
1080
- .filter((a, i) => a !== "--apikey" && args[i - 1] !== "--apikey" && !a.startsWith("-"))
1357
+ const keyIdx = realArgs.indexOf("--apikey");
1358
+ const apiKey = keyIdx !== -1 ? realArgs[keyIdx + 1] : undefined;
1359
+ const provider = realArgs
1360
+ .filter((a, i) => a !== "--apikey" && realArgs[i - 1] !== "--apikey" && !a.startsWith("-"))
1081
1361
  .find(() => true);
1082
1362
  await loginCmd(provider, apiKey);
1083
1363
  }
1084
1364
  async function cmdLogout(providerId) {
1085
1365
  await logoutCmd(providerId);
1086
1366
  }
1367
+ /** Project mode: `auton` bare — se não há agente, cria + deploya + entra no chat. */
1368
+ async function cmdProject() {
1369
+ // 1. Tenta achar agente no cwd (task.yaml)
1370
+ let agentName;
1371
+ try {
1372
+ const { loadTaskManifest } = await import("./task.js");
1373
+ const manifest = await loadTaskManifest(process.cwd());
1374
+ if (manifest?.name)
1375
+ agentName = manifest.name;
1376
+ }
1377
+ catch {
1378
+ // sem task.yaml
1379
+ }
1380
+ // 2. Se não há no cwd, tenta deployed agents
1381
+ if (!agentName) {
1382
+ const deployed = await listAgentNames();
1383
+ if (deployed.length === 1) {
1384
+ agentName = deployed[0];
1385
+ }
1386
+ else if (deployed.length > 1) {
1387
+ // Múltiplos deployados — usa picker (cmdChat já faz isso)
1388
+ await cmdChat(undefined);
1389
+ return;
1390
+ }
1391
+ }
1392
+ // 3. Tem agente (cwd ou único deployado) → entra no chat
1393
+ if (agentName) {
1394
+ console.log(info(`entrando no agente ${bold(agentName)}...`));
1395
+ await cmdChat(agentName);
1396
+ return;
1397
+ }
1398
+ // 4. Nenhum agente — oferece init interativo
1399
+ console.log(box("auton — nenhum agente encontrado", [
1400
+ "Este diretório não tem task.yaml e não há agentes deployados.",
1401
+ "Quer criar um novo agente aqui?",
1402
+ ]));
1403
+ const { confirm } = await import("@clack/prompts");
1404
+ const create = await confirm({
1405
+ message: "criar novo agente neste diretório?",
1406
+ active: "sim",
1407
+ inactive: "não",
1408
+ });
1409
+ if (!create) {
1410
+ console.log(dim("até logo."));
1411
+ return;
1412
+ }
1413
+ // Init interativo
1414
+ const { promptInit } = await import("./prompts.js");
1415
+ const p = await promptInit();
1416
+ // Deploy automático
1417
+ console.log(info(`criando e deployando ${bold(p.name)}...`));
1418
+ await cmdInit(p.name, p.templateId, {
1419
+ mode: p.mode,
1420
+ skills: p.skills,
1421
+ genesis: p.genesis,
1422
+ deploy: true,
1423
+ dryRun: false,
1424
+ variables: p.variables,
1425
+ });
1426
+ // Entra no chat
1427
+ console.log(info(`entrando no agente ${bold(p.name)}...`));
1428
+ await cmdChat(p.name);
1429
+ }
1087
1430
  async function main() {
1088
- const args = process.argv.slice(2);
1089
- if (args.length === 0) {
1431
+ let realArgs = process.argv.slice(2);
1432
+ // G3: auto-start CP para comandos que precisam dele (--no-auto-cp desliga)
1433
+ const noAutoCP = realArgs.includes("--no-auto-cp");
1434
+ const noCP = new Set(["login", "logout", "model", "--help", "-h", "--version", "-v", "init"]);
1435
+ const cmd = realArgs[0];
1436
+ if (!noAutoCP && cmd && !noCP.has(cmd)) {
1437
+ await ensureCPRunning();
1438
+ }
1439
+ // Remove --no-auto-cp dos realArgs antes do switch
1440
+ realArgs = realArgs.filter((a) => a !== "--no-auto-cp");
1441
+ if (realArgs.length === 0) {
1090
1442
  // 0.17.0 — `auton` bare: abre sessão de trabalho (estilo opencode):
1091
1443
  // cwd é agente → sessão nele; senão picker. Pipe: mosta ajuda.
1444
+ // NOVO: project mode — se não há agente, oferece init + deploy + chat
1092
1445
  if (isTTY) {
1093
- await cmdChat(undefined);
1446
+ await cmdProject();
1094
1447
  return;
1095
1448
  }
1096
1449
  console.log(USAGE);
1097
1450
  return;
1098
1451
  }
1099
- if (args[0] === "--help" || args[0] === "-h") {
1452
+ if (realArgs[0] === "--help" || realArgs[0] === "-h") {
1100
1453
  console.log(USAGE);
1101
1454
  return;
1102
1455
  }
1103
- if (args[0] === "--version" || args[0] === "-v") {
1456
+ if (realArgs[0] === "--version" || realArgs[0] === "-v") {
1104
1457
  if (isTTY)
1105
1458
  console.log(logo());
1106
1459
  console.log(bold(`auton v${VERSION}`));
@@ -1116,33 +1469,34 @@ async function main() {
1116
1469
  const KNOWN = new Set([
1117
1470
  "login", "logout", "model", "agent", "logs", "status", "restart", "run",
1118
1471
  "verify", "inspect", "continue", "invoke", "undeploy", "approve", "reject",
1119
- "ps", "context", "env", "init", "kv", "chat", "answer",
1472
+ "ps", "context", "env", "init", "kv", "chat", "answer", "wait", "result",
1473
+ "template",
1120
1474
  ]);
1121
- if (!KNOWN.has(args[0]) && isTTY) {
1475
+ if (!KNOWN.has(realArgs[0]) && isTTY) {
1122
1476
  const deployed = await listAgentNames();
1123
- if (deployed.includes(args[0])) {
1124
- await cmdChat(args[0]);
1477
+ if (deployed.includes(realArgs[0])) {
1478
+ await cmdChat(realArgs[0]);
1125
1479
  return;
1126
1480
  }
1127
1481
  }
1128
- switch (args[0]) {
1482
+ switch (realArgs[0]) {
1129
1483
  case "login":
1130
- await cmdLogin(args.slice(1));
1484
+ await cmdLogin(realArgs.slice(1));
1131
1485
  break;
1132
1486
  case "logout":
1133
- await cmdLogout(args[1]);
1487
+ await cmdLogout(realArgs[1]);
1134
1488
  break;
1135
1489
  case "model": {
1136
1490
  // auton model [ls|set <spec>|test] — sem sub: seletor TTY
1137
- const sub = args[1];
1138
- const json = args.includes("--json");
1491
+ const sub = realArgs[1];
1492
+ const json = realArgs.includes("--json");
1139
1493
  if (sub === "ls") {
1140
1494
  await modelLs(json);
1141
1495
  }
1142
1496
  else if (sub === "set") {
1143
- if (!args[2])
1497
+ if (!realArgs[2])
1144
1498
  throw new ExitError(EXIT.USAGE, "uso: auton model set <provider:modelo>");
1145
- await modelSet(args[2]);
1499
+ await modelSet(realArgs[2]);
1146
1500
  }
1147
1501
  else if (sub === "test") {
1148
1502
  await modelTest();
@@ -1156,8 +1510,8 @@ async function main() {
1156
1510
  break;
1157
1511
  }
1158
1512
  case "agent":
1159
- if (args[1]) {
1160
- const dir = resolve(args[1]);
1513
+ if (realArgs[1]) {
1514
+ const dir = resolve(realArgs[1]);
1161
1515
  ensureRuntimeLink(dir);
1162
1516
  await cmdDeploy(dir);
1163
1517
  }
@@ -1167,8 +1521,8 @@ async function main() {
1167
1521
  }
1168
1522
  break;
1169
1523
  case "logs":
1170
- // args[1..]: [name] [--tail N] [--since RFC3339] [--follow] [--turn N]
1171
- const rest = args.slice(1);
1524
+ // realArgs[1..]: [name] [--tail N] [--since RFC3339] [--follow] [--turn N]
1525
+ const rest = realArgs.slice(1);
1172
1526
  const tailIdx = rest.indexOf("--tail");
1173
1527
  const sinceIdx = rest.indexOf("--since");
1174
1528
  let name;
@@ -1200,34 +1554,90 @@ async function main() {
1200
1554
  await cmdLogs(name, tail, since, follow, turn);
1201
1555
  break;
1202
1556
  case "status":
1203
- await cmdStatus(args[1]);
1557
+ await cmdStatus(realArgs[1]);
1204
1558
  break;
1205
1559
  case "restart":
1206
- await cmdRestart(args[1]);
1560
+ await cmdRestart(realArgs[1]);
1207
1561
  break;
1208
1562
  case "run": {
1209
- // auton run "<goal>" [--skip-verify] [--model X] (ou auton run goal do task.yaml)
1210
- const skipVerify = args.includes("--skip-verify");
1211
- const modelIdx = args.indexOf("--model");
1212
- const model = modelIdx !== -1 ? args[modelIdx + 1] : undefined;
1213
- const goal = args
1214
- .slice(1)
1215
- .filter((a, i) => a !== "--skip-verify" && a !== "--model" && args[i - 1] !== "--model")
1216
- .join(" ")
1217
- .trim() || undefined;
1218
- ensureRuntimeLink(process.cwd());
1219
- await cmdRun(process.cwd(), goal, skipVerify, model);
1563
+ // auton run "<goal>" [--skip-verify] [--model X] [--async] [--dir DIR] [-d DIR] (ou auton run <agent-name> "goal")
1564
+ const skipVerify = realArgs.includes("--skip-verify");
1565
+ const isAsync = realArgs.includes("--async");
1566
+ const modelIdx = realArgs.indexOf("--model");
1567
+ const model = modelIdx !== -1 ? realArgs[modelIdx + 1] : undefined;
1568
+ // --dir / -d
1569
+ const dirIdx = realArgs.indexOf("--dir");
1570
+ const dirShortIdx = realArgs.indexOf("-d");
1571
+ const explicitDir = (dirIdx !== -1 ? realArgs[dirIdx + 1] : undefined) ??
1572
+ (dirShortIdx !== -1 ? realArgs[dirShortIdx + 1] : undefined);
1573
+ // resolve dir: --dir > agent name via CP > cwd
1574
+ let targetDir = explicitDir ? resolve(explicitDir) : process.cwd();
1575
+ let usedAgentName = false;
1576
+ let agentNameSliceIdx = -1; // index in realArgs.slice(1) of the agent name
1577
+ if (!explicitDir) {
1578
+ // Find first positional arg (not a flag) after "run"
1579
+ let firstPosArg;
1580
+ let firstPosArgIdx = -1;
1581
+ for (let i = 1; i < realArgs.length; i++) {
1582
+ const arg = realArgs[i];
1583
+ if (arg && !arg.startsWith("-")) {
1584
+ firstPosArg = arg;
1585
+ firstPosArgIdx = i;
1586
+ break;
1587
+ }
1588
+ }
1589
+ if (firstPosArg) {
1590
+ const knownCmds = new Set([
1591
+ "login", "logout", "model", "agent", "logs", "status", "restart", "run",
1592
+ "verify", "inspect", "continue", "invoke", "undeploy", "approve", "reject",
1593
+ "ps", "context", "env", "init", "kv", "chat", "answer", "wait", "result",
1594
+ ]);
1595
+ if (!knownCmds.has(firstPosArg)) {
1596
+ try {
1597
+ const agent = await resolveAgent("run", firstPosArg);
1598
+ const statusRes = await fetch(`${API}/v1/agents/${agent}`, { headers: authHeaders() });
1599
+ if (statusRes.ok) {
1600
+ const status = (await statusRes.json());
1601
+ if (status.dir) {
1602
+ targetDir = resolve(status.dir);
1603
+ usedAgentName = true;
1604
+ agentNameSliceIdx = firstPosArgIdx - 1; // index in realArgs.slice(1)
1605
+ }
1606
+ }
1607
+ }
1608
+ catch { /* ignora, usa cwd */ }
1609
+ }
1610
+ }
1611
+ }
1612
+ // build goal: exclude flags + agent name if used for resolution
1613
+ const goal = realArgs.slice(1).filter((a, i) => {
1614
+ const origIdx = i + 1; // index in realArgs
1615
+ if (a === "--skip-verify" || a === "--model" || realArgs[origIdx - 1] === "--model" ||
1616
+ a === "--async" || a === "--dir" || a === "-d" ||
1617
+ realArgs[origIdx - 1] === "--dir" || realArgs[origIdx - 1] === "-d")
1618
+ return false;
1619
+ if (usedAgentName && i === agentNameSliceIdx)
1620
+ return false; // exclude agent name from goal
1621
+ return true;
1622
+ }).join(" ").trim() || undefined;
1623
+ ensureRuntimeLink(targetDir);
1624
+ if (isAsync) {
1625
+ await cmdRunAsync(targetDir, goal, skipVerify, model);
1626
+ }
1627
+ else {
1628
+ await cmdRun(targetDir, goal, skipVerify, model);
1629
+ }
1220
1630
  break;
1221
1631
  }
1222
1632
  case "verify": {
1223
- const rest = args.slice(1);
1633
+ const rest = realArgs.slice(1);
1224
1634
  const json = rest.includes("--json");
1225
1635
  const dirArg = rest.find((a) => !a.startsWith("-"));
1226
1636
  await cmdVerify(resolve(dirArg ?? process.cwd()), json);
1227
1637
  break;
1228
1638
  }
1229
1639
  case "inspect": {
1230
- const rest = args.slice(1);
1640
+ const rest = realArgs.slice(1);
1231
1641
  const json = rest.includes("--json");
1232
1642
  const dirArg = rest.find((a) => !a.startsWith("-"));
1233
1643
  await cmdInspect(resolve(dirArg ?? process.cwd()), json);
@@ -1235,29 +1645,41 @@ async function main() {
1235
1645
  }
1236
1646
  case "continue": {
1237
1647
  // auton continue [name] [goal...]
1238
- const goal = args.slice(2).filter((a) => !a.startsWith("-")).join(" ").trim() || undefined;
1239
- const nameArg = args[1] && !args[1].startsWith("-") ? args[1] : undefined;
1648
+ const goal = realArgs.slice(2).filter((a) => !a.startsWith("-")).join(" ").trim() || undefined;
1649
+ const nameArg = realArgs[1] && !realArgs[1].startsWith("-") ? realArgs[1] : undefined;
1240
1650
  ensureRuntimeLink(process.cwd());
1241
1651
  await cmdContinue(process.cwd(), nameArg, goal);
1242
1652
  break;
1243
1653
  }
1244
1654
  case "invoke": {
1245
1655
  // deploy invoke [name] [-- prompt] [--stream]
1246
- const rest = args.slice(2);
1656
+ const rest = realArgs.slice(2);
1247
1657
  const stream = rest.includes("--stream");
1248
1658
  const promptIdx = rest.indexOf("--");
1249
1659
  const prompt = promptIdx !== -1 ? rest.slice(promptIdx + 1).join(" ") : undefined;
1250
- await cmdInvoke(args[1], prompt, stream);
1660
+ await cmdInvoke(realArgs[1], prompt, stream);
1251
1661
  break;
1252
1662
  }
1253
1663
  case "undeploy":
1254
- await cmdUndeploy(args[1]);
1664
+ await cmdUndeploy(realArgs[1]);
1665
+ break;
1666
+ case "wait":
1667
+ // auton wait <task-id> [--timeout Ms]
1668
+ if (!realArgs[1])
1669
+ throw new ExitError(EXIT.USAGE, "uso: auton wait <task-id>");
1670
+ await cmdWait(realArgs[1]);
1671
+ break;
1672
+ case "result":
1673
+ // auton result <task-id>
1674
+ if (!realArgs[1])
1675
+ throw new ExitError(EXIT.USAGE, "uso: auton result <task-id>");
1676
+ await cmdResult(realArgs[1]);
1255
1677
  break;
1256
1678
  case "approve":
1257
1679
  case "reject": {
1258
- const approved = args[0] === "approve";
1680
+ const approved = realArgs[0] === "approve";
1259
1681
  // auton approve [name] [-- razão]: -- na posição [1] = sem name
1260
- const rest = args.slice(1);
1682
+ const rest = realArgs.slice(1);
1261
1683
  const dash = rest.indexOf("--");
1262
1684
  const name = dash === -1 ? rest[0] : dash === 0 ? undefined : rest[0];
1263
1685
  const reason = dash !== -1 ? rest.slice(dash + 1).join(" ") : undefined;
@@ -1265,10 +1687,10 @@ async function main() {
1265
1687
  break;
1266
1688
  }
1267
1689
  case "chat":
1268
- await cmdChat(args[1]);
1690
+ await cmdChat(realArgs[1]);
1269
1691
  break;
1270
1692
  case "answer": {
1271
- const rest = args.slice(1);
1693
+ const rest = realArgs.slice(1);
1272
1694
  const dash = rest.indexOf("--");
1273
1695
  const name = dash === -1 ? rest[0] : dash === 0 ? undefined : rest[0];
1274
1696
  const answer = dash !== -1 ? rest.slice(dash + 1).join(" ") : undefined;
@@ -1276,21 +1698,21 @@ async function main() {
1276
1698
  break;
1277
1699
  }
1278
1700
  case "ps":
1279
- await cmdPs(args[1] === "--json");
1701
+ await cmdPs(realArgs[1] === "--json");
1280
1702
  break;
1281
1703
  case "context":
1282
- await cmdContext(args[1]);
1704
+ await cmdContext(realArgs[1]);
1283
1705
  break;
1284
1706
  case "env": {
1285
- const sub = args[1];
1707
+ const sub = realArgs[1];
1286
1708
  if (sub === "set") {
1287
- await cmdEnvSet(args[2], args[3], args[4]);
1709
+ await cmdEnvSet(realArgs[2], realArgs[3], realArgs[4]);
1288
1710
  }
1289
1711
  else if (sub === "get") {
1290
- await cmdEnvGet(args[2], args[3]);
1712
+ await cmdEnvGet(realArgs[2], realArgs[3]);
1291
1713
  }
1292
1714
  else if (sub === "ls") {
1293
- await cmdEnvLs(args[2]);
1715
+ await cmdEnvLs(realArgs[2]);
1294
1716
  }
1295
1717
  else {
1296
1718
  console.log(USAGE);
@@ -1298,36 +1720,97 @@ async function main() {
1298
1720
  break;
1299
1721
  }
1300
1722
  case "init": {
1301
- // deploy init [nome] [template] [--mode=X] [--skills=a,b] sem argumentos (TTY): pergunta interativo
1302
- const initFlags = args.filter((a) => a.startsWith("--"));
1303
- const initPos = args.filter((a) => !a.startsWith("--"));
1304
- const initOpts = {
1305
- mode: initFlags.find((f) => f.startsWith("--mode="))?.slice(7),
1306
- skills: initFlags
1307
- .find((f) => f.startsWith("--skills="))
1308
- ?.slice(9)
1309
- .split(",")
1310
- .filter(Boolean),
1311
- };
1723
+ // auton init [nome] [template] [--mode=X] [--skills=a,b] [--deploy] [--dry-run] [--genesis] [--var key=value]
1724
+ const initFlags = realArgs.filter((a) => a.startsWith("--"));
1725
+ const initPos = realArgs.filter((a) => !a.startsWith("--"));
1726
+ // Parse flags
1727
+ const deploy = initFlags.includes("--deploy");
1728
+ const dryRun = initFlags.includes("--dry-run");
1729
+ const mode = initFlags.find((f) => f.startsWith("--mode="))?.slice(7);
1730
+ const skills = initFlags
1731
+ .find((f) => f.startsWith("--skills="))
1732
+ ?.slice(9)
1733
+ .split(",")
1734
+ .filter(Boolean);
1735
+ const genesis = initFlags.find((f) => f.startsWith("--genesis="))?.slice(10);
1736
+ // Parse --var key=value
1737
+ const variables = {};
1738
+ for (const f of initFlags) {
1739
+ if (f.startsWith("--var=")) {
1740
+ const rest = f.slice(6); // remove "--var="
1741
+ const eqIdx = rest.indexOf("=");
1742
+ if (eqIdx > 0) {
1743
+ const key = rest.slice(0, eqIdx);
1744
+ const val = rest.slice(eqIdx + 1);
1745
+ variables[key] = val === "true" ? true : val === "false" ? false : val;
1746
+ }
1747
+ }
1748
+ }
1312
1749
  if (isTTY && !initPos[1] && !initPos[2]) {
1313
1750
  const p = await promptInit();
1314
- await cmdInit(p.name, p.template, initOpts);
1751
+ await cmdInit(p.name, p.templateId, {
1752
+ mode: p.mode ?? mode,
1753
+ skills: p.skills ?? skills,
1754
+ genesis: p.genesis ?? genesis,
1755
+ deploy,
1756
+ dryRun,
1757
+ variables,
1758
+ });
1315
1759
  }
1316
1760
  else {
1317
- const tpl = (initPos[2] ?? "support");
1318
- if (!TEMPLATES.includes(tpl)) {
1319
- throw new ExitError(EXIT.USAGE, `template desconhecido: ${tpl} (disponíveis: ${TEMPLATES.join(", ")})`);
1761
+ const name = initPos[1] ?? ".";
1762
+ const templateId = initPos[2];
1763
+ if (!templateId) {
1764
+ throw new ExitError(EXIT.USAGE, "uso: auton init <nome> <template-id> [--deploy] [--dry-run] ...");
1765
+ }
1766
+ // Valida template existe
1767
+ const registry = await getTemplateRegistry();
1768
+ const tpl = await registry.get(templateId);
1769
+ if (!tpl) {
1770
+ const list = await listTemplatesForDisplay();
1771
+ throw new ExitError(EXIT.USAGE, `template desconhecido: ${templateId}\nDisponíveis: ${list.map(t => `${t.id} (${t.category})`).join(", ")}`);
1772
+ }
1773
+ await cmdInit(name, templateId, {
1774
+ mode,
1775
+ skills,
1776
+ genesis,
1777
+ deploy,
1778
+ dryRun,
1779
+ variables,
1780
+ });
1781
+ }
1782
+ break;
1783
+ }
1784
+ case "template": {
1785
+ const sub = realArgs[1];
1786
+ if (sub === "list" || sub === "ls" || !sub) {
1787
+ const list = await listTemplatesForDisplay();
1788
+ if (realArgs.includes("--json")) {
1789
+ console.log(JSON.stringify(list, null, 2));
1790
+ }
1791
+ else {
1792
+ console.log(bold("Templates disponíveis:"));
1793
+ for (const t of list) {
1794
+ const srcTag = t.source === "custom" ? pc.cyan("(custom)") : pc.dim("(builtin)");
1795
+ console.log(` ${bold(t.id)} ${srcTag} — ${t.name} [${t.category}]`);
1796
+ console.log(` ${dim(t.description)}`);
1797
+ }
1320
1798
  }
1321
- await cmdInit(initPos[1] ?? ".", tpl, initOpts);
1799
+ }
1800
+ else if (sub === "create") {
1801
+ console.log(info("Use 'auton init' com template custom — criação de template virá em versão futura"));
1802
+ }
1803
+ else {
1804
+ console.log(USAGE);
1322
1805
  }
1323
1806
  break;
1324
1807
  }
1325
1808
  case "kv":
1326
1809
  // deploy kv get|set|ls <nome> [chave] [valor]
1327
- await cmdKv(args[1], args[2], args[3], args[4]);
1810
+ await cmdKv(realArgs[1], realArgs[2], realArgs[3], realArgs[4]);
1328
1811
  break;
1329
1812
  default: {
1330
- console.error(fail(`comando desconhecido: ${args[0]}`));
1813
+ console.error(fail(`comando desconhecido: ${realArgs[0]}`));
1331
1814
  console.error(USAGE);
1332
1815
  process.exit(EXIT.USAGE);
1333
1816
  }