Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ af validate # check the file and report which env vars it references
af run # run it in Docker; a REPL without triggers, a service with them
```

`af up -d` starts one or more agents in the background, `af ps` lists them and `af down` stops them. The full command reference lives at [docs.looped.sh/agent-framework/cli](https://docs.looped.sh/agent-framework/cli).
`af up -d` starts one or more agents in the background, `af ps` lists them and `af down` stops them. Run `af update` to reinstall the CLI at the latest published version. The full command reference lives at [docs.looped.sh/agent-framework/cli](https://docs.looped.sh/agent-framework/cli).
10 changes: 3 additions & 7 deletions packages/cli/banner.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// The first-boot banner: shown once per agent, on the run where it names
// itself. A little something for the devs.

import { LOOP_GRADIENT } from "./style.ts";
import { ACCENT, LOOP_GRADIENT } from "./style.ts";

const ART = String.raw`
██╗ ██████╗ ██████╗ ██████╗ ███████╗██████╗ █████╗ ███████╗
Expand All @@ -17,16 +17,12 @@ export function printBirthBanner(handle: string, name: string) {
for (let i = 0; i < lines.length; i++) {
console.log(`%c${lines[i]}`, `color: ${LOOP_GRADIENT[i % LOOP_GRADIENT.length]}`);
}
console.log(
`\n %cone job · one file · it’s hired%c looped.sh\n`,
"color: gray; font-style: italic",
"color: gray",
);
console.log();
console.log(
` ✦ first boot — %c${handle}%c has chosen a name: %c${name}%c\n`,
"color: gray",
"",
"color: #22d3ee; font-weight: bold",
`color: ${ACCENT}; font-weight: bold`,
"",
);
}
6 changes: 6 additions & 0 deletions packages/cli/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* af up <agent.yaml...> start agents in Docker (foreground; -d to detach)
* af down / af ps stop / list af containers
* af validate <agent.yaml> validate a config and report env references
* af update reinstall af at the latest published version
* ```
*
* Nothing executes on the host: run/up start the published container image.
Expand Down Expand Up @@ -36,6 +37,7 @@ import {
import { init } from "./init_command.ts";
import { runLocal, validate } from "./local.ts";
import { accent, dim, table } from "./style.ts";
import { update } from "./update_command.ts";

// Set by the image: `run` executes in-process (the container entrypoint)
// instead of spawning docker inside docker.
Expand All @@ -52,6 +54,7 @@ function usage(): string {
["af flags [agent.yaml]", "Print compiled Deno permission flags"],
["af schema", "Print the agent.yaml JSON Schema"],
["af discord-invite [agent.yaml]", "Print the bot's OAuth invite URL (no bitfield math)"],
["af update", "Reinstall af at the latest published version"],
];
const flags: [string, string][] = [
["-d, --detach", "up: leave agents running in the background (restart unless-stopped)"],
Expand Down Expand Up @@ -113,6 +116,9 @@ async function main() {
if (!arg) fail("usage: af discord-invite [agent.yaml]");
await discordInvite(arg);
break;
case "update":
await update();
break;
default:
console.log(usage());
Deno.exit(command && command !== "--help" && command !== "-h" ? 1 : 0);
Expand Down
8 changes: 4 additions & 4 deletions packages/cli/style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
/** The Looped loop: cyan → violet. The banner paints with it; log prefixes cycle it. */
export const LOOP_GRADIENT = ["#22d3ee", "#38bdf8", "#60a5fa", "#818cf8", "#a78bfa", "#c084fc"];

export const ACCENT = LOOP_GRADIENT[0]; // Looped cyan
const OK = "#34d399";
const WARN = "#fbbf24";
const ERR = "#f87171";
export const ACCENT = "#685EF6"; // Looped primary purple
const OK = "#37946A";
const WARN = "#ED9B00";
const ERR = "#D02E1F";

function detectColor(): boolean {
try {
Expand Down
45 changes: 45 additions & 0 deletions packages/cli/update_command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// `af update` — reinstall the CLI from JSR at its latest published version.
// Mirrors the install command from the README, with -f to overwrite the
// existing binary and @latest to bypass any cached resolution.

import { fail } from "./docker_commands.ts";
import { ok, Spinner } from "./style.ts";

const INSTALL_ARGS = [
"install",
"-g",
"-f",
"--allow-read",
"--allow-write",
"--allow-env",
"--allow-net",
"--allow-run=bash,docker",
"-n",
"af",
"jsr:@looped/af@latest",
];

export async function update() {
const spinner = new Spinner();
spinner.start("updating af...");
try {
const child = new Deno.Command("deno", {
args: INSTALL_ARGS,
stdout: "piped",
stderr: "piped",
stdin: "null",
});
const out = await child.output();
if (out.code !== 0) {
spinner.stop();
fail(new TextDecoder().decode(out.stderr).trim() || "deno install failed");
}
spinner.stop(`${ok("✓")} af is up to date`);
} catch (e) {
spinner.stop();
if (e instanceof Deno.errors.NotFound) {
fail("deno not found — af update reinstalls via `deno install`");
}
throw e;
}
}
Loading