@agenttower/cli 0.1.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/README.md ADDED
@@ -0,0 +1,36 @@
1
+ # @agenttower/cli — `atc`
2
+
3
+ The Agent Tower CLI. Coordinate multiple coding agents working one repo: claim
4
+ scope before editing, get inline conflict verdicts, and share a decision log.
5
+
6
+ ## Install
7
+
8
+ ```bash
9
+ npm i -g @agenttower/cli
10
+ ```
11
+
12
+ ## Configure
13
+
14
+ Get a **frequency token** from the dashboard (https://app.agenttower.dev → your
15
+ project → Mint token), then:
16
+
17
+ ```bash
18
+ export ATC_TOKEN=atcf_… # required
19
+ # export ATC_API=… # optional; defaults to the hosted prod API
20
+ ```
21
+
22
+ ## Use
23
+
24
+ ```bash
25
+ atc checkin --task "refactor auth" # contact the Tower, get your callsign + a brief
26
+ atc brief # roster + claims + conflicts touching you + NOTAMs
27
+ atc claim "src/auth/**" # request scope; exit code 3 if it conflicts
28
+ atc squawk "rewriting User model" # status + heartbeat
29
+ atc note "auth uses JWT, 15min TTL" # write to the decision log (--pin to pin)
30
+ atc clear ["src/auth/**"] # release scope (all, or one glob)
31
+ atc checkout # leave; releases your claims
32
+ ```
33
+
34
+ Session state lives in `.atc/` in the worktree (gitignore it). Add `--json` to any
35
+ command for machine-readable output (useful in hooks). See the project docs for the
36
+ full protocol.
package/bin/atc.js ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ // Thin launcher → compiled CLI. Build with `npm run build` (tsc → dist/).
3
+ require('../dist/index.js');
package/dist/client.js ADDED
@@ -0,0 +1,125 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.loadSession = exports.saveSession = exports.DEFAULT_API = void 0;
37
+ exports.loadConfig = loadConfig;
38
+ exports.workspaceId = workspaceId;
39
+ exports.clearSession = clearSession;
40
+ exports.api = api;
41
+ exports.currentBranch = currentBranch;
42
+ const fs = __importStar(require("node:fs"));
43
+ const path = __importStar(require("node:path"));
44
+ const node_crypto_1 = require("node:crypto");
45
+ /**
46
+ * Thin client for the Agent Tower /v1 API + local session state.
47
+ * Config is env-only (ATC_API / ATC_TOKEN). `.atc/` holds:
48
+ * workspace.json { workspaceId } — persistent takeover identity (ADR-0003)
49
+ * session.json { callsign, sessionToken, api } — lease-bound; cleared on checkout
50
+ * Both live in the worktree and must be gitignored.
51
+ */
52
+ const ATC_DIR = path.join(process.cwd(), '.atc');
53
+ const WORKSPACE_FILE = path.join(ATC_DIR, 'workspace.json');
54
+ const SESSION_FILE = path.join(ATC_DIR, 'session.json');
55
+ /** Prod Tower API. Override with ATC_API (e.g. the dev stack). */
56
+ exports.DEFAULT_API = 'https://mz7tt5ee71.execute-api.us-east-1.amazonaws.com';
57
+ function loadConfig() {
58
+ return {
59
+ api: (process.env.ATC_API || exports.DEFAULT_API).replace(/\/$/, ''),
60
+ token: process.env.ATC_TOKEN ?? '',
61
+ };
62
+ }
63
+ function readJson(file) {
64
+ try {
65
+ return JSON.parse(fs.readFileSync(file, 'utf8'));
66
+ }
67
+ catch {
68
+ return null;
69
+ }
70
+ }
71
+ function writeJson(file, data) {
72
+ fs.mkdirSync(ATC_DIR, { recursive: true });
73
+ fs.writeFileSync(file, JSON.stringify(data, null, 2));
74
+ }
75
+ /** Stable per-checkout workspace id (created once, survives checkout). */
76
+ function workspaceId() {
77
+ const existing = readJson(WORKSPACE_FILE);
78
+ if (existing?.workspaceId)
79
+ return existing.workspaceId;
80
+ const id = `ws_${(0, node_crypto_1.randomUUID)()}`;
81
+ writeJson(WORKSPACE_FILE, { workspaceId: id });
82
+ return id;
83
+ }
84
+ const saveSession = (s) => writeJson(SESSION_FILE, s);
85
+ exports.saveSession = saveSession;
86
+ const loadSession = () => readJson(SESSION_FILE);
87
+ exports.loadSession = loadSession;
88
+ function clearSession() {
89
+ try {
90
+ fs.rmSync(SESSION_FILE);
91
+ }
92
+ catch {
93
+ /* already gone */
94
+ }
95
+ }
96
+ async function api(cfg, bearer, method, apiPath, body) {
97
+ const res = await fetch(`${cfg.api}${apiPath}`, {
98
+ method,
99
+ headers: {
100
+ authorization: `Bearer ${bearer}`,
101
+ 'content-type': 'application/json',
102
+ },
103
+ body: body ? JSON.stringify(body) : undefined,
104
+ });
105
+ const text = await res.text();
106
+ let parsed;
107
+ try {
108
+ parsed = text ? JSON.parse(text) : {};
109
+ }
110
+ catch {
111
+ parsed = { error: text };
112
+ }
113
+ return { status: res.status, ok: res.ok, body: parsed };
114
+ }
115
+ /** Best-effort current git branch for display. */
116
+ function currentBranch() {
117
+ try {
118
+ const head = fs.readFileSync(path.join(process.cwd(), '.git', 'HEAD'), 'utf8').trim();
119
+ const m = /ref:\s+refs\/heads\/(.+)/.exec(head);
120
+ return m ? m[1] : undefined;
121
+ }
122
+ catch {
123
+ return undefined;
124
+ }
125
+ }
package/dist/index.js ADDED
@@ -0,0 +1,190 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ /**
4
+ * atc — the Agent Tower CLI. Thin client of the hosted /v1 API.
5
+ * Config: ATC_API + ATC_TOKEN (frequency token from the dashboard). See
6
+ * docs/AGENT-INTERFACE.md. Exit codes: 0 ok · 1 usage/error · 2 unexpected · 3 conflict.
7
+ */
8
+ const client_1 = require("./client");
9
+ const VERSION = '0.1.0';
10
+ const COMMANDS = ['checkin', 'brief', 'squawk', 'claim', 'clear', 'note', 'notes', 'checkout', 'config'];
11
+ function parseArgs(argv) {
12
+ const positionals = [];
13
+ const flags = {};
14
+ const tags = [];
15
+ for (let i = 0; i < argv.length; i++) {
16
+ const a = argv[i];
17
+ if (a.startsWith('--')) {
18
+ const key = a.slice(2);
19
+ const next = argv[i + 1];
20
+ const boolFlags = ['pin', 'json'];
21
+ if (boolFlags.includes(key)) {
22
+ flags[key] = true;
23
+ }
24
+ else if (key === 'tag') {
25
+ if (next) {
26
+ tags.push(next);
27
+ i++;
28
+ }
29
+ }
30
+ else if (next && !next.startsWith('--')) {
31
+ flags[key] = next;
32
+ i++;
33
+ }
34
+ else {
35
+ flags[key] = true;
36
+ }
37
+ }
38
+ else {
39
+ positionals.push(a);
40
+ }
41
+ }
42
+ return { positionals, flags, tags };
43
+ }
44
+ function out(json, human, data) {
45
+ process.stdout.write(json ? JSON.stringify(data, null, 2) + '\n' : human + '\n');
46
+ }
47
+ function fail(msg, code = 1) {
48
+ process.stderr.write(`atc: ${msg}\n`);
49
+ process.exit(code);
50
+ }
51
+ function requireSession() {
52
+ const s = (0, client_1.loadSession)();
53
+ if (!s)
54
+ fail("not checked in — run 'atc checkin' first");
55
+ return s;
56
+ }
57
+ function renderConflicts(conflicts) {
58
+ if (!conflicts || conflicts.length === 0)
59
+ return '✓ no conflicts';
60
+ return (`⚠ CONFLICT (${conflicts.length}):\n` +
61
+ conflicts
62
+ .map((c) => ` ${c.by} holds ${c.theirGlob} (${c.status}, seen ${c.lastSeen}) vs your ${c.glob}`)
63
+ .join('\n'));
64
+ }
65
+ function printHelp() {
66
+ process.stdout.write(`atc ${VERSION} — Agent Tower CLI\n\n` +
67
+ `Usage: atc <command> [args]\n\n` +
68
+ ` checkin [--task "..."] [--as <callsign>] [--cli <name>]\n` +
69
+ ` brief\n` +
70
+ ` squawk "<status>" [--code working|blocked|review|mayday]\n` +
71
+ ` claim "<glob>" (exit 3 if it conflicts)\n` +
72
+ ` clear ["<glob>"] (omit glob to release all)\n` +
73
+ ` note "<body>" [--tag <t>]... [--pin] [--supersede <id>]\n` +
74
+ ` notes [--tag <t>] list the decision log\n` +
75
+ ` checkout\n` +
76
+ ` config\n\n` +
77
+ `Env: ATC_API, ATC_TOKEN. Add --json for raw output.\n`);
78
+ }
79
+ async function main(argv) {
80
+ const [cmd, ...rest] = argv;
81
+ if (!cmd || cmd === '--help' || cmd === '-h')
82
+ return printHelp();
83
+ if (cmd === '--version' || cmd === '-v')
84
+ return void process.stdout.write(VERSION + '\n');
85
+ if (!COMMANDS.includes(cmd))
86
+ fail(`unknown command '${cmd}'. Run 'atc --help'.`);
87
+ const { positionals, flags, tags } = parseArgs(rest);
88
+ const json = flags.json === true;
89
+ const cfg = (0, client_1.loadConfig)();
90
+ if (cmd === 'config') {
91
+ return out(json, `api: ${cfg.api || '(unset)'}\ntoken: ${cfg.token ? '(set)' : '(unset)'}`, {
92
+ api: cfg.api || null,
93
+ token: cfg.token ? 'set' : null,
94
+ });
95
+ }
96
+ if (!cfg.api || !cfg.token) {
97
+ fail('ATC_API and ATC_TOKEN must be set (get a frequency token from the dashboard)');
98
+ }
99
+ if (cmd === 'checkin') {
100
+ const r = await (0, client_1.api)(cfg, cfg.token, 'POST', '/v1/sessions', {
101
+ workspaceId: (0, client_1.workspaceId)(),
102
+ callsign: flags.as,
103
+ cli: flags.cli || process.env.ATC_CLI || 'cli',
104
+ worktree: process.cwd(),
105
+ branch: (0, client_1.currentBranch)(),
106
+ task: flags.task,
107
+ });
108
+ if (!r.ok)
109
+ fail(r.body?.error ?? `checkin failed (${r.status})`, 2);
110
+ (0, client_1.saveSession)({ callsign: r.body.callsign, sessionToken: r.body.sessionToken, api: cfg.api });
111
+ const c = r.body.brief?.counts ?? {};
112
+ return out(json, `checked in as ${r.body.callsign} · ${c.sessions ?? 0} on board, ${c.claims ?? 0} claims, ${c.notams ?? 0} notams`, r.body);
113
+ }
114
+ const session = requireSession();
115
+ const tok = session.sessionToken;
116
+ switch (cmd) {
117
+ case 'brief': {
118
+ const r = await (0, client_1.api)(cfg, tok, 'GET', '/v1/brief');
119
+ if (!r.ok)
120
+ fail(r.body?.error ?? `brief failed (${r.status})`, 2);
121
+ const b = r.body;
122
+ const human = `roster: ${b.roster.map((x) => `${x.callsign}(${x.status})`).join(', ') || '(none)'}\n` +
123
+ `claims: ${b.claims.map((x) => `${x.callsign}:${x.glob}`).join(', ') || '(none)'}\n` +
124
+ renderConflicts(b.conflictsWithMine) +
125
+ `\nnotams: ${b.notams.length}/${b.counts.notams}`;
126
+ return out(json, human, b);
127
+ }
128
+ case 'squawk': {
129
+ const r = await (0, client_1.api)(cfg, tok, 'POST', '/v1/squawk', {
130
+ status: positionals.join(' ') || undefined,
131
+ code: flags.code,
132
+ });
133
+ if (!r.ok)
134
+ fail(r.body?.error ?? `squawk failed (${r.status})`, 2);
135
+ return out(json, renderConflicts(r.body.conflictsWithMine), r.body);
136
+ }
137
+ case 'claim': {
138
+ const glob = positionals[0];
139
+ if (!glob)
140
+ fail('usage: atc claim "<glob>"');
141
+ const r = await (0, client_1.api)(cfg, tok, 'POST', '/v1/claims', { glob });
142
+ if (!r.ok)
143
+ fail(r.body?.error ?? `claim failed (${r.status})`, 2);
144
+ const conflicts = r.body.conflicts ?? [];
145
+ out(json, `claimed ${glob}\n${renderConflicts(conflicts)}`, r.body);
146
+ if (conflicts.length > 0)
147
+ process.exit(3);
148
+ return;
149
+ }
150
+ case 'clear': {
151
+ const r = await (0, client_1.api)(cfg, tok, 'POST', '/v1/clear', { glob: positionals[0] });
152
+ if (!r.ok)
153
+ fail(r.body?.error ?? `clear failed (${r.status})`, 2);
154
+ return out(json, `cleared: ${(r.body.cleared ?? []).join(', ') || '(none)'}`, r.body);
155
+ }
156
+ case 'note': {
157
+ const body = positionals.join(' ');
158
+ if (!body)
159
+ fail('usage: atc note "<body>" [--tag t] [--pin] [--supersede <id>]');
160
+ const supersedes = typeof flags.supersede === 'string' ? flags.supersede : undefined;
161
+ const r = await (0, client_1.api)(cfg, tok, 'POST', '/v1/notams', { body, tags, pinned: flags.pin === true, supersedes });
162
+ if (!r.ok)
163
+ fail(r.body?.error ?? `note failed (${r.status})`, 2);
164
+ return out(json, `noted (${r.body.id})`, r.body);
165
+ }
166
+ case 'notes': {
167
+ const q = tags[0] ? `?tag=${encodeURIComponent(tags[0])}` : '';
168
+ const r = await (0, client_1.api)(cfg, tok, 'GET', `/v1/notams${q}`);
169
+ if (!r.ok)
170
+ fail(r.body?.error ?? `notes failed (${r.status})`, 2);
171
+ const human = (r.body.notams ?? [])
172
+ .map((n) => `${n.pinned ? '📌 ' : ''}[${n.id}] ${n.body}${n.tags?.length ? ' #' + n.tags.join(' #') : ''}`)
173
+ .join('\n') || '(no notams)';
174
+ return out(json, human, r.body);
175
+ }
176
+ case 'checkout': {
177
+ const r = await (0, client_1.api)(cfg, tok, 'POST', '/v1/checkout');
178
+ (0, client_1.clearSession)();
179
+ if (!r.ok)
180
+ fail(r.body?.error ?? `checkout failed (${r.status})`, 2);
181
+ return out(json, 'checked out', r.body);
182
+ }
183
+ default:
184
+ fail(`command '${cmd}' not wired`);
185
+ }
186
+ }
187
+ main(process.argv.slice(2)).catch((err) => {
188
+ process.stderr.write(`atc: ${err?.message ?? err}\n`);
189
+ process.exit(2);
190
+ });
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@agenttower/cli",
3
+ "version": "0.1.0",
4
+ "description": "atc — the Agent Tower CLI. Coordinate multiple coding agents on one repo (claims, conflicts, decision log).",
5
+ "license": "MIT",
6
+ "repository": { "type": "git", "url": "https://github.com/manateeit/agent-tower.git", "directory": "cli" },
7
+ "homepage": "https://app.agenttower.dev",
8
+ "keywords": ["agent-tower", "atc", "coding-agents", "coordination", "cli", "claude", "mcp"],
9
+ "type": "commonjs",
10
+ "bin": { "atc": "bin/atc.js" },
11
+ "files": ["bin", "dist", "README.md"],
12
+ "engines": { "node": ">=18" },
13
+ "publishConfig": { "access": "public" },
14
+ "scripts": {
15
+ "build": "tsc",
16
+ "typecheck": "tsc --noEmit",
17
+ "prepublishOnly": "npm run build"
18
+ },
19
+ "devDependencies": {
20
+ "@types/node": "^20.16.10",
21
+ "typescript": "^5.6.2"
22
+ }
23
+ }