@agenttower/cli 0.1.1 → 0.3.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/README.md +39 -12
- package/bin/atc.js +0 -0
- package/dist/human.js +86 -0
- package/dist/index.js +470 -9
- package/dist/init.js +447 -0
- package/package.json +29 -7
package/README.md
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
# @agenttower/cli — `atc`
|
|
2
2
|
|
|
3
3
|
The Agent Tower CLI. Coordinate multiple coding agents working one repo: claim
|
|
4
|
-
scope before editing, get inline conflict verdicts,
|
|
4
|
+
scope before editing, get inline conflict verdicts, share a decision log, and
|
|
5
|
+
receive your **standing orders** at every checkin. Also the terminal for humans —
|
|
6
|
+
`atc login` manages projects, tokens, and standing orders without the dashboard.
|
|
5
7
|
|
|
6
8
|
## Install
|
|
7
9
|
|
|
@@ -9,28 +11,53 @@ scope before editing, get inline conflict verdicts, and share a decision log.
|
|
|
9
11
|
npm i -g @agenttower/cli
|
|
10
12
|
```
|
|
11
13
|
|
|
12
|
-
##
|
|
14
|
+
## Wire a repo (fast path)
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
```bash
|
|
17
|
+
atc init # scaffolds .mcp.json + the AGENTS.md compliance block + .gitignore
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Agent plane (frequency token)
|
|
21
|
+
|
|
22
|
+
Get a **frequency token** (dashboard https://app.agenttower.dev → project →
|
|
23
|
+
Mint token, or `atc tokens mint` after login), then:
|
|
16
24
|
|
|
17
25
|
```bash
|
|
18
26
|
export ATC_TOKEN=atcf_… # required
|
|
19
27
|
# export ATC_API=… # optional; defaults to the hosted prod API
|
|
20
28
|
```
|
|
21
29
|
|
|
22
|
-
## Use
|
|
23
|
-
|
|
24
30
|
```bash
|
|
25
|
-
atc checkin --task "refactor auth" # contact the Tower
|
|
26
|
-
|
|
31
|
+
atc checkin --task "refactor auth" # contact the Tower; the brief arrives with
|
|
32
|
+
# your standing orders first — follow them
|
|
33
|
+
atc standing # your assigned standing orders, in full
|
|
34
|
+
atc brief # roster + claims + conflicts + NOTAMs
|
|
27
35
|
atc claim "src/auth/**" # request scope; exit code 3 if it conflicts
|
|
28
36
|
atc squawk "rewriting User model" # status + heartbeat
|
|
29
|
-
atc note "auth uses JWT, 15min TTL" #
|
|
37
|
+
atc note "auth uses JWT, 15min TTL" # decision log (--pin to pin; note show <id> for full)
|
|
38
|
+
atc standing propose handoff --body "…" # propose a DRAFT standing order (a human assigns)
|
|
30
39
|
atc clear ["src/auth/**"] # release scope (all, or one glob)
|
|
31
40
|
atc checkout # leave; releases your claims
|
|
32
41
|
```
|
|
33
42
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
43
|
+
## Human plane (`atc login`)
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
atc login # opens the browser; approve; 30-day session
|
|
47
|
+
atc whoami / atc logout
|
|
48
|
+
|
|
49
|
+
atc projects [create "<name>"]
|
|
50
|
+
atc tokens --project <p> # list (shows each token's alignment)
|
|
51
|
+
atc tokens mint --project <p> --label colby --orders relay-agent
|
|
52
|
+
atc tokens align <tokenId> --orders a,b | --default
|
|
53
|
+
atc standing ls|show|create|edit|rm|assign --project <p>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Standing orders** are a per-project library of named instructions. Which orders
|
|
57
|
+
an agent receives is decided by its token's **alignment** (`mint --orders`,
|
|
58
|
+
realign anytime — it lands at the agent's next brief); unaligned tokens get the
|
|
59
|
+
project default set (`atc standing assign`).
|
|
60
|
+
|
|
61
|
+
Agent session state lives in `.atc/` in the worktree (gitignore it); your login
|
|
62
|
+
session lives in `~/.config/atc/`. Add `--json` to any command for
|
|
63
|
+
machine-readable output. Full protocol: https://app.agenttower.dev/docs
|
package/bin/atc.js
CHANGED
|
File without changes
|
package/dist/human.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
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.loadHuman = loadHuman;
|
|
37
|
+
exports.saveHuman = saveHuman;
|
|
38
|
+
exports.clearHuman = clearHuman;
|
|
39
|
+
exports.humanApi = humanApi;
|
|
40
|
+
exports.requireHuman = requireHuman;
|
|
41
|
+
/**
|
|
42
|
+
* Human-plane session store for `atc login` / management commands.
|
|
43
|
+
* Stores the `atcu_` CLI session token at ~/.config/atc/session.json (chmod 0600).
|
|
44
|
+
* Deliberately separate from the agent session in .atc/session.json.
|
|
45
|
+
*/
|
|
46
|
+
const fs = __importStar(require("node:fs"));
|
|
47
|
+
const os = __importStar(require("node:os"));
|
|
48
|
+
const path = __importStar(require("node:path"));
|
|
49
|
+
const client_1 = require("./client");
|
|
50
|
+
const CONFIG_DIR = path.join(os.homedir(), '.config', 'atc');
|
|
51
|
+
const SESSION_FILE = path.join(CONFIG_DIR, 'session.json');
|
|
52
|
+
function loadHuman() {
|
|
53
|
+
try {
|
|
54
|
+
const raw = fs.readFileSync(SESSION_FILE, 'utf8');
|
|
55
|
+
const s = JSON.parse(raw);
|
|
56
|
+
if (typeof s?.token === 'string' && s.token)
|
|
57
|
+
return s;
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
/* missing or unreadable */
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
function saveHuman(session) {
|
|
65
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
66
|
+
fs.writeFileSync(SESSION_FILE, JSON.stringify(session, null, 2), { mode: 0o600 });
|
|
67
|
+
}
|
|
68
|
+
function clearHuman() {
|
|
69
|
+
try {
|
|
70
|
+
fs.rmSync(SESSION_FILE);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
/* already gone */
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/** Return an api() wrapper that uses the atcu_ token from the saved human session. */
|
|
77
|
+
function humanApi(session, method, apiPath, body) {
|
|
78
|
+
return (0, client_1.api)({ api: session.api, token: session.token }, session.token, method, apiPath, body);
|
|
79
|
+
}
|
|
80
|
+
/** Fail with a friendly message if no human session exists. */
|
|
81
|
+
function requireHuman(fail) {
|
|
82
|
+
const s = loadHuman();
|
|
83
|
+
if (!s)
|
|
84
|
+
fail("not logged in — run 'atc login'");
|
|
85
|
+
return s;
|
|
86
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,54 @@
|
|
|
1
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
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
/**
|
|
4
37
|
* atc — the Agent Tower CLI. Thin client of the hosted /v1 API.
|
|
5
38
|
* Config: ATC_API + ATC_TOKEN (frequency token from the dashboard). See
|
|
6
39
|
* docs/AGENT-INTERFACE.md. Exit codes: 0 ok · 1 usage/error · 2 unexpected · 3 conflict.
|
|
7
40
|
*/
|
|
41
|
+
const fs = __importStar(require("node:fs"));
|
|
42
|
+
const child_process = __importStar(require("node:child_process"));
|
|
8
43
|
const client_1 = require("./client");
|
|
9
|
-
const
|
|
10
|
-
const
|
|
44
|
+
const init_1 = require("./init");
|
|
45
|
+
const human_1 = require("./human");
|
|
46
|
+
const VERSION = '0.3.1';
|
|
47
|
+
const COMMANDS = [
|
|
48
|
+
'init', 'checkin', 'brief', 'standing', 'squawk', 'claim', 'clear',
|
|
49
|
+
'note', 'notes', 'checkout', 'config',
|
|
50
|
+
'login', 'logout', 'whoami', 'projects', 'tokens',
|
|
51
|
+
];
|
|
11
52
|
function parseArgs(argv) {
|
|
12
53
|
const positionals = [];
|
|
13
54
|
const flags = {};
|
|
@@ -17,7 +58,7 @@ function parseArgs(argv) {
|
|
|
17
58
|
if (a.startsWith('--')) {
|
|
18
59
|
const key = a.slice(2);
|
|
19
60
|
const next = argv[i + 1];
|
|
20
|
-
const boolFlags = ['pin', 'json'];
|
|
61
|
+
const boolFlags = ['pin', 'json', 'yes', 'manual', 'draft', 'activate', 'default', 'none', 'save-token'];
|
|
21
62
|
if (boolFlags.includes(key)) {
|
|
22
63
|
flags[key] = true;
|
|
23
64
|
}
|
|
@@ -65,16 +106,89 @@ function renderConflicts(conflicts) {
|
|
|
65
106
|
function printHelp() {
|
|
66
107
|
process.stdout.write(`atc ${VERSION} — Agent Tower CLI\n\n` +
|
|
67
108
|
`Usage: atc <command> [args]\n\n` +
|
|
109
|
+
`Session commands (human login):\n` +
|
|
110
|
+
` login [--api <url>] [--dashboard <url>] [--manual]\n` +
|
|
111
|
+
` logout\n` +
|
|
112
|
+
` whoami\n\n` +
|
|
113
|
+
`Management commands (require atc login):\n` +
|
|
114
|
+
` projects [create "<name>"]\n` +
|
|
115
|
+
` tokens --project <id|name> list tokens\n` +
|
|
116
|
+
` tokens mint --project <p> [--label l] [--orders a,b,c]\n` +
|
|
117
|
+
` tokens revoke <tokenId>\n` +
|
|
118
|
+
` tokens align <tokenId> --orders a,b,c | --default\n` +
|
|
119
|
+
` standing ls --project <p>\n` +
|
|
120
|
+
` standing show <ref> --project <p>\n` +
|
|
121
|
+
` standing create <name> --project <p> (--body "…" | --file f) [--draft]\n` +
|
|
122
|
+
` standing edit <ref> --project <p> (--body "…" | --file f) [--name n] [--activate|--draft]\n` +
|
|
123
|
+
` standing rm <ref> --project <p>\n` +
|
|
124
|
+
` standing assign --project <p> <name1,name2,…> | --none\n` +
|
|
125
|
+
` standing propose <name> (--body "…" | --file f) (agent plane, uses ATC_TOKEN)\n\n` +
|
|
126
|
+
`Agent commands (require ATC_API + ATC_TOKEN):\n` +
|
|
127
|
+
` init [--token <freq-token>] [--save-token] [--yes] wire a project to Agent Tower\n` +
|
|
128
|
+
` --save-token persists the token to .claude/settings.local.json (gitignored)\n` +
|
|
68
129
|
` checkin [--task "..."] [--as <callsign>] [--cli <name>]\n` +
|
|
69
130
|
` brief\n` +
|
|
131
|
+
` standing print the frequency's standing orders in full\n` +
|
|
70
132
|
` squawk "<status>" [--code working|blocked|review|mayday]\n` +
|
|
71
133
|
` claim "<glob>" (exit 3 if it conflicts)\n` +
|
|
72
134
|
` clear ["<glob>"] (omit glob to release all)\n` +
|
|
73
135
|
` note "<body>" [--tag <t>]... [--pin] [--supersede <id>]\n` +
|
|
136
|
+
` note show <id> full body of one NOTAM (briefs truncate)\n` +
|
|
74
137
|
` notes [--tag <t>] list the decision log\n` +
|
|
75
138
|
` checkout\n` +
|
|
76
139
|
` config\n\n` +
|
|
77
|
-
`Env: ATC_API, ATC_TOKEN. Add --json for raw output.\n`);
|
|
140
|
+
`Env: ATC_API, ATC_TOKEN, ATC_DASHBOARD. Add --json for raw output.\n`);
|
|
141
|
+
}
|
|
142
|
+
/** Try to open a URL in the default browser (best-effort, never throws). */
|
|
143
|
+
function tryOpenBrowser(url) {
|
|
144
|
+
const cmds = {
|
|
145
|
+
darwin: ['open', [url]],
|
|
146
|
+
linux: ['xdg-open', [url]],
|
|
147
|
+
win32: ['cmd', ['/c', 'start', url]],
|
|
148
|
+
};
|
|
149
|
+
const entry = cmds[process.platform];
|
|
150
|
+
if (!entry)
|
|
151
|
+
return;
|
|
152
|
+
const [bin, args] = entry;
|
|
153
|
+
try {
|
|
154
|
+
child_process.spawn(bin, args, { detached: true, stdio: 'ignore' }).unref();
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
/* ignore */
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/** Resolve a project ref (id or name) to an id via GET /v1/projects. */
|
|
161
|
+
async function resolveProject(session, ref) {
|
|
162
|
+
if (ref.startsWith('prj_'))
|
|
163
|
+
return ref;
|
|
164
|
+
const r = await (0, human_1.humanApi)(session, 'GET', '/v1/projects');
|
|
165
|
+
if (!r.ok)
|
|
166
|
+
fail(r.body?.error ?? `could not list projects (${r.status})`);
|
|
167
|
+
const projects = r.body.projects ?? [];
|
|
168
|
+
const byId = projects.find((p) => p.id === ref);
|
|
169
|
+
if (byId)
|
|
170
|
+
return byId.id;
|
|
171
|
+
const matches = projects.filter((p) => p.name.toLowerCase() === ref.toLowerCase());
|
|
172
|
+
if (matches.length === 1)
|
|
173
|
+
return matches[0].id;
|
|
174
|
+
if (matches.length === 0)
|
|
175
|
+
fail(`no project named "${ref}"`);
|
|
176
|
+
fail(`ambiguous project name "${ref}" — matches: ${matches.map((p) => `${p.id} (${p.name})`).join(', ')}`);
|
|
177
|
+
}
|
|
178
|
+
/** Read body text from --body "…" or --file f flags. */
|
|
179
|
+
function readBodyFlag(flags) {
|
|
180
|
+
if (typeof flags.body === 'string')
|
|
181
|
+
return flags.body;
|
|
182
|
+
if (typeof flags.file === 'string') {
|
|
183
|
+
try {
|
|
184
|
+
return fs.readFileSync(flags.file, 'utf8');
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
188
|
+
fail(`could not read file "${flags.file}": ${msg}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return undefined;
|
|
78
192
|
}
|
|
79
193
|
async function main(argv) {
|
|
80
194
|
const [cmd, ...rest] = argv;
|
|
@@ -87,12 +201,336 @@ async function main(argv) {
|
|
|
87
201
|
const { positionals, flags, tags } = parseArgs(rest);
|
|
88
202
|
const json = flags.json === true;
|
|
89
203
|
const cfg = (0, client_1.loadConfig)();
|
|
204
|
+
if (cmd === 'init') {
|
|
205
|
+
return (0, init_1.runInit)({
|
|
206
|
+
token: typeof flags.token === 'string' ? flags.token : undefined,
|
|
207
|
+
saveToken: flags['save-token'] === true,
|
|
208
|
+
yes: flags.yes === true,
|
|
209
|
+
json,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
90
212
|
if (cmd === 'config') {
|
|
91
213
|
return out(json, `api: ${cfg.api || '(unset)'}\ntoken: ${cfg.token ? '(set)' : '(unset)'}`, {
|
|
92
214
|
api: cfg.api || null,
|
|
93
215
|
token: cfg.token ? 'set' : null,
|
|
94
216
|
});
|
|
95
217
|
}
|
|
218
|
+
// ---- Human-plane session commands ----
|
|
219
|
+
if (cmd === 'login') {
|
|
220
|
+
const apiBase = (typeof flags.api === 'string' ? flags.api : cfg.api || client_1.DEFAULT_API).replace(/\/$/, '');
|
|
221
|
+
const dashboardBase = (typeof flags.dashboard === 'string'
|
|
222
|
+
? flags.dashboard
|
|
223
|
+
: process.env.ATC_DASHBOARD || 'https://app.agenttower.dev').replace(/\/$/, '');
|
|
224
|
+
const manual = flags.manual === true;
|
|
225
|
+
// Start the device flow
|
|
226
|
+
const startRes = await (0, client_1.api)({ api: apiBase, token: '' }, '', 'POST', '/v1/cli-auth/start').catch((err) => {
|
|
227
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
228
|
+
fail(`login failed: ${msg}`);
|
|
229
|
+
});
|
|
230
|
+
if (!startRes.ok) {
|
|
231
|
+
fail(startRes.body?.error ?? `login start failed (${startRes.status})`);
|
|
232
|
+
}
|
|
233
|
+
const { authId, pollSecret, expiresInSeconds } = startRes.body;
|
|
234
|
+
const verifyUrl = `${dashboardBase}/cli-auth?code=${authId}`;
|
|
235
|
+
process.stdout.write(`Open this URL to approve the login:\n ${verifyUrl}\n`);
|
|
236
|
+
if (!manual) {
|
|
237
|
+
tryOpenBrowser(verifyUrl);
|
|
238
|
+
}
|
|
239
|
+
// Poll until approved or expired
|
|
240
|
+
const deadline = Date.now() + expiresInSeconds * 1000;
|
|
241
|
+
let approved = false;
|
|
242
|
+
let token = '';
|
|
243
|
+
let orgId = '';
|
|
244
|
+
process.stderr.write('Waiting for browser approval');
|
|
245
|
+
const interval = setInterval(() => process.stderr.write('.'), 2000);
|
|
246
|
+
const cleanup = () => {
|
|
247
|
+
clearInterval(interval);
|
|
248
|
+
process.stderr.write('\n');
|
|
249
|
+
};
|
|
250
|
+
// Handle Ctrl-C gracefully
|
|
251
|
+
process.on('SIGINT', () => {
|
|
252
|
+
cleanup();
|
|
253
|
+
process.stderr.write('atc: login cancelled\n');
|
|
254
|
+
process.exit(1);
|
|
255
|
+
});
|
|
256
|
+
while (Date.now() < deadline && !approved) {
|
|
257
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
258
|
+
try {
|
|
259
|
+
const pollRes = await (0, client_1.api)({ api: apiBase, token: '' }, '', 'GET', `/v1/cli-auth/${encodeURIComponent(authId)}?secret=${encodeURIComponent(pollSecret)}`);
|
|
260
|
+
if (pollRes.ok && pollRes.body?.status === 'approved') {
|
|
261
|
+
approved = true;
|
|
262
|
+
token = pollRes.body.token;
|
|
263
|
+
orgId = pollRes.body.orgId;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
/* transient network error — keep polling */
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
cleanup();
|
|
271
|
+
if (!approved) {
|
|
272
|
+
fail('login timed out — run atc login again');
|
|
273
|
+
}
|
|
274
|
+
// Fetch identity
|
|
275
|
+
let userId;
|
|
276
|
+
try {
|
|
277
|
+
const meRes = await (0, client_1.api)({ api: apiBase, token }, token, 'GET', '/v1/me');
|
|
278
|
+
if (meRes.ok) {
|
|
279
|
+
userId = meRes.body?.userId;
|
|
280
|
+
orgId = meRes.body?.orgId || orgId;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
catch {
|
|
284
|
+
/* best-effort */
|
|
285
|
+
}
|
|
286
|
+
(0, human_1.saveHuman)({ api: apiBase, token, orgId, userId });
|
|
287
|
+
return out(json, `logged in as ${userId ?? '(unknown)'} (org ${orgId})`, { userId, orgId });
|
|
288
|
+
}
|
|
289
|
+
if (cmd === 'logout') {
|
|
290
|
+
const session = (0, human_1.loadHuman)();
|
|
291
|
+
if (session) {
|
|
292
|
+
try {
|
|
293
|
+
await (0, human_1.humanApi)(session, 'DELETE', '/v1/cli-auth/session');
|
|
294
|
+
}
|
|
295
|
+
catch {
|
|
296
|
+
/* best-effort */
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
(0, human_1.clearHuman)();
|
|
300
|
+
return out(json, 'logged out', { ok: true });
|
|
301
|
+
}
|
|
302
|
+
if (cmd === 'whoami') {
|
|
303
|
+
const session = (0, human_1.requireHuman)(fail);
|
|
304
|
+
const r = await (0, human_1.humanApi)(session, 'GET', '/v1/me');
|
|
305
|
+
if (!r.ok)
|
|
306
|
+
fail(r.body?.error ?? `whoami failed (${r.status})`);
|
|
307
|
+
return out(json, `userId: ${r.body.userId}\norgId: ${r.body.orgId}\nrole: ${r.body.orgRole ?? '(unknown)'}`, r.body);
|
|
308
|
+
}
|
|
309
|
+
if (cmd === 'projects') {
|
|
310
|
+
const session = (0, human_1.requireHuman)(fail);
|
|
311
|
+
const sub = positionals[0];
|
|
312
|
+
if (!sub || sub === 'list') {
|
|
313
|
+
const r = await (0, human_1.humanApi)(session, 'GET', '/v1/projects');
|
|
314
|
+
if (!r.ok)
|
|
315
|
+
fail(r.body?.error ?? `projects failed (${r.status})`);
|
|
316
|
+
const rows = (r.body.projects ?? []);
|
|
317
|
+
const human = rows.length
|
|
318
|
+
? rows.map((p) => `${p.id} ${p.name} (${p.status})`).join('\n')
|
|
319
|
+
: '(no projects)';
|
|
320
|
+
return out(json, human, r.body);
|
|
321
|
+
}
|
|
322
|
+
if (sub === 'create') {
|
|
323
|
+
const name = positionals[1] ?? (typeof flags.name === 'string' ? flags.name : undefined);
|
|
324
|
+
if (!name)
|
|
325
|
+
fail('usage: atc projects create "<name>"');
|
|
326
|
+
const r = await (0, human_1.humanApi)(session, 'POST', '/v1/projects', { name });
|
|
327
|
+
if (!r.ok)
|
|
328
|
+
fail(r.body?.error ?? `projects create failed (${r.status})`);
|
|
329
|
+
return out(json, `created project ${r.body.id} — ${r.body.name}`, r.body);
|
|
330
|
+
}
|
|
331
|
+
fail(`unknown projects subcommand "${sub}"`);
|
|
332
|
+
}
|
|
333
|
+
if (cmd === 'tokens') {
|
|
334
|
+
const session = (0, human_1.requireHuman)(fail);
|
|
335
|
+
const sub = positionals[0];
|
|
336
|
+
if (!sub || sub === 'list') {
|
|
337
|
+
const projectRef = typeof flags.project === 'string' ? flags.project : undefined;
|
|
338
|
+
if (!projectRef)
|
|
339
|
+
fail('usage: atc tokens --project <id|name>');
|
|
340
|
+
const pid = await resolveProject(session, projectRef);
|
|
341
|
+
const r = await (0, human_1.humanApi)(session, 'GET', `/v1/projects/${encodeURIComponent(pid)}/tokens`);
|
|
342
|
+
if (!r.ok)
|
|
343
|
+
fail(r.body?.error ?? `tokens list failed (${r.status})`);
|
|
344
|
+
const rows = (r.body.tokens ?? []);
|
|
345
|
+
const human = rows.length
|
|
346
|
+
? rows.map((t) => `${t.tokenId} ${t.label ?? '(no label)'} ${t.revoked ? '[revoked]' : '[active]'} ${t.createdAt}`).join('\n')
|
|
347
|
+
: '(no tokens)';
|
|
348
|
+
return out(json, human, r.body);
|
|
349
|
+
}
|
|
350
|
+
if (sub === 'mint') {
|
|
351
|
+
const projectRef = typeof flags.project === 'string' ? flags.project : undefined;
|
|
352
|
+
if (!projectRef)
|
|
353
|
+
fail('usage: atc tokens mint --project <p> [--label l] [--orders a,b,c]');
|
|
354
|
+
const pid = await resolveProject(session, projectRef);
|
|
355
|
+
const label = typeof flags.label === 'string' ? flags.label : undefined;
|
|
356
|
+
const ordersRaw = typeof flags.orders === 'string' ? flags.orders : undefined;
|
|
357
|
+
const standingOrderIds = ordersRaw ? ordersRaw.split(',').map((s) => s.trim()).filter(Boolean) : undefined;
|
|
358
|
+
const r = await (0, human_1.humanApi)(session, 'POST', `/v1/projects/${encodeURIComponent(pid)}/tokens`, {
|
|
359
|
+
label,
|
|
360
|
+
standingOrderIds,
|
|
361
|
+
});
|
|
362
|
+
if (!r.ok)
|
|
363
|
+
fail(r.body?.error ?? `tokens mint failed (${r.status})`);
|
|
364
|
+
const body = r.body;
|
|
365
|
+
// Token shown ONCE — warning on stderr so --json stdout stays parseable.
|
|
366
|
+
process.stderr.write(`WARNING: copy this token now — it will not be shown again.\n`);
|
|
367
|
+
return out(json, `token: ${body.token}\nid: ${body.tokenId}${body.label ? `\nlabel: ${body.label}` : ''}`, r.body);
|
|
368
|
+
}
|
|
369
|
+
if (sub === 'revoke') {
|
|
370
|
+
const tokenId = positionals[1];
|
|
371
|
+
if (!tokenId)
|
|
372
|
+
fail('usage: atc tokens revoke <tokenId>');
|
|
373
|
+
const r = await (0, human_1.humanApi)(session, 'DELETE', `/v1/tokens/${encodeURIComponent(tokenId)}`);
|
|
374
|
+
if (!r.ok)
|
|
375
|
+
fail(r.body?.error ?? `tokens revoke failed (${r.status})`);
|
|
376
|
+
return out(json, `revoked ${tokenId}`, r.body);
|
|
377
|
+
}
|
|
378
|
+
if (sub === 'align') {
|
|
379
|
+
const tokenId = positionals[1];
|
|
380
|
+
if (!tokenId)
|
|
381
|
+
fail('usage: atc tokens align <tokenId> --orders a,b,c | --default');
|
|
382
|
+
let orders;
|
|
383
|
+
if (flags.default === true) {
|
|
384
|
+
orders = null;
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
const ordersRaw = typeof flags.orders === 'string' ? flags.orders : undefined;
|
|
388
|
+
if (!ordersRaw)
|
|
389
|
+
fail('usage: atc tokens align <tokenId> --orders a,b,c | --default');
|
|
390
|
+
orders = ordersRaw.split(',').map((s) => s.trim()).filter(Boolean);
|
|
391
|
+
}
|
|
392
|
+
const r = await (0, human_1.humanApi)(session, 'PUT', `/v1/tokens/${encodeURIComponent(tokenId)}/standing`, { orders });
|
|
393
|
+
if (!r.ok)
|
|
394
|
+
fail(r.body?.error ?? `tokens align failed (${r.status})`);
|
|
395
|
+
return out(json, orders === null ? `${tokenId} → project default` : `${tokenId} → [${orders.join(', ')}]`, r.body);
|
|
396
|
+
}
|
|
397
|
+
fail(`unknown tokens subcommand "${sub}"`);
|
|
398
|
+
}
|
|
399
|
+
// ---- standing subcommands (management + agent) ----
|
|
400
|
+
if (cmd === 'standing') {
|
|
401
|
+
// No sub → agent plane read (existing behaviour; requires ATC_TOKEN session)
|
|
402
|
+
if (!positionals[0]) {
|
|
403
|
+
if (!cfg.api || !cfg.token) {
|
|
404
|
+
fail('ATC_API and ATC_TOKEN must be set (get a frequency token from the dashboard)');
|
|
405
|
+
}
|
|
406
|
+
const session = requireSession();
|
|
407
|
+
const tok = session.sessionToken;
|
|
408
|
+
const r = await (0, client_1.api)(cfg, tok, 'GET', '/v1/standing');
|
|
409
|
+
if (!r.ok)
|
|
410
|
+
fail(r.body?.error ?? `standing failed (${r.status})`, 2);
|
|
411
|
+
const s = r.body.standing;
|
|
412
|
+
return out(json, s
|
|
413
|
+
? `standing orders (${s.source}): ${s.orders.map((o) => `${o.name} v${o.version}`).join(', ')}\n\n${s.body}`
|
|
414
|
+
: '(no standing orders assigned to this session)', r.body);
|
|
415
|
+
}
|
|
416
|
+
const sub = positionals[0];
|
|
417
|
+
// propose — agent plane: uses the existing repo session token
|
|
418
|
+
if (sub === 'propose') {
|
|
419
|
+
if (!cfg.api || !cfg.token) {
|
|
420
|
+
fail('ATC_API and ATC_TOKEN must be set (get a frequency token from the dashboard)');
|
|
421
|
+
}
|
|
422
|
+
const session = requireSession();
|
|
423
|
+
const tok = session.sessionToken;
|
|
424
|
+
const name = positionals[1];
|
|
425
|
+
if (!name)
|
|
426
|
+
fail('usage: atc standing propose <name> (--body "…" | --file f)');
|
|
427
|
+
const body = readBodyFlag(flags);
|
|
428
|
+
if (!body)
|
|
429
|
+
fail('usage: atc standing propose <name> (--body "…" | --file f)');
|
|
430
|
+
const r = await (0, client_1.api)(cfg, tok, 'POST', '/v1/standing-orders', { name, body });
|
|
431
|
+
if (!r.ok)
|
|
432
|
+
fail(r.body?.error ?? `standing propose failed (${r.status})`, 2);
|
|
433
|
+
const order = r.body.order ?? r.body;
|
|
434
|
+
return out(json, `draft ${order.id ?? order.orderId} proposed — a human can activate/assign it`, r.body);
|
|
435
|
+
}
|
|
436
|
+
// Management subcommands below all require human login
|
|
437
|
+
const humanSession = (0, human_1.requireHuman)(fail);
|
|
438
|
+
const projectRef = typeof flags.project === 'string' ? flags.project : undefined;
|
|
439
|
+
if (!projectRef && sub !== 'propose') {
|
|
440
|
+
fail('--project <id|name> is required for standing management commands');
|
|
441
|
+
}
|
|
442
|
+
const pid = projectRef ? await resolveProject(humanSession, projectRef) : '';
|
|
443
|
+
if (sub === 'ls') {
|
|
444
|
+
const r = await (0, human_1.humanApi)(humanSession, 'GET', `/v1/projects/${encodeURIComponent(pid)}/standing-orders`);
|
|
445
|
+
if (!r.ok)
|
|
446
|
+
fail(r.body?.error ?? `standing ls failed (${r.status})`);
|
|
447
|
+
const { orders, defaultAssignment } = r.body;
|
|
448
|
+
if (!orders.length)
|
|
449
|
+
return out(json, '(no standing orders)', r.body);
|
|
450
|
+
const human = orders
|
|
451
|
+
.map((o) => {
|
|
452
|
+
const marker = defaultAssignment.includes(o.id) ? ' [default]' : '';
|
|
453
|
+
return `${o.id} ${o.name} ${o.status} v${o.version} ${o.updatedAt}${marker}`;
|
|
454
|
+
})
|
|
455
|
+
.join('\n');
|
|
456
|
+
return out(json, human, r.body);
|
|
457
|
+
}
|
|
458
|
+
if (sub === 'show') {
|
|
459
|
+
const ref = positionals[1];
|
|
460
|
+
if (!ref)
|
|
461
|
+
fail('usage: atc standing show <ref> --project <p>');
|
|
462
|
+
const r = await (0, human_1.humanApi)(humanSession, 'GET', `/v1/projects/${encodeURIComponent(pid)}/standing-orders/${encodeURIComponent(ref)}`);
|
|
463
|
+
if (!r.ok)
|
|
464
|
+
fail(r.body?.error ?? `standing show failed (${r.status})`);
|
|
465
|
+
const { order, history } = r.body;
|
|
466
|
+
const histLine = history?.length
|
|
467
|
+
? `\nhistory: ${history.map((h) => `v${h.version} (${h.updatedAt})`).join(', ')}`
|
|
468
|
+
: '';
|
|
469
|
+
return out(json, `[${order.id}] ${order.name} ${order.status} v${order.version} ${order.updatedAt}${histLine}\n\n${order.body}`, r.body);
|
|
470
|
+
}
|
|
471
|
+
if (sub === 'create') {
|
|
472
|
+
const name = positionals[1];
|
|
473
|
+
if (!name)
|
|
474
|
+
fail('usage: atc standing create <name> --project <p> (--body "…" | --file f) [--draft]');
|
|
475
|
+
const body = readBodyFlag(flags);
|
|
476
|
+
if (!body)
|
|
477
|
+
fail('--body "…" or --file <path> is required');
|
|
478
|
+
const status = flags.draft === true ? 'draft' : 'active';
|
|
479
|
+
const r = await (0, human_1.humanApi)(humanSession, 'POST', `/v1/projects/${encodeURIComponent(pid)}/standing-orders`, {
|
|
480
|
+
name,
|
|
481
|
+
body,
|
|
482
|
+
status,
|
|
483
|
+
});
|
|
484
|
+
if (!r.ok)
|
|
485
|
+
fail(r.body?.error ?? `standing create failed (${r.status})`);
|
|
486
|
+
const order = r.body.order;
|
|
487
|
+
return out(json, `created ${order.id} ${order.name} ${order.status}`, r.body);
|
|
488
|
+
}
|
|
489
|
+
if (sub === 'edit') {
|
|
490
|
+
const ref = positionals[1];
|
|
491
|
+
if (!ref)
|
|
492
|
+
fail('usage: atc standing edit <ref> --project <p> (--body "…" | --file f) [--name n] [--activate|--draft]');
|
|
493
|
+
const body = readBodyFlag(flags);
|
|
494
|
+
const name = typeof flags.name === 'string' ? flags.name : undefined;
|
|
495
|
+
const status = flags.activate === true ? 'active' : flags.draft === true ? 'draft' : undefined;
|
|
496
|
+
if (!body && !name && !status) {
|
|
497
|
+
fail('standing edit requires at least one of --body/--file, --name, --activate, or --draft');
|
|
498
|
+
}
|
|
499
|
+
const r = await (0, human_1.humanApi)(humanSession, 'PUT', `/v1/projects/${encodeURIComponent(pid)}/standing-orders/${encodeURIComponent(ref)}`, { body, name, status });
|
|
500
|
+
if (!r.ok)
|
|
501
|
+
fail(r.body?.error ?? `standing edit failed (${r.status})`);
|
|
502
|
+
const order = r.body.order;
|
|
503
|
+
return out(json, `updated ${order.id} ${order.name} ${order.status} v${order.version}`, r.body);
|
|
504
|
+
}
|
|
505
|
+
if (sub === 'rm') {
|
|
506
|
+
const ref = positionals[1];
|
|
507
|
+
if (!ref)
|
|
508
|
+
fail('usage: atc standing rm <ref> --project <p>');
|
|
509
|
+
const r = await (0, human_1.humanApi)(humanSession, 'DELETE', `/v1/projects/${encodeURIComponent(pid)}/standing-orders/${encodeURIComponent(ref)}`);
|
|
510
|
+
if (!r.ok)
|
|
511
|
+
fail(r.body?.error ?? `standing rm failed (${r.status})`);
|
|
512
|
+
return out(json, `deleted ${r.body.deleted ?? ref}`, r.body);
|
|
513
|
+
}
|
|
514
|
+
if (sub === 'assign') {
|
|
515
|
+
if (flags.none === true) {
|
|
516
|
+
const r = await (0, human_1.humanApi)(humanSession, 'PUT', `/v1/projects/${encodeURIComponent(pid)}/standing-assignment`, { orders: [] });
|
|
517
|
+
if (!r.ok)
|
|
518
|
+
fail(r.body?.error ?? `standing assign failed (${r.status})`);
|
|
519
|
+
return out(json, 'default assignment cleared', r.body);
|
|
520
|
+
}
|
|
521
|
+
// positionals[1] is the comma-separated list, or could be positionals[1..n]
|
|
522
|
+
const ordersRaw = positionals.slice(1).join(',');
|
|
523
|
+
if (!ordersRaw)
|
|
524
|
+
fail('usage: atc standing assign --project <p> <name1,name2,…> | --none');
|
|
525
|
+
const orders = ordersRaw.split(',').map((s) => s.trim()).filter(Boolean);
|
|
526
|
+
const r = await (0, human_1.humanApi)(humanSession, 'PUT', `/v1/projects/${encodeURIComponent(pid)}/standing-assignment`, { orders });
|
|
527
|
+
if (!r.ok)
|
|
528
|
+
fail(r.body?.error ?? `standing assign failed (${r.status})`);
|
|
529
|
+
return out(json, `default assignment → [${(r.body.defaultAssignment ?? orders).join(', ')}]`, r.body);
|
|
530
|
+
}
|
|
531
|
+
fail(`unknown standing subcommand "${sub}"`);
|
|
532
|
+
}
|
|
533
|
+
// ---- Agent-plane commands: require ATC_API + ATC_TOKEN ----
|
|
96
534
|
if (!cfg.api || !cfg.token) {
|
|
97
535
|
fail('ATC_API and ATC_TOKEN must be set (get a frequency token from the dashboard)');
|
|
98
536
|
}
|
|
@@ -109,7 +547,11 @@ async function main(argv) {
|
|
|
109
547
|
fail(r.body?.error ?? `checkin failed (${r.status})`, 2);
|
|
110
548
|
(0, client_1.saveSession)({ callsign: r.body.callsign, sessionToken: r.body.sessionToken, api: cfg.api });
|
|
111
549
|
const c = r.body.brief?.counts ?? {};
|
|
112
|
-
|
|
550
|
+
const so = r.body.brief?.standing;
|
|
551
|
+
const standing = so
|
|
552
|
+
? `\n⚑ standing orders present (${(so.orders ?? []).map((o) => o.name).join(', ') || 'assigned'}) — read them: atc standing`
|
|
553
|
+
: '';
|
|
554
|
+
return out(json, `checked in as ${r.body.callsign} · ${c.sessions ?? 0} on board, ${c.claims ?? 0} claims, ${c.notams ?? 0} notams${standing}`, r.body);
|
|
113
555
|
}
|
|
114
556
|
const session = requireSession();
|
|
115
557
|
const tok = session.sessionToken;
|
|
@@ -119,10 +561,18 @@ async function main(argv) {
|
|
|
119
561
|
if (!r.ok)
|
|
120
562
|
fail(r.body?.error ?? `brief failed (${r.status})`, 2);
|
|
121
563
|
const b = r.body;
|
|
122
|
-
const
|
|
123
|
-
`
|
|
564
|
+
const standingLine = b.standing
|
|
565
|
+
? `standing orders (${b.standing.source}): ${b.standing.orders.map((o) => o.name).join(', ')}` +
|
|
566
|
+
`${b.standing.truncated ? ' (truncated here — atc standing for full)' : ''}\n${b.standing.body}\n---\n`
|
|
567
|
+
: '';
|
|
568
|
+
const who = (x) => `${x.callsign}(${x.code ?? x.statusText ?? x.status ?? 'working'}${x.claimCount ? `, ${x.claimCount} claims` : ''})`;
|
|
569
|
+
const human = standingLine +
|
|
570
|
+
`roster: ${b.roster.map(who).join(', ') || '(none)'}\n` +
|
|
571
|
+
`claims: ${b.claims.map((x) => `${x.callsign}:${x.glob}`).join(', ') || '(none)'}` +
|
|
572
|
+
(b.counts.claims > b.claims.length ? ` (+${b.counts.claims - b.claims.length} more on board)` : '') +
|
|
573
|
+
`\n` +
|
|
124
574
|
renderConflicts(b.conflictsWithMine) +
|
|
125
|
-
`\nnotams: ${b.notams.length}
|
|
575
|
+
`\nnotams: ${b.notams.length} shown (pinned + recent; truncated bodies — 'atc note show <id>' for full)`;
|
|
126
576
|
return out(json, human, b);
|
|
127
577
|
}
|
|
128
578
|
case 'squawk': {
|
|
@@ -154,9 +604,20 @@ async function main(argv) {
|
|
|
154
604
|
return out(json, `cleared: ${(r.body.cleared ?? []).join(', ') || '(none)'}`, r.body);
|
|
155
605
|
}
|
|
156
606
|
case 'note': {
|
|
607
|
+
if (positionals[0] === 'show') {
|
|
608
|
+
const id = positionals[1];
|
|
609
|
+
if (!id)
|
|
610
|
+
fail('usage: atc note show <id>');
|
|
611
|
+
const r = await (0, client_1.api)(cfg, tok, 'GET', `/v1/notams/${encodeURIComponent(id)}`);
|
|
612
|
+
if (!r.ok)
|
|
613
|
+
fail(r.body?.error ?? `note show failed (${r.status})`, 2);
|
|
614
|
+
const n = r.body.notam;
|
|
615
|
+
return out(json, `${n.pinned ? '📌 ' : ''}[${n.id}] by ${n.author} at ${n.createdAt}` +
|
|
616
|
+
`${n.tags?.length ? ' #' + n.tags.join(' #') : ''}${n.superseded ? ' (superseded)' : ''}\n${n.body}`, r.body);
|
|
617
|
+
}
|
|
157
618
|
const body = positionals.join(' ');
|
|
158
619
|
if (!body)
|
|
159
|
-
fail('usage: atc note "<body>" [--tag t] [--pin] [--supersede <id>]');
|
|
620
|
+
fail('usage: atc note "<body>" [--tag t] [--pin] [--supersede <id>] | atc note show <id>');
|
|
160
621
|
const supersedes = typeof flags.supersede === 'string' ? flags.supersede : undefined;
|
|
161
622
|
const r = await (0, client_1.api)(cfg, tok, 'POST', '/v1/notams', { body, tags, pinned: flags.pin === true, supersedes });
|
|
162
623
|
if (!r.ok)
|
package/dist/init.js
ADDED
|
@@ -0,0 +1,447 @@
|
|
|
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.runInit = runInit;
|
|
37
|
+
/**
|
|
38
|
+
* atc init — wire a project to Agent Tower in one command.
|
|
39
|
+
*
|
|
40
|
+
* Steps (idempotent, fail-open per step):
|
|
41
|
+
* 1. Plan + confirm (unless --yes)
|
|
42
|
+
* 2. .mcp.json — create or merge agent-tower entry
|
|
43
|
+
* 3. AGENTS.md — append compliance block if absent
|
|
44
|
+
* 4. .gitignore — ensure .atc/ (and, with --save-token, the local settings file)
|
|
45
|
+
* 5. --save-token: persist ATC_TOKEN into .claude/settings.local.json (gitignored)
|
|
46
|
+
* 6. Checkin + brief if a frequency token is available
|
|
47
|
+
*/
|
|
48
|
+
const fs = __importStar(require("node:fs"));
|
|
49
|
+
const path = __importStar(require("node:path"));
|
|
50
|
+
const readline = __importStar(require("node:readline"));
|
|
51
|
+
const client_1 = require("./client");
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Embedded compliance block (adapted from templates/agent-tower.md).
|
|
54
|
+
// The CLI ships standalone via npm and cannot read repo files at runtime.
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
const COMPLIANCE_BLOCK = `
|
|
57
|
+
## Agent Tower coordination
|
|
58
|
+
|
|
59
|
+
You are one of several agents working this repository. Coordinate through the
|
|
60
|
+
**Agent Tower** so you don't collide with other agents' edits, duplicate work, or
|
|
61
|
+
re-decide settled questions. Prefer the \`atc_*\` MCP tools; if they're unavailable,
|
|
62
|
+
use the \`atc\` CLI (same actions).
|
|
63
|
+
|
|
64
|
+
**At the start of a session:**
|
|
65
|
+
- \`atc_checkin\` (state your task) — establishes your callsign.
|
|
66
|
+
- \`atc_brief\` — read the roster, active claims, conflicts touching you, and NOTAMs
|
|
67
|
+
(the decision log) so you catch up on what's already been decided.
|
|
68
|
+
- **Brief and checkin return this frequency's standing orders first — read and follow
|
|
69
|
+
them.** For the full text: \`atc standing\` / \`atc_standing\`.
|
|
70
|
+
|
|
71
|
+
**Before editing any area:**
|
|
72
|
+
- \`atc_claim "<glob>"\` (e.g. \`src/auth/**\`) **before** you start editing it.
|
|
73
|
+
- If the response lists **conflicts**, another agent holds overlapping scope —
|
|
74
|
+
**do not edit.** \`atc_squawk\` with code \`blocked\` and surface it to the human.
|
|
75
|
+
If it can't be resolved, \`atc_squawk\` code \`mayday\` and stop.
|
|
76
|
+
|
|
77
|
+
**As you work:**
|
|
78
|
+
- \`atc_squawk "<what you're doing>"\` at checkpoints — it's also your heartbeat and
|
|
79
|
+
shows conflicts that have appeared on your claims.
|
|
80
|
+
|
|
81
|
+
**When you settle a cross-cutting decision** (a contract, convention, TTL, etc.):
|
|
82
|
+
- \`atc_note "<the decision>"\` so the next agent reads it in their brief. Pin
|
|
83
|
+
durable contracts.
|
|
84
|
+
|
|
85
|
+
**When you finish / end the session:**
|
|
86
|
+
- \`atc_clear\` to release scope you no longer need; \`atc_checkout\` on session end.
|
|
87
|
+
|
|
88
|
+
Claims are **advisory** — the Tower detects and surfaces conflicts; it never blocks
|
|
89
|
+
your edits. Respect them anyway: that's the whole point.
|
|
90
|
+
|
|
91
|
+
<!-- CLI equivalents: atc checkin --task "…" · atc brief · atc claim "<glob>" ·
|
|
92
|
+
atc squawk "…" [--code blocked|mayday] · atc note "…" [--pin] · atc clear · atc checkout
|
|
93
|
+
Standing orders: atc standing -->
|
|
94
|
+
`.trimStart();
|
|
95
|
+
const COMPLIANCE_MARKER = '## Agent Tower coordination';
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// MCP entry we always want present
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
const MCP_SERVER_KEY = 'agent-tower';
|
|
100
|
+
const MCP_SERVER_ENTRY = {
|
|
101
|
+
command: 'npx',
|
|
102
|
+
args: ['-y', '@agenttower/mcp'],
|
|
103
|
+
env: {
|
|
104
|
+
ATC_API: '${ATC_API}',
|
|
105
|
+
ATC_TOKEN: '${ATC_TOKEN}',
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
// Helpers
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
function readText(file) {
|
|
112
|
+
try {
|
|
113
|
+
return fs.readFileSync(file, 'utf8');
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function readJson(file) {
|
|
120
|
+
const text = readText(file);
|
|
121
|
+
if (!text)
|
|
122
|
+
return null;
|
|
123
|
+
try {
|
|
124
|
+
return JSON.parse(text);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/** Deep-equal check limited to what we need for the MCP entry. */
|
|
131
|
+
function mcpEntryMatches(existing) {
|
|
132
|
+
if (!existing || typeof existing !== 'object')
|
|
133
|
+
return false;
|
|
134
|
+
const e = existing;
|
|
135
|
+
if (e.command !== 'npx')
|
|
136
|
+
return false;
|
|
137
|
+
const args = e.args;
|
|
138
|
+
if (!Array.isArray(args))
|
|
139
|
+
return false;
|
|
140
|
+
return args.includes('@agenttower/mcp');
|
|
141
|
+
}
|
|
142
|
+
async function confirm(question) {
|
|
143
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
144
|
+
return new Promise((resolve) => {
|
|
145
|
+
rl.question(question, (answer) => {
|
|
146
|
+
rl.close();
|
|
147
|
+
resolve(answer.trim().toLowerCase() === 'y');
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
function planMcp(cwd) {
|
|
152
|
+
const file = path.join(cwd, '.mcp.json');
|
|
153
|
+
const existing = readJson(file);
|
|
154
|
+
if (!existing) {
|
|
155
|
+
return {
|
|
156
|
+
action: 'created',
|
|
157
|
+
preview: `+ mcpServers["${MCP_SERVER_KEY}"] = { command: "npx", args: ["-y", "@agenttower/mcp"], env: {...} }`,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
const servers = existing.mcpServers;
|
|
161
|
+
if (servers && mcpEntryMatches(servers[MCP_SERVER_KEY])) {
|
|
162
|
+
return { action: 'unchanged' };
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
action: 'updated',
|
|
166
|
+
preview: `+ mcpServers["${MCP_SERVER_KEY}"] = { command: "npx", args: ["-y", "@agenttower/mcp"], env: {...} }`,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function applyMcp(cwd) {
|
|
170
|
+
const file = path.join(cwd, '.mcp.json');
|
|
171
|
+
const existing = readJson(file) ?? {};
|
|
172
|
+
const servers = existing.mcpServers ?? {};
|
|
173
|
+
servers[MCP_SERVER_KEY] = MCP_SERVER_ENTRY;
|
|
174
|
+
const updated = { ...existing, mcpServers: servers };
|
|
175
|
+
fs.writeFileSync(file, JSON.stringify(updated, null, 2) + '\n');
|
|
176
|
+
}
|
|
177
|
+
function planAgentsMd(cwd) {
|
|
178
|
+
const file = path.join(cwd, 'AGENTS.md');
|
|
179
|
+
const existing = readText(file);
|
|
180
|
+
if (existing && existing.includes(COMPLIANCE_MARKER)) {
|
|
181
|
+
return { action: 'unchanged' };
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
action: existing ? 'updated' : 'created',
|
|
185
|
+
preview: `+ ## Agent Tower coordination\\n+ (compliance block, ~30 lines)`,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function applyAgentsMd(cwd) {
|
|
189
|
+
const file = path.join(cwd, 'AGENTS.md');
|
|
190
|
+
const existing = readText(file);
|
|
191
|
+
if (existing && existing.includes(COMPLIANCE_MARKER))
|
|
192
|
+
return; // idempotent guard
|
|
193
|
+
const separator = existing && !existing.endsWith('\n\n') ? '\n' : '';
|
|
194
|
+
const content = existing ? existing + separator + COMPLIANCE_BLOCK : COMPLIANCE_BLOCK;
|
|
195
|
+
fs.writeFileSync(file, content);
|
|
196
|
+
}
|
|
197
|
+
function planGitignore(cwd, needed) {
|
|
198
|
+
const file = path.join(cwd, '.gitignore');
|
|
199
|
+
const existing = readText(file);
|
|
200
|
+
if (existing) {
|
|
201
|
+
const lines = existing.split('\n').map((l) => l.trim());
|
|
202
|
+
const missing = needed.filter((n) => !lines.includes(n));
|
|
203
|
+
if (missing.length === 0)
|
|
204
|
+
return { action: 'unchanged' };
|
|
205
|
+
return { action: 'updated', preview: missing.map((m) => `+ ${m}`).join(' ') };
|
|
206
|
+
}
|
|
207
|
+
return { action: 'created', preview: needed.map((m) => `+ ${m}`).join(' ') };
|
|
208
|
+
}
|
|
209
|
+
function applyGitignore(cwd, needed) {
|
|
210
|
+
const file = path.join(cwd, '.gitignore');
|
|
211
|
+
const existing = readText(file);
|
|
212
|
+
const lines = (existing ?? '').split('\n').map((l) => l.trim());
|
|
213
|
+
const missing = needed.filter((n) => !lines.includes(n));
|
|
214
|
+
if (existing && missing.length === 0)
|
|
215
|
+
return; // idempotent guard
|
|
216
|
+
const base = existing ? (existing.endsWith('\n') ? existing : existing + '\n') : '';
|
|
217
|
+
fs.writeFileSync(file, base + missing.join('\n') + '\n');
|
|
218
|
+
}
|
|
219
|
+
// ---------------------------------------------------------------------------
|
|
220
|
+
// --save-token: persist ATC_TOKEN into .claude/settings.local.json so this
|
|
221
|
+
// repo's Claude Code sessions get their own frequency without a global export.
|
|
222
|
+
// The file is local-only and we make sure it's gitignored above.
|
|
223
|
+
// ---------------------------------------------------------------------------
|
|
224
|
+
const SETTINGS_REL = path.join('.claude', 'settings.local.json');
|
|
225
|
+
function planSettings(cwd, token) {
|
|
226
|
+
const file = path.join(cwd, SETTINGS_REL);
|
|
227
|
+
const existing = readJson(file);
|
|
228
|
+
const env = existing?.env ?? {};
|
|
229
|
+
const masked = `ATC_TOKEN=atcf_…${token.slice(-4)}`;
|
|
230
|
+
if (env.ATC_TOKEN === token)
|
|
231
|
+
return { action: 'unchanged' };
|
|
232
|
+
return { action: existing ? 'updated' : 'created', preview: `+ env.${masked}` };
|
|
233
|
+
}
|
|
234
|
+
function applySettings(cwd, token, apiUrl) {
|
|
235
|
+
const file = path.join(cwd, SETTINGS_REL);
|
|
236
|
+
const existing = readJson(file) ?? {};
|
|
237
|
+
const env = (existing.env ?? {});
|
|
238
|
+
env.ATC_TOKEN = token;
|
|
239
|
+
if (apiUrl)
|
|
240
|
+
env.ATC_API = apiUrl; // only when targeting a non-default API
|
|
241
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
242
|
+
fs.writeFileSync(file, JSON.stringify({ ...existing, env }, null, 2) + '\n', { mode: 0o600 });
|
|
243
|
+
try {
|
|
244
|
+
fs.chmodSync(file, 0o600);
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
/* best-effort on platforms without chmod */
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
async function runInit(args) {
|
|
251
|
+
const cwd = process.cwd();
|
|
252
|
+
const json = args.json === true;
|
|
253
|
+
const token = args.token || process.env.ATC_TOKEN || '';
|
|
254
|
+
if (args.saveToken && !token) {
|
|
255
|
+
process.stderr.write("atc: --save-token needs a token — pass --token <atcf_…> or set ATC_TOKEN\n");
|
|
256
|
+
process.exit(1);
|
|
257
|
+
}
|
|
258
|
+
// -------------------------------------------------------------------------
|
|
259
|
+
// Step 1: Plan
|
|
260
|
+
// -------------------------------------------------------------------------
|
|
261
|
+
const gitignoreLines = ['.atc/', ...(args.saveToken ? ['.claude/settings.local.json'] : [])];
|
|
262
|
+
const mcpPlan = planMcp(cwd);
|
|
263
|
+
const agentsPlan = planAgentsMd(cwd);
|
|
264
|
+
const gitignorePlan = planGitignore(cwd, gitignoreLines);
|
|
265
|
+
const settingsPlan = args.saveToken ? planSettings(cwd, token) : null;
|
|
266
|
+
const steps = [
|
|
267
|
+
{ file: '.mcp.json', action: mcpPlan.action, preview: mcpPlan.preview },
|
|
268
|
+
{ file: 'AGENTS.md', action: agentsPlan.action, preview: agentsPlan.preview },
|
|
269
|
+
{ file: '.gitignore', action: gitignorePlan.action, preview: gitignorePlan.preview },
|
|
270
|
+
...(settingsPlan
|
|
271
|
+
? [{ file: SETTINGS_REL, action: settingsPlan.action, preview: settingsPlan.preview }]
|
|
272
|
+
: []),
|
|
273
|
+
];
|
|
274
|
+
if (!json) {
|
|
275
|
+
process.stdout.write('atc init — changes planned:\n\n');
|
|
276
|
+
for (const s of steps) {
|
|
277
|
+
const icon = s.action === 'unchanged' ? '·' : s.action === 'created' ? '+' : '~';
|
|
278
|
+
process.stdout.write(` ${icon} ${s.file.padEnd(14)} ${s.action}\n`);
|
|
279
|
+
if (s.preview && s.action !== 'unchanged') {
|
|
280
|
+
process.stdout.write(` ${s.preview}\n`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
process.stdout.write('\n');
|
|
284
|
+
}
|
|
285
|
+
const anyChanges = steps.some((s) => s.action !== 'unchanged');
|
|
286
|
+
if (!args.yes) {
|
|
287
|
+
if (!anyChanges) {
|
|
288
|
+
if (!json)
|
|
289
|
+
process.stdout.write('All files already up to date. Nothing to do.\n');
|
|
290
|
+
else
|
|
291
|
+
process.stdout.write(JSON.stringify({ actions: steps, checkin: null }, null, 2) + '\n');
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
// Ask on stdin only when changes would be made
|
|
295
|
+
const proceed = await confirm('Proceed? [y/N] ');
|
|
296
|
+
if (!proceed) {
|
|
297
|
+
if (!json)
|
|
298
|
+
process.stdout.write('Aborted.\n');
|
|
299
|
+
else
|
|
300
|
+
process.stdout.write(JSON.stringify({ actions: steps, checkin: null, aborted: true }, null, 2) + '\n');
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
if (!anyChanges) {
|
|
305
|
+
if (!json)
|
|
306
|
+
process.stdout.write('All files already up to date. Nothing to do.\n');
|
|
307
|
+
else
|
|
308
|
+
process.stdout.write(JSON.stringify({ actions: steps, checkin: null }, null, 2) + '\n');
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
// -------------------------------------------------------------------------
|
|
312
|
+
// Step 2: .mcp.json
|
|
313
|
+
// -------------------------------------------------------------------------
|
|
314
|
+
if (mcpPlan.action !== 'unchanged') {
|
|
315
|
+
try {
|
|
316
|
+
applyMcp(cwd);
|
|
317
|
+
if (!json)
|
|
318
|
+
process.stdout.write(` wrote .mcp.json\n`);
|
|
319
|
+
}
|
|
320
|
+
catch (err) {
|
|
321
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
322
|
+
process.stderr.write(`atc: warning: could not write .mcp.json: ${msg}\n`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
// -------------------------------------------------------------------------
|
|
326
|
+
// Step 3: AGENTS.md
|
|
327
|
+
// -------------------------------------------------------------------------
|
|
328
|
+
if (agentsPlan.action !== 'unchanged') {
|
|
329
|
+
try {
|
|
330
|
+
applyAgentsMd(cwd);
|
|
331
|
+
if (!json)
|
|
332
|
+
process.stdout.write(` wrote AGENTS.md\n`);
|
|
333
|
+
}
|
|
334
|
+
catch (err) {
|
|
335
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
336
|
+
process.stderr.write(`atc: warning: could not write AGENTS.md: ${msg}\n`);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
// Note about CLAUDE.md if it exists but doesn't reference AGENTS.md
|
|
340
|
+
try {
|
|
341
|
+
const claudeMd = readText(path.join(cwd, 'CLAUDE.md'));
|
|
342
|
+
if (claudeMd !== null &&
|
|
343
|
+
!claudeMd.includes('@AGENTS.md') &&
|
|
344
|
+
!claudeMd.includes(COMPLIANCE_MARKER)) {
|
|
345
|
+
if (!json) {
|
|
346
|
+
process.stdout.write('\n note: CLAUDE.md exists but does not include @AGENTS.md.\n' +
|
|
347
|
+
' Consider adding "@AGENTS.md" to CLAUDE.md so Claude Code picks up the block.\n\n');
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
catch {
|
|
352
|
+
// best-effort
|
|
353
|
+
}
|
|
354
|
+
// -------------------------------------------------------------------------
|
|
355
|
+
// Step 4: .gitignore
|
|
356
|
+
// -------------------------------------------------------------------------
|
|
357
|
+
if (gitignorePlan.action !== 'unchanged') {
|
|
358
|
+
try {
|
|
359
|
+
applyGitignore(cwd, gitignoreLines);
|
|
360
|
+
if (!json)
|
|
361
|
+
process.stdout.write(` wrote .gitignore\n`);
|
|
362
|
+
}
|
|
363
|
+
catch (err) {
|
|
364
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
365
|
+
process.stderr.write(`atc: warning: could not write .gitignore: ${msg}\n`);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
// -------------------------------------------------------------------------
|
|
369
|
+
// Step 5: --save-token → .claude/settings.local.json (gitignored above)
|
|
370
|
+
// -------------------------------------------------------------------------
|
|
371
|
+
if (settingsPlan && settingsPlan.action !== 'unchanged') {
|
|
372
|
+
try {
|
|
373
|
+
const cfgForApi = (0, client_1.loadConfig)();
|
|
374
|
+
const apiOverride = process.env.ATC_API ? cfgForApi.api : undefined;
|
|
375
|
+
applySettings(cwd, token, apiOverride);
|
|
376
|
+
if (!json)
|
|
377
|
+
process.stdout.write(` wrote ${SETTINGS_REL} (token saved for this repo's sessions)\n`);
|
|
378
|
+
}
|
|
379
|
+
catch (err) {
|
|
380
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
381
|
+
process.stderr.write(`atc: warning: could not write ${SETTINGS_REL}: ${msg}\n`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
// -------------------------------------------------------------------------
|
|
385
|
+
// Step 6: Checkin + brief (fail-open)
|
|
386
|
+
// -------------------------------------------------------------------------
|
|
387
|
+
let checkinResult = null;
|
|
388
|
+
if (token) {
|
|
389
|
+
const cfg = (0, client_1.loadConfig)();
|
|
390
|
+
// If --token was passed, use it in-process only — never written to any file
|
|
391
|
+
const effectiveCfg = args.token ? { ...cfg, token: args.token } : cfg;
|
|
392
|
+
if (!json)
|
|
393
|
+
process.stdout.write('\nChecking in to Agent Tower...\n');
|
|
394
|
+
try {
|
|
395
|
+
const r = await (0, client_1.api)(effectiveCfg, effectiveCfg.token, 'POST', '/v1/sessions', {
|
|
396
|
+
workspaceId: (0, client_1.workspaceId)(),
|
|
397
|
+
cli: process.env.ATC_CLI || 'cli',
|
|
398
|
+
worktree: cwd,
|
|
399
|
+
branch: (0, client_1.currentBranch)(),
|
|
400
|
+
task: 'atc init',
|
|
401
|
+
});
|
|
402
|
+
if (r.ok) {
|
|
403
|
+
(0, client_1.saveSession)({ callsign: r.body.callsign, sessionToken: r.body.sessionToken, api: effectiveCfg.api });
|
|
404
|
+
const c = r.body.brief?.counts ?? {};
|
|
405
|
+
const so = r.body.brief?.standing;
|
|
406
|
+
const standing = so
|
|
407
|
+
? `\n⚑ standing orders present (${(so.orders ?? []).map((o) => o.name).join(', ') || 'assigned'}) — read them: atc standing`
|
|
408
|
+
: '';
|
|
409
|
+
checkinResult = r.body;
|
|
410
|
+
if (!json) {
|
|
411
|
+
process.stdout.write(`checked in as ${r.body.callsign} · ${c.sessions ?? 0} on board, ` +
|
|
412
|
+
`${c.claims ?? 0} claims, ${c.notams ?? 0} notams${standing}\n`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
const msg = r.body?.error ?? `checkin failed (${r.status})`;
|
|
417
|
+
process.stderr.write(`atc: warning: could not check in: ${msg}\n`);
|
|
418
|
+
if (!json) {
|
|
419
|
+
process.stdout.write('\nNext steps:\n' +
|
|
420
|
+
' 1. Set ATC_TOKEN to your frequency token (get one at https://app.agenttower.dev)\n' +
|
|
421
|
+
' 2. Run: atc checkin\n');
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
catch (err) {
|
|
426
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
427
|
+
process.stderr.write(`atc: warning: checkin failed (network error): ${msg}\n`);
|
|
428
|
+
if (!json) {
|
|
429
|
+
process.stdout.write('\nNext steps:\n' +
|
|
430
|
+
' 1. Ensure ATC_API is reachable (default: https://api.agenttower.dev)\n' +
|
|
431
|
+
' 2. Set ATC_TOKEN to your frequency token\n' +
|
|
432
|
+
' 3. Run: atc checkin\n');
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
if (!json) {
|
|
438
|
+
process.stdout.write('\nDone. To land on the board:\n' +
|
|
439
|
+
' 1. Get a frequency token at https://app.agenttower.dev\n' +
|
|
440
|
+
' 2. export ATC_TOKEN=<your-token>\n' +
|
|
441
|
+
' 3. Run: atc checkin\n');
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
if (json) {
|
|
445
|
+
process.stdout.write(JSON.stringify({ actions: steps, checkin: checkinResult }, null, 2) + '\n');
|
|
446
|
+
}
|
|
447
|
+
}
|
package/package.json
CHANGED
|
@@ -1,16 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agenttower/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "atc — the Agent Tower CLI. Coordinate multiple coding agents on one repo (claims, conflicts, decision log).",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"repository": {
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/manateeit/agent-tower.git",
|
|
9
|
+
"directory": "cli"
|
|
10
|
+
},
|
|
7
11
|
"homepage": "https://app.agenttower.dev",
|
|
8
|
-
"keywords": [
|
|
12
|
+
"keywords": [
|
|
13
|
+
"agent-tower",
|
|
14
|
+
"atc",
|
|
15
|
+
"coding-agents",
|
|
16
|
+
"coordination",
|
|
17
|
+
"cli",
|
|
18
|
+
"claude",
|
|
19
|
+
"mcp"
|
|
20
|
+
],
|
|
9
21
|
"type": "commonjs",
|
|
10
|
-
"bin": {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
"
|
|
22
|
+
"bin": {
|
|
23
|
+
"atc": "bin/atc.js"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"bin",
|
|
27
|
+
"dist",
|
|
28
|
+
"README.md"
|
|
29
|
+
],
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=18"
|
|
32
|
+
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
14
36
|
"scripts": {
|
|
15
37
|
"build": "tsc",
|
|
16
38
|
"typecheck": "tsc --noEmit",
|