Skip to content
Open
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
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,26 @@ UnforcedAGI is a pattern for running a single Claude Code session as your person

A user's personal instance lives in `~/UnforcedAGI/` (a separate private repo) and uses the tooling from this package.

## Install (future)
## Install

```bash
bun install -g github:Unforced-Dev/unforced-agi
```

## Usage

```bash
unforced-agi launch # start (or attach to) your UnforcedAGI session in tmux
unforced-agi help # show help
unforced-agi version # show version
```

`launch` creates a tmux session named `unforcedagi` rooted at `~/UnforcedAGI`,
runs `claude` inside it, and attaches your terminal. Detach any time with
`Ctrl-b d` — the session keeps running in the background. Run `launch` again
to reattach. Requires `tmux` and `claude` on your `PATH`, and `~/UnforcedAGI`
to exist.

## Roadmap

- [ ] `unforced-agi init` — scaffold a new `~/UnforcedAGI/` directory for a user
Expand Down
93 changes: 85 additions & 8 deletions bin/unforced-agi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,99 @@
// unforced-agi — CLI for the UnforcedAGI personal agent framework
// https://github.com/Unforced-Dev/unforced-agi

const VERSION = "0.0.1";
import { existsSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";

function main() {
const args = process.argv.slice(2);
const command = args[0];
const VERSION = "0.0.2";

if (!command || command === "--help" || command === "-h") {
console.log(`unforced-agi ${VERSION}
const SESSION_NAME = "unforcedagi";
const AGENT_HOME = join(homedir(), "UnforcedAGI");

function help() {
console.log(`unforced-agi ${VERSION}

A framework for running a single Claude Code session as your personal
primary agent — coordinating work across projects via Agent Teams,
subagents, and knowledge graph memory.

Commands:
launch Start (or attach to) your UnforcedAGI session in tmux
version Show version
help Show this help

(More commands will be added as needed. Currently a placeholder scaffold.)

Learn more: https://github.com/Unforced-Dev/unforced-agi
`);
}

function which(cmd: string): boolean {
const r = Bun.spawnSync(["which", cmd], { stdout: "ignore", stderr: "ignore" });
return r.exitCode === 0;
}

function tmuxHasSession(name: string): boolean {
const r = Bun.spawnSync(["tmux", "has-session", "-t", name], {
stdout: "ignore",
stderr: "ignore",
});
return r.exitCode === 0;
}

async function launch() {
if (!which("tmux")) {
console.error("error: tmux is not installed or not on PATH.");
console.error("Install it (e.g. `brew install tmux`) and try again.");
process.exit(1);
}
if (!which("claude")) {
console.error("error: `claude` (Claude Code CLI) is not on PATH.");
console.error("Install Claude Code first: https://docs.claude.com/claude-code");
process.exit(1);
}
if (!existsSync(AGENT_HOME)) {
console.error(`error: ${AGENT_HOME} does not exist.`);
console.error("UnforcedAGI expects its home directory at ~/UnforcedAGI.");
console.error("Create it (and a CLAUDE.md inside) before launching.");
process.exit(1);
}

const alreadyRunning = tmuxHasSession(SESSION_NAME);
if (!alreadyRunning) {
const create = Bun.spawnSync(
["tmux", "new-session", "-d", "-s", SESSION_NAME, "-c", AGENT_HOME, "claude"],
{ stdout: "inherit", stderr: "inherit" },
);
if (create.exitCode !== 0) {
console.error("error: failed to create tmux session.");
process.exit(create.exitCode ?? 1);
}
console.log(`started UnforcedAGI in tmux session "${SESSION_NAME}" (cwd: ${AGENT_HOME})`);
} else {
console.log(`attaching to existing tmux session "${SESSION_NAME}"`);
}

// Attach (or switch-client if we're already inside tmux). When the user
// detaches with Ctrl-b d, the session keeps running in the background.
const insideTmux = !!process.env.TMUX;
const attachCmd = insideTmux
? ["tmux", "switch-client", "-t", SESSION_NAME]
: ["tmux", "attach-session", "-t", SESSION_NAME];

const attach = Bun.spawn(attachCmd, {
stdin: "inherit",
stdout: "inherit",
stderr: "inherit",
});
const code = await attach.exited;
process.exit(code);
}

async function main() {
const args = process.argv.slice(2);
const command = args[0];

if (!command || command === "help" || command === "--help" || command === "-h") {
help();
return;
}

Expand All @@ -31,6 +103,11 @@ Learn more: https://github.com/Unforced-Dev/unforced-agi
return;
}

if (command === "launch") {
await launch();
return;
}

console.error(`Unknown command: ${command}`);
console.error(`Run 'unforced-agi help' for usage.`);
process.exit(1);
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "unforced-agi",
"version": "0.0.1",
"version": "0.0.2",
"description": "CLI tooling for the UnforcedAGI personal agent framework",
"license": "MIT",
"type": "module",
Expand Down