@agenttower/cli 0.3.0 → 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/bin/atc.js CHANGED
File without changes
package/dist/index.js CHANGED
@@ -43,7 +43,7 @@ const child_process = __importStar(require("node:child_process"));
43
43
  const client_1 = require("./client");
44
44
  const init_1 = require("./init");
45
45
  const human_1 = require("./human");
46
- const VERSION = '0.3.0';
46
+ const VERSION = '0.3.1';
47
47
  const COMMANDS = [
48
48
  'init', 'checkin', 'brief', 'standing', 'squawk', 'claim', 'clear',
49
49
  'note', 'notes', 'checkout', 'config',
@@ -58,7 +58,7 @@ function parseArgs(argv) {
58
58
  if (a.startsWith('--')) {
59
59
  const key = a.slice(2);
60
60
  const next = argv[i + 1];
61
- const boolFlags = ['pin', 'json', 'yes', 'manual', 'draft', 'activate', 'default', 'none'];
61
+ const boolFlags = ['pin', 'json', 'yes', 'manual', 'draft', 'activate', 'default', 'none', 'save-token'];
62
62
  if (boolFlags.includes(key)) {
63
63
  flags[key] = true;
64
64
  }
@@ -124,7 +124,8 @@ function printHelp() {
124
124
  ` standing assign --project <p> <name1,name2,…> | --none\n` +
125
125
  ` standing propose <name> (--body "…" | --file f) (agent plane, uses ATC_TOKEN)\n\n` +
126
126
  `Agent commands (require ATC_API + ATC_TOKEN):\n` +
127
- ` init [--token <freq-token>] [--yes] wire a project to Agent Tower\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` +
128
129
  ` checkin [--task "..."] [--as <callsign>] [--cli <name>]\n` +
129
130
  ` brief\n` +
130
131
  ` standing print the frequency's standing orders in full\n` +
@@ -203,6 +204,7 @@ async function main(argv) {
203
204
  if (cmd === 'init') {
204
205
  return (0, init_1.runInit)({
205
206
  token: typeof flags.token === 'string' ? flags.token : undefined,
207
+ saveToken: flags['save-token'] === true,
206
208
  yes: flags.yes === true,
207
209
  json,
208
210
  });
package/dist/init.js CHANGED
@@ -41,8 +41,9 @@ exports.runInit = runInit;
41
41
  * 1. Plan + confirm (unless --yes)
42
42
  * 2. .mcp.json — create or merge agent-tower entry
43
43
  * 3. AGENTS.md — append compliance block if absent
44
- * 4. .gitignore — ensure .atc/ line exists
45
- * 5. Checkin + brief if a frequency token is available
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
46
47
  */
47
48
  const fs = __importStar(require("node:fs"));
48
49
  const path = __importStar(require("node:path"));
@@ -193,45 +194,82 @@ function applyAgentsMd(cwd) {
193
194
  const content = existing ? existing + separator + COMPLIANCE_BLOCK : COMPLIANCE_BLOCK;
194
195
  fs.writeFileSync(file, content);
195
196
  }
196
- function planGitignore(cwd) {
197
+ function planGitignore(cwd, needed) {
197
198
  const file = path.join(cwd, '.gitignore');
198
199
  const existing = readText(file);
199
200
  if (existing) {
200
- // Match exact line .atc/ (with or without trailing newline variations)
201
201
  const lines = existing.split('\n').map((l) => l.trim());
202
- if (lines.includes('.atc/'))
202
+ const missing = needed.filter((n) => !lines.includes(n));
203
+ if (missing.length === 0)
203
204
  return { action: 'unchanged' };
204
- return { action: 'updated', preview: '+ .atc/' };
205
+ return { action: 'updated', preview: missing.map((m) => `+ ${m}`).join(' ') };
205
206
  }
206
- return { action: 'created', preview: '+ .atc/' };
207
+ return { action: 'created', preview: needed.map((m) => `+ ${m}`).join(' ') };
207
208
  }
208
- function applyGitignore(cwd) {
209
+ function applyGitignore(cwd, needed) {
209
210
  const file = path.join(cwd, '.gitignore');
210
211
  const existing = readText(file);
211
- if (existing) {
212
- const lines = existing.split('\n').map((l) => l.trim());
213
- if (lines.includes('.atc/'))
214
- return; // idempotent guard
215
- const appended = existing.endsWith('\n') ? existing + '.atc/\n' : existing + '\n.atc/\n';
216
- fs.writeFileSync(file, appended);
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);
217
245
  }
218
- else {
219
- fs.writeFileSync(file, '.atc/\n');
246
+ catch {
247
+ /* best-effort on platforms without chmod */
220
248
  }
221
249
  }
222
250
  async function runInit(args) {
223
251
  const cwd = process.cwd();
224
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
+ }
225
258
  // -------------------------------------------------------------------------
226
259
  // Step 1: Plan
227
260
  // -------------------------------------------------------------------------
261
+ const gitignoreLines = ['.atc/', ...(args.saveToken ? ['.claude/settings.local.json'] : [])];
228
262
  const mcpPlan = planMcp(cwd);
229
263
  const agentsPlan = planAgentsMd(cwd);
230
- const gitignorePlan = planGitignore(cwd);
264
+ const gitignorePlan = planGitignore(cwd, gitignoreLines);
265
+ const settingsPlan = args.saveToken ? planSettings(cwd, token) : null;
231
266
  const steps = [
232
267
  { file: '.mcp.json', action: mcpPlan.action, preview: mcpPlan.preview },
233
268
  { file: 'AGENTS.md', action: agentsPlan.action, preview: agentsPlan.preview },
234
269
  { file: '.gitignore', action: gitignorePlan.action, preview: gitignorePlan.preview },
270
+ ...(settingsPlan
271
+ ? [{ file: SETTINGS_REL, action: settingsPlan.action, preview: settingsPlan.preview }]
272
+ : []),
235
273
  ];
236
274
  if (!json) {
237
275
  process.stdout.write('atc init — changes planned:\n\n');
@@ -318,7 +356,7 @@ async function runInit(args) {
318
356
  // -------------------------------------------------------------------------
319
357
  if (gitignorePlan.action !== 'unchanged') {
320
358
  try {
321
- applyGitignore(cwd);
359
+ applyGitignore(cwd, gitignoreLines);
322
360
  if (!json)
323
361
  process.stdout.write(` wrote .gitignore\n`);
324
362
  }
@@ -328,9 +366,24 @@ async function runInit(args) {
328
366
  }
329
367
  }
330
368
  // -------------------------------------------------------------------------
331
- // Step 5: Checkin + brief (fail-open)
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)
332
386
  // -------------------------------------------------------------------------
333
- const token = args.token || process.env.ATC_TOKEN || '';
334
387
  let checkinResult = null;
335
388
  if (token) {
336
389
  const cfg = (0, client_1.loadConfig)();
@@ -349,8 +402,9 @@ async function runInit(args) {
349
402
  if (r.ok) {
350
403
  (0, client_1.saveSession)({ callsign: r.body.callsign, sessionToken: r.body.sessionToken, api: effectiveCfg.api });
351
404
  const c = r.body.brief?.counts ?? {};
352
- const standing = r.body.brief?.standing
353
- ? `\n⚑ standing orders present (v${r.body.brief.standing.version}) — read them: atc standing`
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`
354
408
  : '';
355
409
  checkinResult = r.body;
356
410
  if (!json) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agenttower/cli",
3
- "version": "0.3.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
6
  "repository": {