@integrity-labs/agt-cli 0.6.6

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.
@@ -0,0 +1,3223 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ AGT_HOST,
4
+ CHANNEL_REGISTRY,
5
+ DEPLOYMENT_TEMPLATES,
6
+ SLACK_SCOPE_CATEGORY_LABELS,
7
+ SLACK_SCOPE_PRESETS,
8
+ SlackApiError,
9
+ api,
10
+ createSlackApp,
11
+ detectDrift,
12
+ exchangeApiKey,
13
+ extractFrontmatter,
14
+ generateCharterMd,
15
+ generateSlackAppManifest,
16
+ generateToolsMd,
17
+ getActiveTeam,
18
+ getAllChannelIds,
19
+ getApiKey,
20
+ getChannel,
21
+ getDefaultSlackScopes,
22
+ getFramework,
23
+ getHostId,
24
+ getScopesByCategory,
25
+ getTemplate,
26
+ lintAll,
27
+ lintCharter,
28
+ lintTools,
29
+ provision,
30
+ renderTemplate,
31
+ resolveChannels,
32
+ serializeManifestForSlackCli,
33
+ setActiveTeam
34
+ } from "../chunk-EUF2V4N5.js";
35
+
36
+ // src/bin/agt.ts
37
+ import { join as join10 } from "path";
38
+ import { homedir as homedir2 } from "os";
39
+ import { Command } from "commander";
40
+
41
+ // src/lib/globals.ts
42
+ import chalk from "chalk";
43
+ var _jsonMode = false;
44
+ function setJsonMode(enabled) {
45
+ _jsonMode = enabled;
46
+ if (enabled) {
47
+ chalk.level = 0;
48
+ }
49
+ }
50
+ function isJsonMode() {
51
+ return _jsonMode;
52
+ }
53
+ function jsonOutput(data) {
54
+ console.log(JSON.stringify(data, null, 2));
55
+ }
56
+
57
+ // src/commands/whoami.ts
58
+ import chalk3 from "chalk";
59
+ import ora from "ora";
60
+
61
+ // src/lib/output.ts
62
+ import chalk2 from "chalk";
63
+ import Table from "cli-table3";
64
+ function success(msg) {
65
+ console.log(chalk2.green(`\u2714 ${msg}`));
66
+ }
67
+ function error(msg) {
68
+ console.error(chalk2.red(`\u2718 ${msg}`));
69
+ }
70
+ function warn(msg) {
71
+ console.warn(chalk2.yellow(`\u26A0 ${msg}`));
72
+ }
73
+ function info(msg) {
74
+ console.log(chalk2.cyan(`\u2139 ${msg}`));
75
+ }
76
+ function table(headers, rows) {
77
+ const t = new Table({
78
+ head: headers.map((h) => chalk2.bold.cyan(h)),
79
+ style: { head: [], border: [] }
80
+ });
81
+ for (const row of rows) {
82
+ t.push(row);
83
+ }
84
+ console.log(t.toString());
85
+ }
86
+
87
+ // src/commands/whoami.ts
88
+ async function whoamiCommand() {
89
+ const json = isJsonMode();
90
+ const apiKey = getApiKey();
91
+ if (!apiKey) {
92
+ if (json) {
93
+ jsonOutput({ ok: false, error: "AGT_API_KEY is not set" });
94
+ } else {
95
+ error("AGT_API_KEY is not set. Export it with your host API key (tlk_...)");
96
+ }
97
+ process.exitCode = 1;
98
+ return;
99
+ }
100
+ const spinner = ora({ text: "Exchanging API key\u2026", isSilent: json });
101
+ spinner.start();
102
+ try {
103
+ const exchange = await exchangeApiKey(apiKey);
104
+ spinner.stop();
105
+ if (json) {
106
+ jsonOutput({
107
+ ok: true,
108
+ api_key_prefix: apiKey.slice(0, 8) + "****",
109
+ host: AGT_HOST ?? null,
110
+ host_id: exchange.hostId,
111
+ team: exchange.teamSlug,
112
+ team_id: exchange.teamId,
113
+ email: exchange.userEmail
114
+ });
115
+ return;
116
+ }
117
+ success("Authenticated via API key");
118
+ info(`API Key: ${chalk3.bold(apiKey.slice(0, 8) + "****")}`);
119
+ info(`Host: ${chalk3.bold(AGT_HOST ?? "not set")}`);
120
+ info(`Host ID: ${exchange.hostId}`);
121
+ info(`Team: ${chalk3.bold(exchange.teamSlug ?? exchange.teamId)}`);
122
+ info(`User: ${chalk3.bold(exchange.userEmail ?? "unknown")}`);
123
+ } catch (err) {
124
+ spinner.fail("Failed to exchange API key.");
125
+ if (json) {
126
+ jsonOutput({ ok: false, error: err.message });
127
+ } else {
128
+ error(err.message);
129
+ }
130
+ process.exitCode = 1;
131
+ }
132
+ }
133
+
134
+ // src/commands/team.ts
135
+ import chalk4 from "chalk";
136
+ import ora2 from "ora";
137
+
138
+ // src/lib/auth-guard.ts
139
+ function requireAuth() {
140
+ const key = getApiKey();
141
+ if (!key) {
142
+ error("AGT_API_KEY is not set. Export it with your host API key (tlk_...)");
143
+ process.exitCode = 1;
144
+ return false;
145
+ }
146
+ return true;
147
+ }
148
+ function requireTeam() {
149
+ if (!requireAuth()) return null;
150
+ return "auto";
151
+ }
152
+
153
+ // src/commands/team.ts
154
+ async function teamListCommand() {
155
+ if (!requireAuth()) return;
156
+ const json = isJsonMode();
157
+ const spinner = ora2({ text: "Fetching teams\u2026", isSilent: json });
158
+ spinner.start();
159
+ try {
160
+ const data = await api.get("/teams");
161
+ spinner.stop();
162
+ if (!data.teams || data.teams.length === 0) {
163
+ if (json) {
164
+ jsonOutput({ ok: true, teams: [] });
165
+ } else {
166
+ info("You are not a member of any teams. Create one with `agt team create <name>`.");
167
+ }
168
+ return;
169
+ }
170
+ const activeSlug = getActiveTeam();
171
+ if (json) {
172
+ const teams = data.teams.map((ws) => ({
173
+ name: ws.name,
174
+ slug: ws.slug,
175
+ role: ws.role,
176
+ active: ws.slug === activeSlug
177
+ }));
178
+ jsonOutput({ ok: true, teams });
179
+ return;
180
+ }
181
+ const rows = data.teams.map((ws) => {
182
+ const active = ws.slug === activeSlug ? chalk4.green("*") : "";
183
+ return [active, ws.name, ws.slug, ws.role];
184
+ });
185
+ table(["", "Name", "Slug", "Role"], rows);
186
+ } catch (err) {
187
+ spinner.fail("Failed to fetch teams.");
188
+ if (json) {
189
+ jsonOutput({ ok: false, error: err.message });
190
+ } else {
191
+ error(err.message);
192
+ }
193
+ process.exitCode = 1;
194
+ }
195
+ }
196
+ async function teamCreateCommand(name) {
197
+ if (!requireAuth()) return;
198
+ const json = isJsonMode();
199
+ const slug = name.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
200
+ if (!slug) {
201
+ if (json) {
202
+ jsonOutput({ ok: false, error: "Invalid team name" });
203
+ } else {
204
+ error("Invalid team name. It must contain at least one alphanumeric character.");
205
+ }
206
+ process.exitCode = 1;
207
+ return;
208
+ }
209
+ const spinner = ora2({ text: `Creating team "${name}" (${slug})\u2026`, isSilent: json });
210
+ spinner.start();
211
+ try {
212
+ const data = await api.post("/teams", { name, slug });
213
+ setActiveTeam(data.team.slug);
214
+ spinner.succeed(`Team "${chalk4.bold(name)}" created.`);
215
+ if (json) {
216
+ jsonOutput({ ok: true, name, slug: data.team.slug, role: data.team.role });
217
+ return;
218
+ }
219
+ info(`Slug: ${chalk4.bold(data.team.slug)}`);
220
+ success(`Active team set to ${chalk4.bold(data.team.slug)}.`);
221
+ } catch (err) {
222
+ spinner.fail("Failed to create team.");
223
+ if (json) {
224
+ jsonOutput({ ok: false, error: err.message });
225
+ } else {
226
+ error(err.message);
227
+ }
228
+ process.exitCode = 1;
229
+ }
230
+ }
231
+ async function teamSwitchCommand(slug) {
232
+ if (!requireAuth()) return;
233
+ const json = isJsonMode();
234
+ const spinner = ora2({ text: `Switching to team "${slug}"\u2026`, isSilent: json });
235
+ spinner.start();
236
+ try {
237
+ await api.get(`/teams/${encodeURIComponent(slug)}`);
238
+ setActiveTeam(slug);
239
+ spinner.succeed(`Active team set to ${chalk4.bold(slug)}.`);
240
+ if (json) {
241
+ jsonOutput({ ok: true, team: slug });
242
+ }
243
+ } catch (err) {
244
+ spinner.fail(`Could not switch to team "${slug}".`);
245
+ if (json) {
246
+ jsonOutput({ ok: false, error: err.message });
247
+ } else {
248
+ error(err.message);
249
+ }
250
+ process.exitCode = 1;
251
+ }
252
+ }
253
+
254
+ // src/commands/init.ts
255
+ import chalk5 from "chalk";
256
+ import ora3 from "ora";
257
+ import { input, select, checkbox } from "@inquirer/prompts";
258
+ import { randomUUID } from "crypto";
259
+ import { mkdirSync, writeFileSync } from "fs";
260
+ import { join } from "path";
261
+ function toSlug(name) {
262
+ return name.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
263
+ }
264
+ function printDiagnostics(diagnostics) {
265
+ for (const d of diagnostics) {
266
+ const prefix = d.severity === "error" ? chalk5.red("ERR") : chalk5.yellow("WARN");
267
+ const path = d.path ? chalk5.dim(` (${d.path})`) : "";
268
+ console.log(` ${prefix} [${d.code}] ${d.message}${path}`);
269
+ }
270
+ }
271
+ async function initCommand(opts) {
272
+ const teamSlug = requireTeam();
273
+ if (!teamSlug) return;
274
+ const nonInteractive = !!opts.name;
275
+ const json = isJsonMode();
276
+ let displayName;
277
+ let codeName;
278
+ let description;
279
+ let environment;
280
+ let riskTier;
281
+ let budgetType;
282
+ let budgetLimitTokens;
283
+ let budgetLimitDollars;
284
+ let budgetLimit;
285
+ let budgetWindow;
286
+ let budgetEnforcement;
287
+ let selectedChannels;
288
+ let loggingMode;
289
+ if (nonInteractive) {
290
+ displayName = opts.name;
291
+ codeName = opts.codeName ?? toSlug(displayName);
292
+ description = opts.description ?? `${displayName} agent`;
293
+ environment = opts.env ?? "dev";
294
+ riskTier = opts.riskTier ?? "Low";
295
+ budgetType = opts.budgetType ?? "tokens";
296
+ budgetLimitTokens = budgetType === "tokens" || budgetType === "both" ? Number(opts.budgetTokens ?? "10000") : void 0;
297
+ budgetLimitDollars = budgetType === "dollars" || budgetType === "both" ? Number(opts.budgetDollars ?? "10") : void 0;
298
+ budgetLimit = budgetLimitTokens ?? budgetLimitDollars ?? 1e4;
299
+ budgetWindow = opts.budgetWindow ?? "daily";
300
+ budgetEnforcement = opts.budgetEnforcement ?? "block";
301
+ selectedChannels = opts.channels ? opts.channels.split(",").map((c) => c.trim()) : [];
302
+ loggingMode = opts.logging ?? "redacted";
303
+ } else {
304
+ console.log(chalk5.bold("\nAugmented \u2014 Agent Init Wizard\n"));
305
+ displayName = await input({
306
+ message: "Agent display name:",
307
+ validate: (v) => v.trim().length > 0 || "Name is required"
308
+ });
309
+ const suggestedSlug = toSlug(displayName);
310
+ codeName = await input({
311
+ message: "Code name (kebab-case):",
312
+ default: suggestedSlug,
313
+ validate: (v) => /^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/.test(v) || "Must be lowercase alphanumeric with hyphens"
314
+ });
315
+ description = await input({
316
+ message: "Short description:",
317
+ default: `${displayName} agent`
318
+ });
319
+ environment = await select({
320
+ message: "Environment:",
321
+ choices: [
322
+ { value: "dev", name: "dev" },
323
+ { value: "stage", name: "stage" },
324
+ { value: "prod", name: "prod" }
325
+ ]
326
+ });
327
+ riskTier = await select({
328
+ message: "Risk tier:",
329
+ choices: [
330
+ { value: "Low", name: "Low \u2014 read-only tools, no PII" },
331
+ { value: "Medium", name: "Medium \u2014 write access, limited scope" },
332
+ { value: "High", name: "High \u2014 broad access, PII, external comms" }
333
+ ]
334
+ });
335
+ budgetType = await select({
336
+ message: "Budget type:",
337
+ choices: [
338
+ { value: "tokens", name: "Tokens" },
339
+ { value: "dollars", name: "Dollars" },
340
+ { value: "both", name: "Both" }
341
+ ]
342
+ });
343
+ budgetLimit = 1e4;
344
+ budgetLimitTokens = void 0;
345
+ budgetLimitDollars = void 0;
346
+ if (budgetType === "tokens" || budgetType === "both") {
347
+ const val = await input({
348
+ message: "Token budget limit:",
349
+ default: "10000",
350
+ validate: (v) => !isNaN(Number(v)) && Number(v) > 0 || "Must be a positive number"
351
+ });
352
+ budgetLimitTokens = Number(val);
353
+ budgetLimit = budgetLimitTokens;
354
+ }
355
+ if (budgetType === "dollars" || budgetType === "both") {
356
+ const val = await input({
357
+ message: "Dollar budget limit:",
358
+ default: "10",
359
+ validate: (v) => !isNaN(Number(v)) && Number(v) > 0 || "Must be a positive number"
360
+ });
361
+ budgetLimitDollars = Number(val);
362
+ if (budgetType === "dollars") budgetLimit = budgetLimitDollars;
363
+ }
364
+ budgetWindow = await select({
365
+ message: "Budget window:",
366
+ choices: [
367
+ { value: "daily", name: "Daily" },
368
+ { value: "weekly", name: "Weekly" },
369
+ { value: "monthly", name: "Monthly" }
370
+ ]
371
+ });
372
+ budgetEnforcement = await select({
373
+ message: "Budget enforcement:",
374
+ choices: [
375
+ { value: "block", name: "Block \u2014 hard stop when limit reached" },
376
+ { value: "throttle", name: "Throttle \u2014 slow down near limit" },
377
+ { value: "alert", name: "Alert \u2014 warn but do not stop" },
378
+ { value: "degrade", name: "Degrade \u2014 switch to cheaper model" }
379
+ ]
380
+ });
381
+ const allChannels = getAllChannelIds();
382
+ selectedChannels = await checkbox({
383
+ message: "Select allowed channels:",
384
+ choices: allChannels.map((c) => ({ value: c, name: c }))
385
+ });
386
+ loggingMode = await select({
387
+ message: "Logging mode:",
388
+ choices: [
389
+ { value: "redacted", name: "Redacted \u2014 PII stripped" },
390
+ { value: "hash-only", name: "Hash-only \u2014 content is hashed" },
391
+ { value: "full-local", name: "Full local \u2014 plain text (dev only)" }
392
+ ]
393
+ });
394
+ }
395
+ const spinner = ora3({ text: "Creating agent\u2026", isSilent: json });
396
+ spinner.start();
397
+ let userEmail;
398
+ let userId;
399
+ try {
400
+ const me = await api.get("/auth/me");
401
+ userId = me.id;
402
+ userEmail = me.email ?? me.id;
403
+ } catch (err) {
404
+ spinner.fail("Could not determine current user.");
405
+ if (json) {
406
+ jsonOutput({ ok: false, error: err.message });
407
+ } else {
408
+ error(err.message);
409
+ }
410
+ process.exitCode = 1;
411
+ return;
412
+ }
413
+ const agentId = randomUUID();
414
+ const charterInput = {
415
+ agent_id: agentId,
416
+ code_name: codeName,
417
+ display_name: displayName,
418
+ environment,
419
+ owner: {
420
+ id: userId,
421
+ name: userEmail,
422
+ email: userEmail
423
+ },
424
+ risk_tier: riskTier,
425
+ logging_mode: loggingMode,
426
+ description
427
+ };
428
+ const toolsInput = {
429
+ agent_id: agentId,
430
+ code_name: codeName,
431
+ environment,
432
+ owner: userEmail,
433
+ display_name: displayName,
434
+ logging_redaction: loggingMode
435
+ };
436
+ const charterMd = generateCharterMd(charterInput);
437
+ const toolsMd = generateToolsMd(toolsInput);
438
+ const lintResult = lintAll(charterMd, toolsMd);
439
+ try {
440
+ await api.post("/agents", {
441
+ code_name: codeName,
442
+ display_name: displayName,
443
+ description,
444
+ environment,
445
+ risk_tier: riskTier,
446
+ channels: selectedChannels,
447
+ charter_content: charterMd,
448
+ charter_version: "0.1",
449
+ tools_content: toolsMd,
450
+ tools_version: "0.1"
451
+ });
452
+ } catch (err) {
453
+ spinner.fail("Failed to register agent.");
454
+ if (json) {
455
+ jsonOutput({ ok: false, error: err.message });
456
+ } else {
457
+ error(err.message);
458
+ }
459
+ process.exitCode = 1;
460
+ return;
461
+ }
462
+ const agentDir = join(process.cwd(), ".augmented", codeName);
463
+ mkdirSync(agentDir, { recursive: true });
464
+ writeFileSync(join(agentDir, "CHARTER.md"), charterMd);
465
+ writeFileSync(join(agentDir, "TOOLS.md"), toolsMd);
466
+ spinner.succeed(`Agent "${chalk5.bold(displayName)}" created.`);
467
+ if (json) {
468
+ jsonOutput({
469
+ ok: true,
470
+ agent_id: agentId,
471
+ code_name: codeName,
472
+ display_name: displayName,
473
+ environment,
474
+ risk_tier: riskTier,
475
+ channels: selectedChannels,
476
+ directory: agentDir,
477
+ lint: {
478
+ ok: lintResult.ok,
479
+ errors: lintResult.errors.length,
480
+ warnings: lintResult.warnings.length,
481
+ diagnostics: [...lintResult.errors, ...lintResult.warnings]
482
+ }
483
+ });
484
+ return;
485
+ }
486
+ console.log();
487
+ info(`Agent ID: ${agentId}`);
488
+ info(`Code Name: ${codeName}`);
489
+ info(`Environment: ${environment}`);
490
+ info(`Risk Tier: ${riskTier}`);
491
+ info(`Channels: ${selectedChannels.length > 0 ? selectedChannels.join(", ") : "none"}`);
492
+ info(`Files: ${agentDir}/CHARTER.md, TOOLS.md`);
493
+ console.log();
494
+ if (lintResult.ok && lintResult.warnings.length === 0) {
495
+ success("Lint passed \u2014 no errors or warnings.");
496
+ } else {
497
+ if (lintResult.errors.length > 0) {
498
+ error(`Lint: ${lintResult.errors.length} error(s)`);
499
+ printDiagnostics(lintResult.errors);
500
+ }
501
+ if (lintResult.warnings.length > 0) {
502
+ warn(`Lint: ${lintResult.warnings.length} warning(s)`);
503
+ printDiagnostics(lintResult.warnings);
504
+ }
505
+ }
506
+ console.log();
507
+ info(`Next steps:`);
508
+ info(` 1. Edit ${chalk5.bold(".augmented/" + codeName + "/CHARTER.md")} to refine the agent charter`);
509
+ info(` 2. Edit ${chalk5.bold(".augmented/" + codeName + "/TOOLS.md")} to add tools`);
510
+ info(` 3. Run ${chalk5.bold("agt lint")} to validate`);
511
+ info(` 4. Run ${chalk5.bold("agt deploy")} to generate deployment config`);
512
+ }
513
+
514
+ // src/commands/lint.ts
515
+ import chalk6 from "chalk";
516
+ import ora4 from "ora";
517
+ import { readFileSync, existsSync } from "fs";
518
+ import { join as join2, resolve } from "path";
519
+ function printDiagnostics2(diagnostics) {
520
+ for (const d of diagnostics) {
521
+ const prefix = d.severity === "error" ? chalk6.red("ERR") : chalk6.yellow("WARN");
522
+ const file = chalk6.dim(`[${d.file}]`);
523
+ const path = d.path ? chalk6.dim(` (${d.path})`) : "";
524
+ console.log(` ${prefix} ${file} ${d.code}: ${d.message}${path}`);
525
+ }
526
+ }
527
+ function printResult(label, result) {
528
+ if (result.ok && result.warnings.length === 0) {
529
+ success(`${label}: passed`);
530
+ return;
531
+ }
532
+ if (result.errors.length > 0) {
533
+ error(`${label}: ${result.errors.length} error(s)`);
534
+ printDiagnostics2(result.errors);
535
+ }
536
+ if (result.warnings.length > 0) {
537
+ warn(`${label}: ${result.warnings.length} warning(s)`);
538
+ printDiagnostics2(result.warnings);
539
+ }
540
+ }
541
+ async function lintCommand(path) {
542
+ const json = isJsonMode();
543
+ const dirs = [];
544
+ if (path) {
545
+ const resolved = resolve(path);
546
+ if (!existsSync(resolved)) {
547
+ if (json) {
548
+ jsonOutput({ ok: false, error: `Path not found: ${resolved}` });
549
+ } else {
550
+ error(`Path not found: ${resolved}`);
551
+ }
552
+ process.exitCode = 1;
553
+ return;
554
+ }
555
+ dirs.push({ name: path, dir: resolved });
556
+ } else {
557
+ const augmentedDir = join2(process.cwd(), ".augmented");
558
+ if (!existsSync(augmentedDir)) {
559
+ if (json) {
560
+ jsonOutput({ ok: false, error: "No .augmented/ directory found" });
561
+ } else {
562
+ error("No .augmented/ directory found. Run `agt init` first.");
563
+ }
564
+ process.exitCode = 1;
565
+ return;
566
+ }
567
+ const { readdirSync, statSync } = await import("fs");
568
+ const entries = readdirSync(augmentedDir);
569
+ for (const entry of entries) {
570
+ const entryPath = join2(augmentedDir, entry);
571
+ if (statSync(entryPath).isDirectory()) {
572
+ dirs.push({ name: entry, dir: entryPath });
573
+ }
574
+ }
575
+ if (dirs.length === 0) {
576
+ if (json) {
577
+ jsonOutput({ ok: false, error: "No agent directories found in .augmented/" });
578
+ } else {
579
+ error("No agent directories found in .augmented/. Run `agt init` first.");
580
+ }
581
+ process.exitCode = 1;
582
+ return;
583
+ }
584
+ }
585
+ let teamPolicy;
586
+ const teamSlug = requireTeam();
587
+ if (teamSlug) {
588
+ const spinner = ora4({ text: "Fetching team channel policy\u2026", isSilent: json });
589
+ spinner.start();
590
+ try {
591
+ const data = await api.get(`/teams/${encodeURIComponent(teamSlug)}/channel-policy`);
592
+ if (data.channel_policy) {
593
+ teamPolicy = {
594
+ team_id: data.channel_policy.team_id,
595
+ allowed_channels: data.channel_policy.allowed_channels ?? [],
596
+ denied_channels: data.channel_policy.denied_channels ?? [],
597
+ require_elevated_for_pii: data.channel_policy.require_elevated_for_pii ?? false
598
+ };
599
+ }
600
+ spinner.stop();
601
+ } catch {
602
+ spinner.stop();
603
+ }
604
+ }
605
+ let totalErrors = 0;
606
+ let totalWarnings = 0;
607
+ const jsonResults = [];
608
+ for (const { name, dir } of dirs) {
609
+ if (!json) console.log(chalk6.bold(`
610
+ Linting ${name}:`));
611
+ const charterPath = join2(dir, "CHARTER.md");
612
+ const toolsPath = join2(dir, "TOOLS.md");
613
+ const hasCharter = existsSync(charterPath);
614
+ const hasTools = existsSync(toolsPath);
615
+ if (!hasCharter && !hasTools) {
616
+ if (!json) warn("No CHARTER.md or TOOLS.md found \u2014 skipping.");
617
+ if (json) jsonResults.push({ agent: name, skipped: true });
618
+ continue;
619
+ }
620
+ const charterContent = hasCharter ? readFileSync(charterPath, "utf-8") : void 0;
621
+ const toolsContent = hasTools ? readFileSync(toolsPath, "utf-8") : void 0;
622
+ let result;
623
+ if (charterContent && toolsContent) {
624
+ result = lintAll(charterContent, toolsContent, { teamChannelPolicy: teamPolicy });
625
+ if (!json) printResult("Full lint", result);
626
+ } else if (charterContent) {
627
+ result = lintCharter(charterContent, { teamChannelPolicy: teamPolicy });
628
+ if (!json) printResult("CHARTER.md", result);
629
+ } else {
630
+ result = lintTools(toolsContent);
631
+ if (!json) printResult("TOOLS.md", result);
632
+ }
633
+ totalErrors += result.errors.length;
634
+ totalWarnings += result.warnings.length;
635
+ if (json) {
636
+ jsonResults.push({
637
+ agent: name,
638
+ ok: result.ok,
639
+ errors: result.errors.length,
640
+ warnings: result.warnings.length,
641
+ diagnostics: [...result.errors, ...result.warnings]
642
+ });
643
+ }
644
+ }
645
+ if (json) {
646
+ jsonOutput({
647
+ ok: totalErrors === 0,
648
+ agents: jsonResults,
649
+ total_errors: totalErrors,
650
+ total_warnings: totalWarnings
651
+ });
652
+ if (totalErrors > 0) process.exitCode = 1;
653
+ return;
654
+ }
655
+ console.log();
656
+ if (totalErrors === 0 && totalWarnings === 0) {
657
+ success(`All ${dirs.length} agent(s) passed lint.`);
658
+ } else {
659
+ if (totalErrors > 0) {
660
+ error(`Total: ${totalErrors} error(s) across ${dirs.length} agent(s)`);
661
+ process.exitCode = 1;
662
+ }
663
+ if (totalWarnings > 0) {
664
+ warn(`Total: ${totalWarnings} warning(s) across ${dirs.length} agent(s)`);
665
+ }
666
+ }
667
+ }
668
+
669
+ // src/commands/channels.ts
670
+ import chalk7 from "chalk";
671
+ import ora5 from "ora";
672
+ function channelsListCommand() {
673
+ const json = isJsonMode();
674
+ if (json) {
675
+ const channels2 = CHANNEL_REGISTRY.map((ch) => ({
676
+ id: ch.id,
677
+ name: ch.name,
678
+ security_tier: ch.securityTier,
679
+ e2e_encrypted: ch.e2eEncrypted,
680
+ audit_trail: ch.auditTrail,
681
+ public_exposure_risk: ch.publicExposureRisk
682
+ }));
683
+ jsonOutput({ ok: true, channels: channels2 });
684
+ return;
685
+ }
686
+ console.log(chalk7.bold("\nChannel Registry (18 channels)\n"));
687
+ const rows = CHANNEL_REGISTRY.map((ch) => [
688
+ ch.id,
689
+ ch.name,
690
+ ch.securityTier === "elevated" ? chalk7.green(ch.securityTier) : ch.securityTier === "limited" ? chalk7.red(ch.securityTier) : chalk7.cyan(ch.securityTier),
691
+ typeof ch.e2eEncrypted === "boolean" ? ch.e2eEncrypted ? chalk7.green("yes") : chalk7.dim("no") : chalk7.yellow(ch.e2eEncrypted),
692
+ typeof ch.auditTrail === "boolean" ? ch.auditTrail ? chalk7.green("yes") : chalk7.dim("no") : chalk7.yellow(ch.auditTrail),
693
+ ch.publicExposureRisk === "High" ? chalk7.red(ch.publicExposureRisk) : ch.publicExposureRisk === "Medium" ? chalk7.yellow(ch.publicExposureRisk) : chalk7.green(ch.publicExposureRisk)
694
+ ]);
695
+ table(["ID", "Name", "Security Tier", "E2E Encrypted", "Audit Trail", "Public Risk"], rows);
696
+ }
697
+ async function channelsCheckCommand(agentCodeName) {
698
+ const teamSlug = requireTeam();
699
+ if (!teamSlug) return;
700
+ const json = isJsonMode();
701
+ const spinner = ora5({ text: `Resolving channels for "${agentCodeName}"\u2026`, isSilent: json });
702
+ spinner.start();
703
+ try {
704
+ const data = await api.get(`/agents/${encodeURIComponent(agentCodeName)}/channels`);
705
+ const agentPolicy = {
706
+ policy: "allowlist",
707
+ allowed: data.agent_channels ?? [],
708
+ denied: [],
709
+ require_approval_to_change: false
710
+ };
711
+ const teamPolicy = data.channel_policy ? {
712
+ team_id: data.channel_policy.team_id,
713
+ allowed_channels: data.channel_policy.allowed_channels ?? [],
714
+ denied_channels: data.channel_policy.denied_channels ?? [],
715
+ require_elevated_for_pii: data.channel_policy.require_elevated_for_pii ?? false
716
+ } : void 0;
717
+ const resolved = resolveChannels(agentPolicy, teamPolicy);
718
+ spinner.stop();
719
+ if (json) {
720
+ jsonOutput({
721
+ ok: true,
722
+ agent: agentCodeName,
723
+ agent_allowlist: agentPolicy.allowed,
724
+ team_policy: teamPolicy ? {
725
+ allowed: teamPolicy.allowed_channels,
726
+ denied: teamPolicy.denied_channels,
727
+ require_elevated_for_pii: teamPolicy.require_elevated_for_pii
728
+ } : null,
729
+ resolved_channels: resolved,
730
+ count: resolved.length
731
+ });
732
+ return;
733
+ }
734
+ console.log(chalk7.bold(`
735
+ Channel Resolution: ${agentCodeName}
736
+ `));
737
+ info(`Agent allowlist: ${agentPolicy.allowed.length > 0 ? agentPolicy.allowed.join(", ") : "none"}`);
738
+ if (teamPolicy) {
739
+ info(`Team allowed: ${teamPolicy.allowed_channels.length > 0 ? teamPolicy.allowed_channels.join(", ") : "all (no restriction)"}`);
740
+ info(`Team denied: ${teamPolicy.denied_channels.length > 0 ? teamPolicy.denied_channels.join(", ") : "none"}`);
741
+ if (teamPolicy.require_elevated_for_pii) {
742
+ info("Team requires elevated tier for PII channels");
743
+ }
744
+ } else {
745
+ info("No team channel policy configured.");
746
+ }
747
+ console.log();
748
+ if (resolved.length === 0) {
749
+ error("No channels available after resolution. The agent cannot communicate.");
750
+ process.exitCode = 1;
751
+ return;
752
+ }
753
+ const rows = resolved.map((chId) => {
754
+ const ch = getChannel(chId);
755
+ return [
756
+ chId,
757
+ ch?.name ?? "Unknown",
758
+ ch?.securityTier ?? "-",
759
+ ch?.publicExposureRisk ?? "-"
760
+ ];
761
+ });
762
+ success(`${resolved.length} channel(s) available:`);
763
+ table(["ID", "Name", "Security Tier", "Public Risk"], rows);
764
+ } catch (err) {
765
+ spinner.fail("Failed to resolve channels.");
766
+ if (json) {
767
+ jsonOutput({ ok: false, error: err.message });
768
+ } else {
769
+ error(err.message);
770
+ }
771
+ process.exitCode = 1;
772
+ }
773
+ }
774
+
775
+ // src/commands/channel-slack.ts
776
+ import chalk8 from "chalk";
777
+ import ora6 from "ora";
778
+ import { checkbox as checkbox2, confirm as confirm2, input as input2 } from "@inquirer/prompts";
779
+ import { writeFile } from "fs/promises";
780
+ import { join as join3 } from "path";
781
+ import { tmpdir } from "os";
782
+ async function channelSlackSetupCommand(agentCodeName, options) {
783
+ const teamSlug = requireTeam();
784
+ if (!teamSlug) return;
785
+ const json = isJsonMode();
786
+ const spinner = ora6({ text: `Looking up agent "${agentCodeName}"\u2026`, isSilent: json });
787
+ spinner.start();
788
+ try {
789
+ const agentData = await api.get(`/agents/${encodeURIComponent(agentCodeName)}`);
790
+ const agent2 = agentData.agent;
791
+ const channels2 = agent2.channels ?? [];
792
+ if (!channels2.includes("slack")) {
793
+ spinner.fail(`Agent "${agentCodeName}" does not have Slack in its channel list.`);
794
+ error(`Enable Slack in the agent's channels first (add "slack" to the channels array).`);
795
+ process.exitCode = 1;
796
+ return;
797
+ }
798
+ spinner.succeed(`Found agent "${agentCodeName}"`);
799
+ let selectedScopes;
800
+ if (options.scopes) {
801
+ selectedScopes = options.scopes.split(",").map((s) => s.trim());
802
+ } else if (options.preset) {
803
+ const preset = options.preset;
804
+ if (!(preset in SLACK_SCOPE_PRESETS)) {
805
+ error(`Unknown preset "${options.preset}". Available: minimal, standard, full`);
806
+ process.exitCode = 1;
807
+ return;
808
+ }
809
+ selectedScopes = [...SLACK_SCOPE_PRESETS[preset]];
810
+ info(`Using "${preset}" preset (${selectedScopes.length} scopes)`);
811
+ } else if (json) {
812
+ selectedScopes = [...SLACK_SCOPE_PRESETS.standard];
813
+ } else {
814
+ const scopesByCategory = getScopesByCategory();
815
+ const defaults = getDefaultSlackScopes();
816
+ const choices = [];
817
+ for (const [category, defs] of scopesByCategory) {
818
+ const label = SLACK_SCOPE_CATEGORY_LABELS[category];
819
+ choices.push({ type: "separator", separator: `\u2500\u2500 ${label} \u2500\u2500` });
820
+ for (const def of defs) {
821
+ const riskColor = def.risk === "high" ? chalk8.red : def.risk === "medium" ? chalk8.yellow : chalk8.green;
822
+ choices.push({
823
+ name: `${def.scope} ${chalk8.dim(`\u2014 ${def.description}`)} ${riskColor(`[${def.risk}]`)}`,
824
+ value: def.scope,
825
+ checked: defaults.includes(def.scope)
826
+ });
827
+ }
828
+ }
829
+ selectedScopes = await checkbox2({
830
+ message: "Select Slack bot scopes:",
831
+ choices,
832
+ pageSize: 30
833
+ });
834
+ }
835
+ if (selectedScopes.length === 0) {
836
+ error("No scopes selected. At least one scope is required.");
837
+ process.exitCode = 1;
838
+ return;
839
+ }
840
+ info(`Selected ${selectedScopes.length} scope(s): ${selectedScopes.join(", ")}`);
841
+ const manifest = generateSlackAppManifest({
842
+ agent_name: agent2.display_name ?? agentCodeName,
843
+ description: `Augmented-managed Slack bot for agent ${agentCodeName}`,
844
+ scopes: selectedScopes
845
+ });
846
+ const manifestObj = serializeManifestForSlackCli(manifest);
847
+ const manifestJson = JSON.stringify(manifestObj, null, 2);
848
+ const manifestPath = join3(tmpdir(), `augmented-slack-manifest-${agentCodeName}.json`);
849
+ await writeFile(manifestPath, manifestJson, "utf-8");
850
+ success(`Manifest written to ${manifestPath}`);
851
+ console.log(chalk8.dim(manifestJson));
852
+ let appId;
853
+ let botTokenRef = options.botToken;
854
+ let signingSecretRef = options.signingSecret;
855
+ if (!options.skipCreate) {
856
+ let token = options.configToken;
857
+ if (!token && !json) {
858
+ token = (await input2({
859
+ message: "Paste your Slack config token (xoxe-\u2026) to create the app automatically, or leave empty to skip:"
860
+ })).trim() || void 0;
861
+ }
862
+ if (token) {
863
+ const createSpinner = ora6({ text: "Creating Slack app via API\u2026", isSilent: json });
864
+ createSpinner.start();
865
+ try {
866
+ const result = await createSlackApp(token, manifest);
867
+ appId = result.app_id;
868
+ signingSecretRef = `secret_ref://slack/${agentCodeName}/signing_secret`;
869
+ createSpinner.succeed(`Slack app created: ${result.app_id}`);
870
+ info(`OAuth URL: ${result.oauth_authorize_url}`);
871
+ } catch (err) {
872
+ createSpinner.fail("Failed to create Slack app via API.");
873
+ if (err instanceof SlackApiError) {
874
+ warn(`Slack API error: ${err.message}`);
875
+ } else {
876
+ warn(err.message);
877
+ }
878
+ info(`Manifest saved at: ${manifestPath}`);
879
+ info("Create the app manually via https://api.slack.com/apps and paste the manifest.");
880
+ }
881
+ } else {
882
+ info(`Manifest saved at: ${manifestPath}`);
883
+ info("Create the app manually via https://api.slack.com/apps and paste the manifest.");
884
+ }
885
+ }
886
+ if (!signingSecretRef && !json) {
887
+ const secret = await input2({
888
+ message: "Paste your Slack Signing Secret, or leave empty to skip:"
889
+ });
890
+ if (secret.trim()) {
891
+ signingSecretRef = `secret_ref://slack/${agentCodeName}/signing_secret`;
892
+ }
893
+ }
894
+ if (!botTokenRef && !json) {
895
+ const token = await input2({
896
+ message: "Paste your Slack Bot Token (xoxb-\u2026), or leave empty to skip:"
897
+ });
898
+ if (token.trim()) {
899
+ botTokenRef = `secret_ref://slack/${agentCodeName}/bot_token`;
900
+ }
901
+ }
902
+ const configSpinner = ora6({ text: "Saving channel config\u2026", isSilent: json });
903
+ configSpinner.start();
904
+ const config = {
905
+ channel_type: "slack",
906
+ app_name: agent2.display_name ?? agentCodeName,
907
+ scopes: selectedScopes,
908
+ ...appId ? { app_id: appId } : {},
909
+ ...botTokenRef ? { bot_token_ref: botTokenRef } : {},
910
+ ...signingSecretRef ? { signing_secret_ref: signingSecretRef } : {},
911
+ manifest
912
+ };
913
+ try {
914
+ await api.put(
915
+ `/agents/${encodeURIComponent(agentCodeName)}/channel-configs/slack`,
916
+ {
917
+ config,
918
+ status: botTokenRef ? "configured" : "pending"
919
+ }
920
+ );
921
+ } catch (err) {
922
+ configSpinner.fail("Failed to save channel config.");
923
+ error(err.message);
924
+ process.exitCode = 1;
925
+ return;
926
+ }
927
+ configSpinner.succeed("Slack channel config saved.");
928
+ if (json) {
929
+ jsonOutput({
930
+ ok: true,
931
+ agent: agentCodeName,
932
+ channel: "slack",
933
+ scopes: selectedScopes,
934
+ status: botTokenRef ? "configured" : "pending",
935
+ manifest_path: manifestPath
936
+ });
937
+ } else {
938
+ console.log();
939
+ success(`Slack setup complete for "${agentCodeName}"`);
940
+ info(`Status: ${botTokenRef ? "configured" : "pending (add credentials to activate)"}`);
941
+ }
942
+ } catch (err) {
943
+ spinner.fail("Slack setup failed.");
944
+ if (json) {
945
+ jsonOutput({ ok: false, error: err.message });
946
+ } else {
947
+ error(err.message);
948
+ }
949
+ process.exitCode = 1;
950
+ }
951
+ }
952
+ async function channelSlackStatusCommand(agentCodeName) {
953
+ const teamSlug = requireTeam();
954
+ if (!teamSlug) return;
955
+ const json = isJsonMode();
956
+ const spinner = ora6({ text: `Fetching Slack config for "${agentCodeName}"\u2026`, isSilent: json });
957
+ spinner.start();
958
+ try {
959
+ const data = await api.get(`/agents/${encodeURIComponent(agentCodeName)}/channel-configs/slack`);
960
+ spinner.stop();
961
+ const config = data.config;
962
+ if (json) {
963
+ jsonOutput({
964
+ ok: true,
965
+ agent: agentCodeName,
966
+ channel: "slack",
967
+ configured: true,
968
+ config
969
+ });
970
+ return;
971
+ }
972
+ console.log(chalk8.bold(`
973
+ Slack Configuration: ${agentCodeName}
974
+ `));
975
+ const rows = [
976
+ ["App Name", config.app_name ?? "-"],
977
+ ["App ID", config.app_id ?? "-"],
978
+ ["Team ID", config.team_id ?? "-"],
979
+ ["Bot Token", config.bot_token_ref ? chalk8.green("configured") : chalk8.dim("not set")],
980
+ ["Signing Secret", config.signing_secret_ref ? chalk8.green("configured") : chalk8.dim("not set")],
981
+ ["Scopes", (config.scopes ?? []).join(", ") || "-"]
982
+ ];
983
+ table(["Field", "Value"], rows);
984
+ } catch (err) {
985
+ spinner.stop();
986
+ if (err.status === 404) {
987
+ if (json) {
988
+ jsonOutput({ ok: true, agent: agentCodeName, channel: "slack", configured: false });
989
+ } else {
990
+ warn(`No Slack configuration found for "${agentCodeName}".`);
991
+ info("Run `agt channels slack setup " + agentCodeName + "` to configure.");
992
+ }
993
+ return;
994
+ }
995
+ spinner.fail("Failed to fetch Slack status.");
996
+ if (json) {
997
+ jsonOutput({ ok: false, error: err.message });
998
+ } else {
999
+ error(err.message);
1000
+ }
1001
+ process.exitCode = 1;
1002
+ }
1003
+ }
1004
+ async function channelSlackRemoveCommand(agentCodeName) {
1005
+ const teamSlug = requireTeam();
1006
+ if (!teamSlug) return;
1007
+ const json = isJsonMode();
1008
+ const spinner = ora6({ text: `Looking up agent "${agentCodeName}"\u2026`, isSilent: json });
1009
+ spinner.start();
1010
+ try {
1011
+ let config = null;
1012
+ try {
1013
+ const data = await api.get(`/agents/${encodeURIComponent(agentCodeName)}/channel-configs/slack`);
1014
+ config = data.config;
1015
+ } catch (err) {
1016
+ if (err.status === 404) {
1017
+ spinner.info(`No Slack configuration found for "${agentCodeName}".`);
1018
+ if (json) {
1019
+ jsonOutput({ ok: true, agent: agentCodeName, channel: "slack", removed: false, reason: "not_configured" });
1020
+ }
1021
+ return;
1022
+ }
1023
+ throw err;
1024
+ }
1025
+ spinner.stop();
1026
+ if (!json) {
1027
+ const appLabel = config?.app_id ? ` (App ID: ${config.app_id})` : config?.app_name ? ` (${config.app_name})` : "";
1028
+ const confirmed = await confirm2({
1029
+ message: `Remove Slack bot configuration${appLabel} for "${agentCodeName}"?`,
1030
+ default: false
1031
+ });
1032
+ if (!confirmed) {
1033
+ warn("Aborted.");
1034
+ return;
1035
+ }
1036
+ }
1037
+ const removeSpinner = ora6({ text: "Removing Slack configuration\u2026", isSilent: json });
1038
+ removeSpinner.start();
1039
+ await api.del(`/agents/${encodeURIComponent(agentCodeName)}/channel-configs/slack`);
1040
+ removeSpinner.succeed("Slack configuration removed.");
1041
+ if (json) {
1042
+ jsonOutput({ ok: true, agent: agentCodeName, channel: "slack", removed: true });
1043
+ } else {
1044
+ if (config?.app_id) {
1045
+ console.log();
1046
+ info(`Note: The Slack app (${config.app_id}) still exists in your Slack workspace.`);
1047
+ info("Delete it manually at https://api.slack.com/apps if no longer needed.");
1048
+ }
1049
+ }
1050
+ } catch (err) {
1051
+ error(err.message);
1052
+ if (json) {
1053
+ jsonOutput({ ok: false, error: err.message });
1054
+ }
1055
+ process.exitCode = 1;
1056
+ }
1057
+ }
1058
+
1059
+ // src/commands/channel-beam.ts
1060
+ import chalk9 from "chalk";
1061
+ import ora7 from "ora";
1062
+ import { confirm as confirm3 } from "@inquirer/prompts";
1063
+ async function channelBeamSetupCommand(agentCodeName, options) {
1064
+ const teamSlug = requireTeam();
1065
+ if (!teamSlug) return;
1066
+ const json = isJsonMode();
1067
+ const spinner = ora7({ text: `Looking up agent "${agentCodeName}"\u2026`, isSilent: json });
1068
+ spinner.start();
1069
+ try {
1070
+ const agentData = await api.get(`/agents/${encodeURIComponent(agentCodeName)}`);
1071
+ const agent2 = agentData.agent;
1072
+ const channels2 = agent2.channels ?? [];
1073
+ if (!channels2.includes("beam")) {
1074
+ spinner.fail(`Agent "${agentCodeName}" does not have Beam in its channel list.`);
1075
+ error(`Enable Beam in the agent's channels first (add "beam" to the channels array).`);
1076
+ process.exitCode = 1;
1077
+ return;
1078
+ }
1079
+ spinner.succeed(`Found agent "${agentCodeName}"`);
1080
+ const existingConfig = await api.get(`/agents/${encodeURIComponent(agentCodeName)}/channel-configs/beam`).catch(() => ({ config: null }));
1081
+ if (existingConfig.config?.beam_id) {
1082
+ info(`Agent already has Beam identity: ${chalk9.cyan(String(existingConfig.config.beam_id))}`);
1083
+ const proceed = await confirm3({
1084
+ message: "Regenerate identity? This will create a new keypair and re-register.",
1085
+ default: false
1086
+ });
1087
+ if (!proceed) return;
1088
+ }
1089
+ spinner.start("Generating Ed25519 identity and registering with Beam directory\u2026");
1090
+ const result = await api.post(`/agents/${encodeURIComponent(agentCodeName)}/channel-configs/beam/setup`, {
1091
+ auto_publish_capabilities: options.autoPublish ?? true,
1092
+ directory_url: options.directoryUrl
1093
+ });
1094
+ if (!result.ok) {
1095
+ spinner.fail("Registration failed");
1096
+ error(result.error ?? "Unknown error");
1097
+ process.exitCode = 1;
1098
+ return;
1099
+ }
1100
+ spinner.succeed("Beam identity created and registered");
1101
+ if (json) {
1102
+ jsonOutput({
1103
+ beam_id: result.beam_id,
1104
+ did: result.did,
1105
+ public_key: result.public_key,
1106
+ trust_score: result.trust_score,
1107
+ verification_tier: result.verification_tier,
1108
+ published_capabilities: result.published_capabilities
1109
+ });
1110
+ return;
1111
+ }
1112
+ console.log();
1113
+ table(
1114
+ ["Field", "Value"],
1115
+ [
1116
+ ["Beam ID", chalk9.cyan(result.beam_id)],
1117
+ ["DID", chalk9.dim(result.did)],
1118
+ ["Trust Score", String(result.trust_score)],
1119
+ ["Verification", result.verification_tier],
1120
+ ["Capabilities", result.published_capabilities.length > 0 ? result.published_capabilities.join(", ") : chalk9.dim("none published")]
1121
+ ]
1122
+ );
1123
+ console.log();
1124
+ success(`Agent "${agentCodeName}" is now reachable at ${chalk9.cyan(result.beam_id)}`);
1125
+ } catch (err) {
1126
+ spinner.fail("Setup failed");
1127
+ error(err instanceof Error ? err.message : "Unknown error");
1128
+ process.exitCode = 1;
1129
+ }
1130
+ }
1131
+ async function channelBeamStatusCommand(agentCodeName) {
1132
+ const teamSlug = requireTeam();
1133
+ if (!teamSlug) return;
1134
+ const json = isJsonMode();
1135
+ const spinner = ora7({ text: `Fetching Beam config for "${agentCodeName}"\u2026`, isSilent: json });
1136
+ spinner.start();
1137
+ try {
1138
+ const data = await api.get(`/agents/${encodeURIComponent(agentCodeName)}/channel-configs/beam`);
1139
+ if (!data.config?.beam_id) {
1140
+ spinner.info("No Beam identity configured for this agent.");
1141
+ return;
1142
+ }
1143
+ spinner.succeed("Beam configuration found");
1144
+ const cfg = data.config;
1145
+ if (json) {
1146
+ jsonOutput(cfg);
1147
+ return;
1148
+ }
1149
+ console.log();
1150
+ table(
1151
+ ["Field", "Value"],
1152
+ [
1153
+ ["Beam ID", chalk9.cyan(String(cfg.beam_id))],
1154
+ ["DID", chalk9.dim(String(cfg.did ?? "\u2014"))],
1155
+ ["Public Key", chalk9.dim(String(cfg.public_key ?? "").slice(0, 24) + "\u2026")],
1156
+ ["Trust Score", String(cfg.trust_score ?? 0)],
1157
+ ["Verification", String(cfg.verification_tier ?? "basic")],
1158
+ ["Auto-publish", cfg.auto_publish_capabilities ? chalk9.green("yes") : chalk9.dim("no")],
1159
+ ["Capabilities", Array.isArray(cfg.published_capabilities) && cfg.published_capabilities.length > 0 ? cfg.published_capabilities.join(", ") : chalk9.dim("none")],
1160
+ ["Status", data.status ?? "\u2014"]
1161
+ ]
1162
+ );
1163
+ console.log();
1164
+ } catch (err) {
1165
+ spinner.fail("Failed to fetch config");
1166
+ error(err instanceof Error ? err.message : "Unknown error");
1167
+ process.exitCode = 1;
1168
+ }
1169
+ }
1170
+ async function channelBeamRemoveCommand(agentCodeName) {
1171
+ const teamSlug = requireTeam();
1172
+ if (!teamSlug) return;
1173
+ const json = isJsonMode();
1174
+ if (!json) {
1175
+ const proceed = await confirm3({
1176
+ message: `Remove Beam identity from "${agentCodeName}"? The directory entry will expire.`,
1177
+ default: false
1178
+ });
1179
+ if (!proceed) return;
1180
+ }
1181
+ const spinner = ora7({ text: "Removing Beam identity\u2026", isSilent: json });
1182
+ spinner.start();
1183
+ try {
1184
+ await api.del(`/agents/${encodeURIComponent(agentCodeName)}/channel-configs/beam`);
1185
+ spinner.succeed("Beam identity removed");
1186
+ if (json) {
1187
+ jsonOutput({ ok: true });
1188
+ }
1189
+ } catch (err) {
1190
+ spinner.fail("Failed to remove");
1191
+ error(err instanceof Error ? err.message : "Unknown error");
1192
+ process.exitCode = 1;
1193
+ }
1194
+ }
1195
+
1196
+ // src/commands/deploy.ts
1197
+ import chalk10 from "chalk";
1198
+ import ora8 from "ora";
1199
+ import { select as select2, input as input3 } from "@inquirer/prompts";
1200
+ import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
1201
+ import { join as join4 } from "path";
1202
+ async function deployCommand(opts) {
1203
+ const teamSlug = requireTeam();
1204
+ if (!teamSlug) return;
1205
+ const nonInteractive = !!opts.template;
1206
+ const json = isJsonMode();
1207
+ let templateId;
1208
+ let basePort;
1209
+ if (nonInteractive) {
1210
+ templateId = opts.template;
1211
+ basePort = Number(opts.port ?? "9000");
1212
+ if (isNaN(basePort) || basePort < 1 || basePort > 65535) {
1213
+ if (json) {
1214
+ jsonOutput({ ok: false, error: "Invalid port number" });
1215
+ } else {
1216
+ error("Invalid port number. Must be between 1 and 65535.");
1217
+ }
1218
+ process.exitCode = 1;
1219
+ return;
1220
+ }
1221
+ } else {
1222
+ console.log(chalk10.bold("\nAugmented \u2014 Deploy\n"));
1223
+ templateId = await select2({
1224
+ message: "Deployment template:",
1225
+ choices: DEPLOYMENT_TEMPLATES.map((t) => ({
1226
+ value: t.id,
1227
+ name: `${t.name} \u2014 ${t.description}`
1228
+ }))
1229
+ });
1230
+ const basePortStr = await input3({
1231
+ message: "Base port for gateway:",
1232
+ default: "9000",
1233
+ validate: (v) => {
1234
+ const n = Number(v);
1235
+ return !isNaN(n) && n > 0 && n < 65536 || "Must be a valid port number";
1236
+ }
1237
+ });
1238
+ basePort = Number(basePortStr);
1239
+ }
1240
+ const tmpl = getTemplate(templateId);
1241
+ if (!tmpl) {
1242
+ if (json) {
1243
+ jsonOutput({ ok: false, error: `Template "${templateId}" not found` });
1244
+ } else {
1245
+ error(`Template "${templateId}" not found.`);
1246
+ }
1247
+ process.exitCode = 1;
1248
+ return;
1249
+ }
1250
+ const spinner = ora8({ text: "Fetching agents\u2026", isSilent: json });
1251
+ spinner.start();
1252
+ let agents;
1253
+ try {
1254
+ const data = await api.get("/agents");
1255
+ agents = data.agents;
1256
+ } catch (err) {
1257
+ spinner.fail("Failed to fetch agents.");
1258
+ if (json) {
1259
+ jsonOutput({ ok: false, error: err.message });
1260
+ } else {
1261
+ error(err.message);
1262
+ }
1263
+ process.exitCode = 1;
1264
+ return;
1265
+ }
1266
+ if (!agents || agents.length === 0) {
1267
+ spinner.fail("No agents found in team.");
1268
+ if (json) {
1269
+ jsonOutput({ ok: false, error: "No agents found in team. Run `agt init` first." });
1270
+ } else {
1271
+ error("No agents found in team. Run `agt init` first.");
1272
+ }
1273
+ process.exitCode = 1;
1274
+ return;
1275
+ }
1276
+ const templateAgents = agents.map((a, i) => ({
1277
+ agent_id: a.agent_id,
1278
+ code_name: a.code_name,
1279
+ display_name: a.display_name,
1280
+ environment: a.environment,
1281
+ port: basePort + i
1282
+ }));
1283
+ const context = {
1284
+ agents: templateAgents,
1285
+ gateway: { port: basePort },
1286
+ variables: {}
1287
+ };
1288
+ let rendered;
1289
+ try {
1290
+ rendered = renderTemplate(tmpl.template, context);
1291
+ } catch (err) {
1292
+ spinner.fail("Template rendering failed.");
1293
+ if (json) {
1294
+ jsonOutput({ ok: false, error: err.message });
1295
+ } else {
1296
+ error(err.message);
1297
+ }
1298
+ process.exitCode = 1;
1299
+ return;
1300
+ }
1301
+ try {
1302
+ await api.post("/deployments", {
1303
+ template_id: tmpl.id,
1304
+ agent_ids: agents.map((a) => a.agent_id),
1305
+ variables: { base_port: basePort }
1306
+ });
1307
+ } catch (deployErr) {
1308
+ spinner.stop();
1309
+ if (!json) info(`Warning: could not record deployment in database: ${deployErr.message}`);
1310
+ }
1311
+ const outDir = join4(process.cwd(), ".augmented", "deploy");
1312
+ mkdirSync2(outDir, { recursive: true });
1313
+ const outPath = join4(outDir, "docker-compose.yaml");
1314
+ writeFileSync2(outPath, rendered);
1315
+ spinner.succeed("Deployment config generated.");
1316
+ if (json) {
1317
+ jsonOutput({
1318
+ ok: true,
1319
+ template: tmpl.id,
1320
+ agents: agents.map((a) => a.code_name),
1321
+ output_path: outPath
1322
+ });
1323
+ return;
1324
+ }
1325
+ console.log();
1326
+ info(`Template: ${chalk10.bold(tmpl.name)}`);
1327
+ info(`Agents: ${agents.map((a) => a.code_name).join(", ")}`);
1328
+ info(`Output: ${outPath}`);
1329
+ console.log();
1330
+ info(`Next steps:`);
1331
+ info(` cd .augmented/deploy && docker compose up`);
1332
+ }
1333
+
1334
+ // src/commands/provision.ts
1335
+ import chalk11 from "chalk";
1336
+ import ora9 from "ora";
1337
+ import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
1338
+ import { join as join5 } from "path";
1339
+ function printDiagnostics3(diagnostics) {
1340
+ for (const d of diagnostics) {
1341
+ const prefix = d.severity === "error" ? chalk11.red("ERR") : chalk11.yellow("WARN");
1342
+ const path = d.path ? chalk11.dim(` (${d.path})`) : "";
1343
+ console.log(` ${prefix} [${d.code}] ${d.message}${path}`);
1344
+ }
1345
+ }
1346
+ async function provisionCommand(codeName, options) {
1347
+ const teamSlug = requireTeam();
1348
+ if (!teamSlug) return;
1349
+ const json = isJsonMode();
1350
+ const target = options.target ?? "local_docker";
1351
+ const outputDir = options.output ?? join5(process.cwd(), ".augmented", codeName, "provision");
1352
+ const dryRun = options.dryRun ?? false;
1353
+ if (!json) console.log(chalk11.bold("\nAugmented \u2014 Provision\n"));
1354
+ const spinner = ora9({ text: "Fetching agent data\u2026", isSilent: json });
1355
+ spinner.start();
1356
+ let provisionData;
1357
+ try {
1358
+ provisionData = await api.get(`/agents/${encodeURIComponent(codeName)}/provision-data`);
1359
+ } catch (err) {
1360
+ spinner.fail(`Could not fetch provision data for "${codeName}".`);
1361
+ if (json) {
1362
+ jsonOutput({ ok: false, error: err.message });
1363
+ } else {
1364
+ error(err.message);
1365
+ }
1366
+ process.exitCode = 1;
1367
+ return;
1368
+ }
1369
+ const agentData = provisionData.agent;
1370
+ if (!provisionData.charter) {
1371
+ spinner.fail("No CHARTER.md version found.");
1372
+ if (json) jsonOutput({ ok: false, error: "No CHARTER.md version found" });
1373
+ else error("No CHARTER.md version found for this agent");
1374
+ process.exitCode = 1;
1375
+ return;
1376
+ }
1377
+ if (!provisionData.tools) {
1378
+ spinner.fail("No TOOLS.md version found.");
1379
+ if (json) jsonOutput({ ok: false, error: "No TOOLS.md version found" });
1380
+ else error("No TOOLS.md version found for this agent");
1381
+ process.exitCode = 1;
1382
+ return;
1383
+ }
1384
+ spinner.text = "Parsing frontmatter\u2026";
1385
+ const charterContent = provisionData.charter.raw_content;
1386
+ const toolsContent = provisionData.tools.raw_content;
1387
+ const charterParsed = extractFrontmatter(charterContent);
1388
+ if (!charterParsed.frontmatter) {
1389
+ spinner.fail("Failed to parse CHARTER.md frontmatter.");
1390
+ if (json) jsonOutput({ ok: false, error: charterParsed.error ?? "Unknown parse error" });
1391
+ else error(charterParsed.error ?? "Unknown parse error");
1392
+ process.exitCode = 1;
1393
+ return;
1394
+ }
1395
+ const toolsParsed = extractFrontmatter(toolsContent);
1396
+ if (!toolsParsed.frontmatter) {
1397
+ spinner.fail("Failed to parse TOOLS.md frontmatter.");
1398
+ if (json) jsonOutput({ ok: false, error: toolsParsed.error ?? "Unknown parse error" });
1399
+ else error(toolsParsed.error ?? "Unknown parse error");
1400
+ process.exitCode = 1;
1401
+ return;
1402
+ }
1403
+ const charterFrontmatter = charterParsed.frontmatter;
1404
+ const toolsFrontmatter = toolsParsed.frontmatter;
1405
+ spinner.text = "Linting\u2026";
1406
+ const lintResult = lintAll(charterContent, toolsContent);
1407
+ if (!lintResult.ok) {
1408
+ spinner.fail("Lint errors \u2014 cannot provision.");
1409
+ if (json) {
1410
+ jsonOutput({
1411
+ ok: false,
1412
+ error: "Lint errors \u2014 cannot provision",
1413
+ lint: {
1414
+ errors: lintResult.errors.length,
1415
+ warnings: lintResult.warnings.length,
1416
+ diagnostics: [...lintResult.errors, ...lintResult.warnings]
1417
+ }
1418
+ });
1419
+ } else {
1420
+ printDiagnostics3(lintResult.errors);
1421
+ if (lintResult.warnings.length > 0) printDiagnostics3(lintResult.warnings);
1422
+ }
1423
+ process.exitCode = 1;
1424
+ return;
1425
+ }
1426
+ if (lintResult.warnings.length > 0 && !json) {
1427
+ spinner.stop();
1428
+ warn(`Lint: ${lintResult.warnings.length} warning(s)`);
1429
+ printDiagnostics3(lintResult.warnings);
1430
+ spinner.start("Provisioning\u2026");
1431
+ }
1432
+ spinner.text = "Resolving channels\u2026";
1433
+ const configuredChannels = Object.keys(provisionData.channel_configs ?? {});
1434
+ const agentChannelPolicy = {
1435
+ policy: "allowlist",
1436
+ allowed: configuredChannels,
1437
+ denied: [],
1438
+ require_approval_to_change: true
1439
+ };
1440
+ let teamChannelPolicy;
1441
+ const policyData = provisionData.team_channel_policy;
1442
+ if (policyData) {
1443
+ teamChannelPolicy = {
1444
+ team_id: policyData.team_id,
1445
+ allowed_channels: policyData.allowed_channels ?? [],
1446
+ denied_channels: policyData.denied_channels ?? [],
1447
+ require_elevated_for_pii: policyData.require_elevated_for_pii ?? false
1448
+ };
1449
+ }
1450
+ const resolvedChannels = resolveChannels(agentChannelPolicy, teamChannelPolicy);
1451
+ const frameworkId = agentData.framework ?? "openclaw";
1452
+ const adapter = getFramework(frameworkId);
1453
+ spinner.text = `Building ${adapter.label} config\u2026`;
1454
+ const provisionInput = {
1455
+ agent: agentData,
1456
+ charterFrontmatter,
1457
+ charterContent,
1458
+ toolsFrontmatter,
1459
+ toolsContent,
1460
+ resolvedChannels,
1461
+ deploymentTarget: target,
1462
+ gatewayPort: 9e3
1463
+ };
1464
+ const provisionOutput = provision(provisionInput, frameworkId);
1465
+ const templateId = target === "local_docker" ? "shared-gateway-local" : "shared-gateway-local";
1466
+ const tmpl = getTemplate(templateId);
1467
+ let deploymentYaml = "";
1468
+ if (tmpl) {
1469
+ const templateContext = {
1470
+ agents: [{
1471
+ agent_id: agentData.agent_id,
1472
+ code_name: agentData.code_name,
1473
+ display_name: agentData.display_name,
1474
+ environment: agentData.environment,
1475
+ port: 9e3
1476
+ }],
1477
+ gateway: { port: 9e3 },
1478
+ variables: {}
1479
+ };
1480
+ deploymentYaml = renderTemplate(tmpl.template, templateContext);
1481
+ }
1482
+ if (dryRun) {
1483
+ spinner.stop();
1484
+ if (json) {
1485
+ jsonOutput({
1486
+ ok: true,
1487
+ dry_run: true,
1488
+ agent: codeName,
1489
+ framework: frameworkId,
1490
+ target,
1491
+ charter_hash: provisionOutput.charterHash,
1492
+ tools_hash: provisionOutput.toolsHash,
1493
+ channels: resolvedChannels.length,
1494
+ artifacts: provisionOutput.artifacts.map((a) => ({
1495
+ path: a.relativePath,
1496
+ size: a.content.length
1497
+ })),
1498
+ deployment_yaml: deploymentYaml || null
1499
+ });
1500
+ return;
1501
+ }
1502
+ console.log();
1503
+ info("Dry run \u2014 no files written.");
1504
+ console.log();
1505
+ for (const artifact of provisionOutput.artifacts) {
1506
+ console.log(chalk11.bold(`${artifact.relativePath}:`));
1507
+ console.log(artifact.content);
1508
+ }
1509
+ if (deploymentYaml) {
1510
+ console.log(chalk11.bold("Deployment Template:"));
1511
+ console.log(deploymentYaml);
1512
+ }
1513
+ console.log();
1514
+ info(`CHARTER hash: ${provisionOutput.charterHash}`);
1515
+ info(`TOOLS hash: ${provisionOutput.toolsHash}`);
1516
+ return;
1517
+ }
1518
+ spinner.text = "Writing files\u2026";
1519
+ mkdirSync3(outputDir, { recursive: true });
1520
+ for (const artifact of provisionOutput.artifacts) {
1521
+ writeFileSync3(join5(outputDir, artifact.relativePath), artifact.content);
1522
+ }
1523
+ if (deploymentYaml) {
1524
+ writeFileSync3(join5(outputDir, "docker-compose.yaml"), deploymentYaml);
1525
+ }
1526
+ spinner.text = "Storing provision snapshot\u2026";
1527
+ try {
1528
+ await api.post(`/agents/${encodeURIComponent(codeName)}/provision-snapshots`, {
1529
+ charter_version: provisionData.charter.version,
1530
+ tools_version: provisionData.tools.version,
1531
+ charter_content: charterContent,
1532
+ tools_content: toolsContent,
1533
+ channels: resolvedChannels
1534
+ });
1535
+ } catch (err) {
1536
+ spinner.stop();
1537
+ if (!json) warn(`Provision completed but could not store snapshot: ${err.message}`);
1538
+ }
1539
+ spinner.succeed(`Agent "${chalk11.bold(agentData.display_name)}" provisioned.`);
1540
+ if (json) {
1541
+ jsonOutput({
1542
+ ok: true,
1543
+ agent: codeName,
1544
+ framework: frameworkId,
1545
+ target,
1546
+ output_dir: outputDir,
1547
+ charter_hash: provisionOutput.charterHash,
1548
+ tools_hash: provisionOutput.toolsHash,
1549
+ channels: resolvedChannels.length,
1550
+ artifacts: provisionOutput.artifacts.map((a) => a.relativePath)
1551
+ });
1552
+ return;
1553
+ }
1554
+ console.log();
1555
+ info(`Agent: ${agentData.code_name}`);
1556
+ info(`Framework: ${adapter.label}`);
1557
+ info(`Target: ${target}`);
1558
+ info(`Output: ${outputDir}`);
1559
+ info(`CHARTER hash: ${provisionOutput.charterHash.slice(0, 12)}\u2026`);
1560
+ info(`TOOLS hash: ${provisionOutput.toolsHash.slice(0, 12)}\u2026`);
1561
+ info(`Channels: ${resolvedChannels.length} active`);
1562
+ info(`Artifacts: ${provisionOutput.artifacts.map((a) => a.relativePath).join(", ")}`);
1563
+ console.log();
1564
+ info("Next steps:");
1565
+ info(` cd ${outputDir} && docker compose up`);
1566
+ info(` agt drift check ${agentData.code_name}`);
1567
+ }
1568
+
1569
+ // src/commands/drift.ts
1570
+ import chalk12 from "chalk";
1571
+ import ora10 from "ora";
1572
+
1573
+ // ../../packages/core/dist/drift/live-state-reader.js
1574
+ import { readFile } from "fs/promises";
1575
+ import { createHash } from "crypto";
1576
+ import { join as join6 } from "path";
1577
+ import JSON5 from "json5";
1578
+ async function hashFile(filePath) {
1579
+ try {
1580
+ const content = await readFile(filePath);
1581
+ return createHash("sha256").update(content).digest("hex");
1582
+ } catch {
1583
+ return null;
1584
+ }
1585
+ }
1586
+ async function readJsonFile(filePath) {
1587
+ try {
1588
+ const content = await readFile(filePath, "utf-8");
1589
+ return JSON5.parse(content);
1590
+ } catch {
1591
+ return null;
1592
+ }
1593
+ }
1594
+ async function readLiveState(options) {
1595
+ const trackedFiles = options.trackedFiles ?? ["openclaw.json5", "CHARTER.md", "TOOLS.md"];
1596
+ const charterFile = trackedFiles.find((f) => f.includes("CHARTER")) ?? "CHARTER.md";
1597
+ const toolsFile = trackedFiles.find((f) => f.includes("TOOLS")) ?? "TOOLS.md";
1598
+ const [frameworkConfig, charterHash, toolsHash] = await Promise.all([
1599
+ readJsonFile(options.configPath),
1600
+ hashFile(join6(options.teamDir, charterFile)),
1601
+ hashFile(join6(options.teamDir, toolsFile))
1602
+ ]);
1603
+ return {
1604
+ frameworkConfig,
1605
+ charterHash,
1606
+ toolsHash,
1607
+ configPath: options.configPath
1608
+ };
1609
+ }
1610
+
1611
+ // src/commands/drift.ts
1612
+ var KEBAB_CASE_RE = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
1613
+ function severityColor(severity) {
1614
+ switch (severity) {
1615
+ case "critical":
1616
+ return chalk12.red(severity);
1617
+ case "warning":
1618
+ return chalk12.yellow(severity);
1619
+ default:
1620
+ return chalk12.dim(severity);
1621
+ }
1622
+ }
1623
+ function printDriftReport(report) {
1624
+ console.log(chalk12.bold(`
1625
+ Drift Report: ${report.codeName}
1626
+ `));
1627
+ info(`Agent ID: ${report.agentId}`);
1628
+ info(`Checked at: ${report.checkedAt.toISOString()}`);
1629
+ console.log();
1630
+ if (!report.hasDrift) {
1631
+ success("No drift detected. Agent matches provisioned state.");
1632
+ return;
1633
+ }
1634
+ warn(`Drift detected: ${report.criticalCount} critical, ${report.warningCount} warning`);
1635
+ console.log();
1636
+ const rows = report.findings.map((f) => [
1637
+ severityColor(f.severity),
1638
+ f.category,
1639
+ f.field,
1640
+ f.message
1641
+ ]);
1642
+ table(["Severity", "Category", "Field", "Message"], rows);
1643
+ }
1644
+ async function driftCheckCommand(codeName, opts) {
1645
+ const teamSlug = requireTeam();
1646
+ if (!teamSlug) return;
1647
+ if (!KEBAB_CASE_RE.test(codeName)) {
1648
+ error('Invalid code-name. Must be kebab-case (e.g. "my-agent").');
1649
+ process.exitCode = 1;
1650
+ return;
1651
+ }
1652
+ const useJson = opts.json || isJsonMode();
1653
+ const spinner = ora10({ text: `Checking drift for "${codeName}"\u2026`, isSilent: useJson });
1654
+ spinner.start();
1655
+ try {
1656
+ const driftData = await api.get(`/agents/${encodeURIComponent(codeName)}/drift-data`);
1657
+ const agent2 = driftData.agent;
1658
+ const snapshot = driftData.snapshot;
1659
+ if (!snapshot) {
1660
+ spinner.fail(`No provision snapshot found for "${codeName}". Run \`agt provision\` first.`);
1661
+ error("No provision snapshot available");
1662
+ process.exitCode = 1;
1663
+ return;
1664
+ }
1665
+ const frameworkId = agent2.framework ?? "openclaw";
1666
+ const adapter = getFramework(frameworkId);
1667
+ const trackedFiles = adapter.driftTrackedFiles();
1668
+ const defaultDir = `.augmented/${teamSlug}/${codeName}/provision`;
1669
+ const configPath = opts.config ?? `${defaultDir}/${trackedFiles[0]}`;
1670
+ const liveState = await readLiveState({
1671
+ configPath,
1672
+ teamDir: defaultDir,
1673
+ trackedFiles,
1674
+ runAudit: opts.audit
1675
+ });
1676
+ const report = detectDrift(
1677
+ {
1678
+ frameworkConfig: snapshot.openclaw_config ?? {},
1679
+ charterHash: snapshot.charter_hash ?? "",
1680
+ toolsHash: snapshot.tools_hash ?? "",
1681
+ toolAllow: snapshot.tool_allow ?? [],
1682
+ toolDeny: snapshot.tool_deny ?? [],
1683
+ channelsConfig: snapshot.channels_config ?? {},
1684
+ sandboxMode: snapshot.sandbox_mode ?? "all"
1685
+ },
1686
+ liveState,
1687
+ agent2.agent_id,
1688
+ codeName,
1689
+ agent2.risk_tier ?? "Medium"
1690
+ );
1691
+ spinner.stop();
1692
+ if (useJson) {
1693
+ console.log(JSON.stringify(report, null, 2));
1694
+ } else {
1695
+ printDriftReport(report);
1696
+ }
1697
+ if (report.hasDrift) {
1698
+ process.exitCode = 1;
1699
+ }
1700
+ } catch (err) {
1701
+ spinner.fail("Drift check failed.");
1702
+ error(err.message);
1703
+ process.exitCode = 1;
1704
+ }
1705
+ }
1706
+ async function driftWatchCommand(codeName, opts) {
1707
+ const teamSlug = requireTeam();
1708
+ if (!teamSlug) return;
1709
+ if (!KEBAB_CASE_RE.test(codeName)) {
1710
+ error('Invalid code-name. Must be kebab-case (e.g. "my-agent").');
1711
+ process.exitCode = 1;
1712
+ return;
1713
+ }
1714
+ const useJson = opts.json || isJsonMode();
1715
+ const intervalSec = parseInt(opts.interval ?? "60", 10);
1716
+ if (isNaN(intervalSec) || intervalSec < 1) {
1717
+ error("Interval must be a positive number of seconds.");
1718
+ process.exitCode = 1;
1719
+ return;
1720
+ }
1721
+ let agent2;
1722
+ try {
1723
+ const data = await api.get(`/agents/${encodeURIComponent(codeName)}`);
1724
+ agent2 = data.agent;
1725
+ } catch (err) {
1726
+ error(err.message);
1727
+ process.exitCode = 1;
1728
+ return;
1729
+ }
1730
+ const frameworkId = agent2.framework ?? "openclaw";
1731
+ const adapter = getFramework(frameworkId);
1732
+ const trackedFiles = adapter.driftTrackedFiles();
1733
+ if (!useJson) {
1734
+ console.log(
1735
+ chalk12.bold(`
1736
+ Watching drift for "${codeName}" every ${intervalSec}s. Press Ctrl+C to stop.
1737
+ `)
1738
+ );
1739
+ }
1740
+ let previousFindingCount = -1;
1741
+ const runCheck = async () => {
1742
+ const spinner = ora10({ text: `Checking drift for "${codeName}"\u2026`, isSilent: useJson });
1743
+ spinner.start();
1744
+ try {
1745
+ const driftData = await api.get(`/agents/${encodeURIComponent(codeName)}/drift-data`);
1746
+ const snapshot = driftData.snapshot;
1747
+ if (!snapshot) {
1748
+ spinner.warn("No provision snapshot found. Skipping check.");
1749
+ return;
1750
+ }
1751
+ const watchDefaultDir = `.augmented/${teamSlug}/${codeName}/provision`;
1752
+ const configPath = opts.config ?? `${watchDefaultDir}/${trackedFiles[0]}`;
1753
+ const liveState = await readLiveState({
1754
+ configPath,
1755
+ teamDir: watchDefaultDir,
1756
+ trackedFiles
1757
+ });
1758
+ const report = detectDrift(
1759
+ {
1760
+ frameworkConfig: snapshot.openclaw_config ?? {},
1761
+ charterHash: snapshot.charter_hash ?? "",
1762
+ toolsHash: snapshot.tools_hash ?? "",
1763
+ toolAllow: snapshot.tool_allow ?? [],
1764
+ toolDeny: snapshot.tool_deny ?? [],
1765
+ channelsConfig: snapshot.channels_config ?? {},
1766
+ sandboxMode: snapshot.sandbox_mode ?? "all"
1767
+ },
1768
+ liveState,
1769
+ agent2.agent_id,
1770
+ codeName,
1771
+ agent2.risk_tier ?? "Medium"
1772
+ );
1773
+ spinner.stop();
1774
+ const isNew = report.findings.length !== previousFindingCount;
1775
+ previousFindingCount = report.findings.length;
1776
+ if (useJson) {
1777
+ console.log(JSON.stringify(report, null, 2));
1778
+ } else if (report.hasDrift) {
1779
+ printDriftReport(report);
1780
+ } else {
1781
+ success(`[${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] No drift detected.`);
1782
+ }
1783
+ if (isNew && report.hasDrift && opts.webhook) {
1784
+ try {
1785
+ await fetch(opts.webhook, {
1786
+ method: "POST",
1787
+ headers: { "Content-Type": "application/json" },
1788
+ body: JSON.stringify(report)
1789
+ });
1790
+ if (!useJson) info(`Webhook notified: ${opts.webhook}`);
1791
+ } catch (webhookErr) {
1792
+ if (!useJson) warn(`Failed to notify webhook: ${webhookErr.message}`);
1793
+ }
1794
+ }
1795
+ } catch (err) {
1796
+ spinner.fail("Drift check failed.");
1797
+ error(err.message);
1798
+ }
1799
+ };
1800
+ await runCheck();
1801
+ const timer = setInterval(() => {
1802
+ void runCheck();
1803
+ }, intervalSec * 1e3);
1804
+ process.on("SIGINT", () => {
1805
+ clearInterval(timer);
1806
+ if (!useJson) console.log(chalk12.dim("\nStopped watching."));
1807
+ process.exit(0);
1808
+ });
1809
+ }
1810
+
1811
+ // src/commands/host.ts
1812
+ import chalk13 from "chalk";
1813
+ import ora11 from "ora";
1814
+ async function hostListCommand() {
1815
+ const teamSlug = requireTeam();
1816
+ if (!teamSlug) return;
1817
+ const json = isJsonMode();
1818
+ const spinner = ora11({ text: "Fetching hosts\u2026", isSilent: json });
1819
+ spinner.start();
1820
+ try {
1821
+ const data = await api.get("/hosts");
1822
+ spinner.stop();
1823
+ if (!data.hosts || data.hosts.length === 0) {
1824
+ if (json) {
1825
+ jsonOutput({ ok: true, hosts: [] });
1826
+ } else {
1827
+ info("No hosts found. Create one with `agt host create --name <name>`.");
1828
+ }
1829
+ return;
1830
+ }
1831
+ if (json) {
1832
+ jsonOutput({ ok: true, hosts: data.hosts });
1833
+ return;
1834
+ }
1835
+ const rows = data.hosts.map((h) => {
1836
+ const status = h.status === "active" ? chalk13.green("active") : chalk13.red("decommissioned");
1837
+ const agents = String(h.agents);
1838
+ const lastSeen = h.last_seen_at ? new Date(h.last_seen_at).toLocaleDateString() : chalk13.dim("never");
1839
+ const prefix = h.key_prefix ? `tlk_${h.key_prefix}\u2026` : chalk13.dim("none");
1840
+ return [h.name, status, agents, lastSeen, prefix];
1841
+ });
1842
+ table(["Name", "Status", "Agents", "Last Seen", "Key"], rows);
1843
+ } catch (err) {
1844
+ spinner.fail("Failed to fetch hosts.");
1845
+ if (json) {
1846
+ jsonOutput({ ok: false, error: err.message });
1847
+ } else {
1848
+ error(err.message);
1849
+ }
1850
+ process.exitCode = 1;
1851
+ }
1852
+ }
1853
+ async function hostAssignCommand(hostName, agentCodeNames, opts) {
1854
+ const teamSlug = requireTeam();
1855
+ if (!teamSlug) return;
1856
+ const json = isJsonMode();
1857
+ if (!agentCodeNames || agentCodeNames.length === 0) {
1858
+ if (json) {
1859
+ jsonOutput({ ok: false, error: "At least one agent code name is required" });
1860
+ } else {
1861
+ error("At least one agent code name is required.");
1862
+ }
1863
+ process.exitCode = 1;
1864
+ return;
1865
+ }
1866
+ const spinner = ora11({ text: "Assigning agents\u2026", isSilent: json });
1867
+ spinner.start();
1868
+ try {
1869
+ await api.post(`/hosts/${encodeURIComponent(hostName)}/assign`, {
1870
+ agents: agentCodeNames,
1871
+ force: opts.force
1872
+ });
1873
+ spinner.succeed(`Agents assigned to ${chalk13.bold(hostName)}.`);
1874
+ if (json) {
1875
+ jsonOutput({ ok: true, host: hostName, assigned: agentCodeNames });
1876
+ return;
1877
+ }
1878
+ for (const name of agentCodeNames) {
1879
+ info(` ${name}`);
1880
+ }
1881
+ } catch (err) {
1882
+ spinner.fail("Failed to assign agents.");
1883
+ if (json) {
1884
+ jsonOutput({ ok: false, error: err.message });
1885
+ } else {
1886
+ error(err.message);
1887
+ }
1888
+ process.exitCode = 1;
1889
+ }
1890
+ }
1891
+ async function hostUnassignCommand(hostName, agentCodeNames) {
1892
+ const teamSlug = requireTeam();
1893
+ if (!teamSlug) return;
1894
+ const json = isJsonMode();
1895
+ if (!agentCodeNames || agentCodeNames.length === 0) {
1896
+ if (json) {
1897
+ jsonOutput({ ok: false, error: "At least one agent code name is required" });
1898
+ } else {
1899
+ error("At least one agent code name is required.");
1900
+ }
1901
+ process.exitCode = 1;
1902
+ return;
1903
+ }
1904
+ const spinner = ora11({ text: "Unassigning agents\u2026", isSilent: json });
1905
+ spinner.start();
1906
+ try {
1907
+ await api.post(`/hosts/${encodeURIComponent(hostName)}/unassign`, {
1908
+ agents: agentCodeNames
1909
+ });
1910
+ spinner.succeed(`Agents unassigned from ${chalk13.bold(hostName)}.`);
1911
+ if (json) {
1912
+ jsonOutput({ ok: true, host: hostName, unassigned: agentCodeNames });
1913
+ return;
1914
+ }
1915
+ for (const name of agentCodeNames) {
1916
+ info(` ${name}`);
1917
+ }
1918
+ } catch (err) {
1919
+ spinner.fail("Failed to unassign agents.");
1920
+ if (json) {
1921
+ jsonOutput({ ok: false, error: err.message });
1922
+ } else {
1923
+ error(err.message);
1924
+ }
1925
+ process.exitCode = 1;
1926
+ }
1927
+ }
1928
+ async function hostAgentsCommand(hostName) {
1929
+ const json = isJsonMode();
1930
+ const spinner = ora11({ text: "Fetching host agents\u2026", isSilent: json });
1931
+ spinner.start();
1932
+ try {
1933
+ const hostId = await getHostId();
1934
+ if (!hostName && !hostId) {
1935
+ spinner.fail("No host specified.");
1936
+ if (json) {
1937
+ jsonOutput({ ok: false, error: "Provide a host name or use an AGT_API_KEY" });
1938
+ } else {
1939
+ error("Provide a host name, or set AGT_API_KEY to auto-resolve the host.");
1940
+ }
1941
+ process.exitCode = 1;
1942
+ return;
1943
+ }
1944
+ let agents;
1945
+ if (hostId && !hostName) {
1946
+ const data = await api.post("/host/agents", { host_id: hostId });
1947
+ agents = data.agents ?? [];
1948
+ } else {
1949
+ const teamSlug = requireTeam();
1950
+ if (!teamSlug) return;
1951
+ const data = await api.get(`/hosts/${encodeURIComponent(hostName)}/agents`);
1952
+ agents = data.agents ?? [];
1953
+ }
1954
+ spinner.stop();
1955
+ if (agents.length === 0) {
1956
+ if (json) {
1957
+ jsonOutput({ ok: true, agents: [] });
1958
+ } else {
1959
+ info("No agents assigned to this host.");
1960
+ info("Assign agents with `agt host assign <host-name> <agent-code-name>`.");
1961
+ }
1962
+ return;
1963
+ }
1964
+ if (json) {
1965
+ jsonOutput({ ok: true, agents });
1966
+ return;
1967
+ }
1968
+ const rows = agents.map((a) => {
1969
+ const statusColor = a.status === "active" ? chalk13.green : chalk13.yellow;
1970
+ return [
1971
+ a.code_name,
1972
+ a.display_name,
1973
+ statusColor(a.status),
1974
+ a.environment
1975
+ ];
1976
+ });
1977
+ table(["Code Name", "Display Name", "Status", "Environment"], rows);
1978
+ } catch (err) {
1979
+ spinner.fail("Failed to fetch host agents.");
1980
+ if (json) {
1981
+ jsonOutput({ ok: false, error: err.message });
1982
+ } else {
1983
+ error(err.message);
1984
+ }
1985
+ process.exitCode = 1;
1986
+ }
1987
+ }
1988
+ async function hostRotateKeyCommand(hostName) {
1989
+ const teamSlug = requireTeam();
1990
+ if (!teamSlug) return;
1991
+ const json = isJsonMode();
1992
+ const spinner = ora11({ text: "Rotating host key\u2026", isSilent: json });
1993
+ spinner.start();
1994
+ try {
1995
+ const data = await api.post(`/hosts/${encodeURIComponent(hostName)}/rotate-key`);
1996
+ spinner.succeed(`Key rotated for ${chalk13.bold(hostName)}.`);
1997
+ if (json) {
1998
+ jsonOutput({
1999
+ ok: true,
2000
+ host: hostName,
2001
+ key: data.key
2002
+ });
2003
+ return;
2004
+ }
2005
+ console.log();
2006
+ info(`Host: ${chalk13.bold(hostName)}`);
2007
+ info(`Prefix: ${data.key.prefix}`);
2008
+ console.log();
2009
+ console.log(chalk13.yellow.bold(" Save this key now \u2014 it will not be shown again:"));
2010
+ console.log();
2011
+ console.log(` ${chalk13.green.bold(data.key.raw_key)}`);
2012
+ console.log();
2013
+ } catch (err) {
2014
+ spinner.fail("Failed to rotate key.");
2015
+ if (json) {
2016
+ jsonOutput({ ok: false, error: err.message });
2017
+ } else {
2018
+ error(err.message);
2019
+ }
2020
+ process.exitCode = 1;
2021
+ }
2022
+ }
2023
+ async function hostDecommissionCommand(hostName) {
2024
+ const teamSlug = requireTeam();
2025
+ if (!teamSlug) return;
2026
+ const json = isJsonMode();
2027
+ const spinner = ora11({ text: "Decommissioning host\u2026", isSilent: json });
2028
+ spinner.start();
2029
+ try {
2030
+ await api.post(`/hosts/${encodeURIComponent(hostName)}/decommission`);
2031
+ spinner.succeed(`Host "${chalk13.bold(hostName)}" decommissioned.`);
2032
+ if (json) {
2033
+ jsonOutput({
2034
+ ok: true,
2035
+ host: hostName,
2036
+ status: "decommissioned"
2037
+ });
2038
+ return;
2039
+ }
2040
+ info(`Host: ${hostName}`);
2041
+ info(`Status: ${chalk13.red("decommissioned")}`);
2042
+ info("API key revoked. Agents remain assigned for audit visibility.");
2043
+ info("Reassign agents to another host with `agt host assign --force`.");
2044
+ } catch (err) {
2045
+ spinner.fail("Failed to decommission host.");
2046
+ if (json) {
2047
+ jsonOutput({ ok: false, error: err.message });
2048
+ } else {
2049
+ error(err.message);
2050
+ }
2051
+ process.exitCode = 1;
2052
+ }
2053
+ }
2054
+
2055
+ // src/commands/manager.ts
2056
+ import chalk14 from "chalk";
2057
+ import { join as join8 } from "path";
2058
+ import { homedir } from "os";
2059
+
2060
+ // src/lib/watchdog.ts
2061
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync4, unlinkSync, existsSync as existsSync2, mkdirSync as mkdirSync4 } from "fs";
2062
+ import { join as join7 } from "path";
2063
+ var AUGMENTED_DIR = join7(process.env["HOME"] ?? "/tmp", ".augmented");
2064
+ var PID_FILE = join7(AUGMENTED_DIR, "manager.pid");
2065
+ var STATE_FILE = join7(AUGMENTED_DIR, "manager-state.json");
2066
+ function ensureDir() {
2067
+ if (!existsSync2(AUGMENTED_DIR)) {
2068
+ mkdirSync4(AUGMENTED_DIR, { recursive: true });
2069
+ }
2070
+ }
2071
+ function writePidFile(pid) {
2072
+ ensureDir();
2073
+ writeFileSync4(PID_FILE, String(pid), { mode: 384 });
2074
+ }
2075
+ function readPidFile() {
2076
+ try {
2077
+ const raw = readFileSync2(PID_FILE, "utf-8").trim();
2078
+ const pid = parseInt(raw, 10);
2079
+ return isNaN(pid) ? null : pid;
2080
+ } catch {
2081
+ return null;
2082
+ }
2083
+ }
2084
+ function removePidFile() {
2085
+ try {
2086
+ unlinkSync(PID_FILE);
2087
+ } catch {
2088
+ }
2089
+ }
2090
+ function isProcessAlive(pid) {
2091
+ try {
2092
+ process.kill(pid, 0);
2093
+ return true;
2094
+ } catch {
2095
+ return false;
2096
+ }
2097
+ }
2098
+ function readStateFile() {
2099
+ try {
2100
+ const raw = readFileSync2(STATE_FILE, "utf-8");
2101
+ return JSON.parse(raw);
2102
+ } catch {
2103
+ return null;
2104
+ }
2105
+ }
2106
+ function removeStateFile() {
2107
+ try {
2108
+ unlinkSync(STATE_FILE);
2109
+ } catch {
2110
+ }
2111
+ }
2112
+ function startWatchdog(opts) {
2113
+ const existingPid = readPidFile();
2114
+ if (existingPid !== null) {
2115
+ if (isProcessAlive(existingPid)) {
2116
+ throw new Error(`Manager already running (PID ${existingPid}). Use \`agt manager stop\` first.`);
2117
+ }
2118
+ removePidFile();
2119
+ removeStateFile();
2120
+ }
2121
+ writePidFile(process.pid);
2122
+ void import("../lib/manager-worker.js").then(({ startManager }) => {
2123
+ startManager({
2124
+ intervalMs: opts.intervalMs,
2125
+ configDir: opts.configDir
2126
+ });
2127
+ });
2128
+ process.on("exit", () => {
2129
+ removePidFile();
2130
+ });
2131
+ return { pid: process.pid };
2132
+ }
2133
+ async function stopWatchdog() {
2134
+ const pid = readPidFile();
2135
+ if (pid === null) {
2136
+ return { stopped: false };
2137
+ }
2138
+ if (!isProcessAlive(pid)) {
2139
+ removePidFile();
2140
+ removeStateFile();
2141
+ return { stopped: true, pid };
2142
+ }
2143
+ process.kill(pid, "SIGTERM");
2144
+ const deadline = Date.now() + 5e3;
2145
+ while (Date.now() < deadline) {
2146
+ await new Promise((r) => setTimeout(r, 200));
2147
+ if (!isProcessAlive(pid)) {
2148
+ removePidFile();
2149
+ return { stopped: true, pid };
2150
+ }
2151
+ }
2152
+ try {
2153
+ process.kill(pid, "SIGKILL");
2154
+ } catch {
2155
+ }
2156
+ removePidFile();
2157
+ removeStateFile();
2158
+ return { stopped: true, pid };
2159
+ }
2160
+ function getManagerStatus() {
2161
+ const pid = readPidFile();
2162
+ if (pid === null) return null;
2163
+ if (!isProcessAlive(pid)) {
2164
+ removePidFile();
2165
+ removeStateFile();
2166
+ return null;
2167
+ }
2168
+ return readStateFile();
2169
+ }
2170
+
2171
+ // src/commands/manager.ts
2172
+ function managerStartCommand(opts) {
2173
+ const json = isJsonMode();
2174
+ if (!AGT_HOST) {
2175
+ const msg = "AGT_HOST is not set. Export it to point at the Augmented API (e.g. export AGT_HOST=https://your-api.example.com)";
2176
+ if (json) {
2177
+ jsonOutput({ ok: false, error: msg });
2178
+ } else {
2179
+ error(msg);
2180
+ }
2181
+ process.exitCode = 1;
2182
+ return;
2183
+ }
2184
+ const apiKey = getApiKey();
2185
+ if (!apiKey) {
2186
+ const msg = "AGT_API_KEY is not set. Export it with your host API key (tlk_...)";
2187
+ if (json) {
2188
+ jsonOutput({ ok: false, error: msg });
2189
+ } else {
2190
+ error(msg);
2191
+ }
2192
+ process.exitCode = 1;
2193
+ return;
2194
+ }
2195
+ const intervalSec = parseInt(opts.interval ?? "10", 10);
2196
+ if (isNaN(intervalSec) || intervalSec < 5) {
2197
+ if (json) {
2198
+ jsonOutput({ ok: false, error: "Interval must be at least 10 seconds" });
2199
+ } else {
2200
+ error("Interval must be at least 10 seconds.");
2201
+ }
2202
+ process.exitCode = 1;
2203
+ return;
2204
+ }
2205
+ const configDir = opts.configDir ?? join8(homedir(), ".augmented");
2206
+ try {
2207
+ const { pid } = startWatchdog({
2208
+ intervalMs: intervalSec * 1e3,
2209
+ configDir
2210
+ });
2211
+ if (json) {
2212
+ jsonOutput({ ok: true, pid, interval: intervalSec, configDir });
2213
+ } else {
2214
+ success(`Manager started (PID ${pid}, interval ${intervalSec}s)`);
2215
+ info(`Config dir: ${configDir}`);
2216
+ info("Stop with: agt manager stop");
2217
+ }
2218
+ } catch (err) {
2219
+ if (json) {
2220
+ jsonOutput({ ok: false, error: err.message });
2221
+ } else {
2222
+ error(err.message);
2223
+ }
2224
+ process.exitCode = 1;
2225
+ }
2226
+ }
2227
+ async function managerStopCommand() {
2228
+ const json = isJsonMode();
2229
+ try {
2230
+ const result = await stopWatchdog();
2231
+ if (!result.stopped && !result.pid) {
2232
+ if (json) {
2233
+ jsonOutput({ ok: false, error: "Manager is not running" });
2234
+ } else {
2235
+ error("Manager is not running.");
2236
+ }
2237
+ process.exitCode = 1;
2238
+ return;
2239
+ }
2240
+ if (json) {
2241
+ jsonOutput({ ok: true, stopped: true, pid: result.pid });
2242
+ } else {
2243
+ success(`Manager stopped (PID ${result.pid})`);
2244
+ }
2245
+ } catch (err) {
2246
+ if (json) {
2247
+ jsonOutput({ ok: false, error: err.message });
2248
+ } else {
2249
+ error(err.message);
2250
+ }
2251
+ process.exitCode = 1;
2252
+ }
2253
+ }
2254
+ function managerStatusCommand() {
2255
+ const json = isJsonMode();
2256
+ const status = getManagerStatus();
2257
+ if (!status) {
2258
+ if (json) {
2259
+ jsonOutput({ ok: true, running: false });
2260
+ } else {
2261
+ info("Manager is not running.");
2262
+ }
2263
+ return;
2264
+ }
2265
+ if (json) {
2266
+ jsonOutput({ ok: true, running: true, ...status });
2267
+ return;
2268
+ }
2269
+ console.log(chalk14.bold("\nManager Status\n"));
2270
+ info(`PID: ${status.pid}`);
2271
+ info(`Started: ${status.startedAt}`);
2272
+ info(`Last poll: ${status.lastPollAt ?? chalk14.dim("none")}`);
2273
+ info(`Polls: ${status.pollCount}`);
2274
+ info(`Errors: ${status.errorCount}`);
2275
+ console.log();
2276
+ if (status.agents.length === 0) {
2277
+ info("No agents discovered yet.");
2278
+ return;
2279
+ }
2280
+ const rows = status.agents.map((a) => {
2281
+ let gwStatus = chalk14.dim("\u2014");
2282
+ if (a.gatewayRunning) {
2283
+ gwStatus = chalk14.green(`:${a.gatewayPort} (PID ${a.gatewayPid})`);
2284
+ } else if (a.gatewayPort) {
2285
+ gwStatus = chalk14.red(`:${a.gatewayPort} (down)`);
2286
+ }
2287
+ return [
2288
+ a.codeName,
2289
+ a.status === "active" ? chalk14.green(a.status) : a.status === "paused" ? chalk14.yellow(a.status) : chalk14.dim(a.status ?? "\u2014"),
2290
+ a.charterVersion || chalk14.dim("\u2014"),
2291
+ gwStatus,
2292
+ a.lastProvisionAt ? new Date(a.lastProvisionAt).toLocaleTimeString() : chalk14.dim("\u2014"),
2293
+ a.lastDriftCheckAt ? new Date(a.lastDriftCheckAt).toLocaleTimeString() : chalk14.dim("\u2014")
2294
+ ];
2295
+ });
2296
+ table(
2297
+ ["Agent", "Status", "Charter", "Gateway", "Last Provision", "Last Drift"],
2298
+ rows
2299
+ );
2300
+ const acpAgents = status.agents.filter((a) => a.acpSessions && a.acpSessions.length > 0);
2301
+ if (acpAgents.length > 0) {
2302
+ console.log(chalk14.bold("\nACP Sessions\n"));
2303
+ const acpRows = acpAgents.flatMap(
2304
+ (a) => a.acpSessions.map((s) => [
2305
+ a.codeName,
2306
+ s.agentCommand,
2307
+ s.sessionName ?? chalk14.dim("default"),
2308
+ s.queueState === "running" ? chalk14.green(s.queueState) : s.queueState === "queued" ? chalk14.yellow(s.queueState) : chalk14.dim(s.queueState),
2309
+ String(s.turnCount),
2310
+ new Date(s.startedAt).toLocaleTimeString()
2311
+ ])
2312
+ );
2313
+ table(
2314
+ ["Agent", "Coding Agent", "Session", "Queue", "Turns", "Started"],
2315
+ acpRows
2316
+ );
2317
+ }
2318
+ }
2319
+
2320
+ // src/commands/agent.ts
2321
+ import chalk15 from "chalk";
2322
+ import JSON52 from "json5";
2323
+ import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
2324
+ import { join as join9 } from "path";
2325
+ async function agentShowCommand(codeName, opts) {
2326
+ const json = isJsonMode();
2327
+ const agentDir = join9(opts.configDir, codeName, "provision");
2328
+ const hasLocalConfig = existsSync3(agentDir);
2329
+ let apiChannels = null;
2330
+ let apiAgent = null;
2331
+ if (getApiKey()) {
2332
+ try {
2333
+ const data = await api.get(`/agents/${encodeURIComponent(codeName)}/provision-data`);
2334
+ apiAgent = data.agent ?? null;
2335
+ if (data.channel_configs) {
2336
+ apiChannels = Object.entries(data.channel_configs).map(
2337
+ ([channelId, val]) => ({
2338
+ channel_id: channelId,
2339
+ config: val.config,
2340
+ status: val.status
2341
+ })
2342
+ );
2343
+ }
2344
+ } catch {
2345
+ }
2346
+ }
2347
+ if (!hasLocalConfig && !apiAgent) {
2348
+ if (json) {
2349
+ jsonOutput({ ok: false, error: `Agent '${codeName}' not found` });
2350
+ } else {
2351
+ error(`Agent '${codeName}' not found`);
2352
+ }
2353
+ process.exitCode = 1;
2354
+ return;
2355
+ }
2356
+ let charter = null;
2357
+ let tools = null;
2358
+ let openclawConfig = null;
2359
+ let agentState = null;
2360
+ if (hasLocalConfig) {
2361
+ const charterPath = join9(agentDir, "CHARTER.md");
2362
+ if (existsSync3(charterPath)) {
2363
+ const raw = readFileSync3(charterPath, "utf-8");
2364
+ const parsed = extractFrontmatter(raw);
2365
+ if (parsed.frontmatter) {
2366
+ charter = parsed.frontmatter;
2367
+ }
2368
+ }
2369
+ const toolsPath = join9(agentDir, "TOOLS.md");
2370
+ if (existsSync3(toolsPath)) {
2371
+ const raw = readFileSync3(toolsPath, "utf-8");
2372
+ const parsed = extractFrontmatter(raw);
2373
+ if (parsed.frontmatter) {
2374
+ tools = parsed.frontmatter;
2375
+ }
2376
+ }
2377
+ const openclawPath = join9(agentDir, "openclaw.json5");
2378
+ if (existsSync3(openclawPath)) {
2379
+ try {
2380
+ const raw = readFileSync3(openclawPath, "utf-8");
2381
+ openclawConfig = JSON52.parse(raw);
2382
+ } catch {
2383
+ }
2384
+ }
2385
+ const statePath = join9(opts.configDir, "manager-state.json");
2386
+ if (existsSync3(statePath)) {
2387
+ try {
2388
+ const raw = readFileSync3(statePath, "utf-8");
2389
+ const state = JSON.parse(raw);
2390
+ agentState = state.agents?.find((a) => a.codeName === codeName) ?? null;
2391
+ } catch {
2392
+ }
2393
+ }
2394
+ }
2395
+ const channelRows = [];
2396
+ if (apiChannels && apiChannels.length > 0) {
2397
+ for (const ch of apiChannels) {
2398
+ const def = getChannel(ch.channel_id);
2399
+ channelRows.push({
2400
+ id: ch.channel_id,
2401
+ name: def?.name ?? ch.channel_id,
2402
+ tier: def?.securityTier ?? "unknown",
2403
+ enabled: true,
2404
+ status: ch.status,
2405
+ e2e: def ? String(def.e2eEncrypted) : "\u2014"
2406
+ });
2407
+ }
2408
+ } else {
2409
+ const openclawChannels = Array.isArray(openclawConfig?.channels) ? openclawConfig.channels : [];
2410
+ for (const ch of openclawChannels) {
2411
+ const def = getChannel(ch.id);
2412
+ channelRows.push({
2413
+ id: ch.id,
2414
+ name: def?.name ?? ch.id,
2415
+ tier: def?.securityTier ?? "unknown",
2416
+ enabled: ch.enabled !== false,
2417
+ status: ch.enabled !== false ? "configured" : "disabled",
2418
+ e2e: def ? String(def.e2eEncrypted) : "\u2014"
2419
+ });
2420
+ }
2421
+ }
2422
+ if (json) {
2423
+ const filtered2 = opts.allChannels ? channelRows : channelRows.filter((c) => c.enabled);
2424
+ jsonOutput({
2425
+ ok: true,
2426
+ agent: {
2427
+ code_name: codeName,
2428
+ display_name: apiAgent?.display_name ?? charter?.display_name ?? null,
2429
+ agent_id: apiAgent?.agent_id ?? charter?.agent_id ?? null,
2430
+ environment: apiAgent?.environment ?? charter?.environment ?? null,
2431
+ risk_tier: apiAgent?.risk_tier ?? charter?.risk_tier ?? null,
2432
+ owner: charter?.owner ?? null,
2433
+ logging_mode: charter?.logging_mode ?? null,
2434
+ budget: charter?.budget ?? null,
2435
+ limits: charter?.limits ?? null
2436
+ },
2437
+ versions: {
2438
+ charter: agentState?.charterVersion ?? charter?.version ?? null,
2439
+ tools: agentState?.toolsVersion ?? tools?.version ?? null,
2440
+ last_provision: agentState?.lastProvisionAt ?? null,
2441
+ last_drift_check: agentState?.lastDriftCheckAt ?? null
2442
+ },
2443
+ tools: {
2444
+ enforcement_mode: tools?.enforcement_mode ?? null,
2445
+ global_controls: tools?.global_controls ?? null,
2446
+ count: tools?.tools?.length ?? 0,
2447
+ items: tools?.tools ?? []
2448
+ },
2449
+ channels: filtered2,
2450
+ openclaw: openclawConfig ? { sandbox: openclawConfig.sandbox ?? null, gateway: openclawConfig.gateway ?? null } : null
2451
+ });
2452
+ return;
2453
+ }
2454
+ const displayName = apiAgent?.display_name ?? charter?.display_name ?? codeName;
2455
+ console.log(chalk15.bold(`
2456
+ Agent: ${codeName}`) + (displayName !== codeName ? ` (${displayName})` : "") + "\n");
2457
+ if (charter || apiAgent) {
2458
+ const agentId = apiAgent?.agent_id ?? charter?.agent_id;
2459
+ const environment = apiAgent?.environment ?? charter?.environment;
2460
+ const riskTier = apiAgent?.risk_tier ?? charter?.risk_tier;
2461
+ info(`Agent ID: ${agentId ?? "\u2014"}`);
2462
+ info(`Environment: ${environment ?? "\u2014"}`);
2463
+ info(`Risk Tier: ${riskTier ?? "\u2014"}`);
2464
+ info(`Owner: ${charter?.owner?.name ?? "\u2014"}`);
2465
+ const charterVer = agentState?.charterVersion ?? charter?.version;
2466
+ const toolsVer = agentState?.toolsVersion ?? tools?.version ?? "\u2014";
2467
+ const provTs = agentState?.lastProvisionAt ? formatTimestamp(agentState.lastProvisionAt) : null;
2468
+ if (charterVer) {
2469
+ info(`Charter: v${charterVer}${provTs ? ` (provisioned ${provTs})` : ""}`);
2470
+ }
2471
+ if (toolsVer !== "\u2014") {
2472
+ info(`Tools: v${toolsVer}${provTs ? ` (provisioned ${provTs})` : ""}`);
2473
+ }
2474
+ const sandbox = openclawConfig ? openclawConfig.sandbox : null;
2475
+ if (sandbox != null) {
2476
+ info(`Sandbox: ${sandbox ? "on" : "off"}`);
2477
+ }
2478
+ const b = charter?.budget;
2479
+ if (b) {
2480
+ const budgetStr = `${b.limit.toLocaleString()} ${b.type} / ${b.window} (${b.enforcement ?? "block"})`;
2481
+ info(`Budget: ${budgetStr}`);
2482
+ }
2483
+ } else {
2484
+ info("No agent metadata available");
2485
+ }
2486
+ const filtered = opts.allChannels ? channelRows : channelRows.filter((c) => c.enabled);
2487
+ console.log(chalk15.bold("\nChannels:"));
2488
+ if (filtered.length === 0) {
2489
+ info("(none enabled)");
2490
+ } else {
2491
+ table(
2492
+ ["Channel", "Name", "Tier", "Status", "E2E"],
2493
+ filtered.map((c) => [
2494
+ c.id,
2495
+ c.name,
2496
+ c.tier,
2497
+ c.status,
2498
+ c.e2e
2499
+ ])
2500
+ );
2501
+ }
2502
+ if (tools) {
2503
+ console.log(chalk15.bold("\nTools:"));
2504
+ if (tools.tools.length === 0) {
2505
+ info("(none configured)");
2506
+ } else {
2507
+ table(
2508
+ ["ID", "Name", "Type", "Access", "Enforcement"],
2509
+ tools.tools.map((t) => [t.id, t.name, t.type, t.access, t.enforcement])
2510
+ );
2511
+ }
2512
+ if (tools.global_controls) {
2513
+ const gc = tools.global_controls;
2514
+ console.log(chalk15.bold("\nGlobal Controls:"));
2515
+ info(`Network: ${gc.default_network_policy === "deny" ? "deny-by-default" : "allow-by-default"}`);
2516
+ info(`Timeout: ${gc.default_timeout_ms}ms`);
2517
+ info(`Rate: ${gc.default_rate_limit_rpm} rpm`);
2518
+ info(`Retries: ${gc.default_retries}`);
2519
+ }
2520
+ }
2521
+ console.log();
2522
+ }
2523
+ function formatTimestamp(iso) {
2524
+ const d = new Date(iso);
2525
+ const pad = (n) => String(n).padStart(2, "0");
2526
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
2527
+ }
2528
+
2529
+ // src/commands/kanban.ts
2530
+ import chalk16 from "chalk";
2531
+ import ora12 from "ora";
2532
+ async function resolveAgentId(codeName) {
2533
+ try {
2534
+ const data = await api.get(`/agents/${codeName}`);
2535
+ return data.agent_id;
2536
+ } catch {
2537
+ return null;
2538
+ }
2539
+ }
2540
+ function priorityLabel(p) {
2541
+ return p === 1 ? chalk16.red("HIGH") : p === 3 ? chalk16.dim("LOW") : chalk16.yellow("MED");
2542
+ }
2543
+ function statusLabel(s) {
2544
+ const map = {
2545
+ in_progress: chalk16.blue("In Progress"),
2546
+ today: chalk16.green("Today"),
2547
+ backlog: chalk16.dim("Backlog"),
2548
+ done: chalk16.gray("Done")
2549
+ };
2550
+ return map[s] ?? s;
2551
+ }
2552
+ async function kanbanListCommand(opts) {
2553
+ const teamSlug = requireTeam();
2554
+ if (!teamSlug) return;
2555
+ const json = isJsonMode();
2556
+ const spinner = ora12({ text: "Fetching kanban board\u2026", isSilent: json });
2557
+ spinner.start();
2558
+ const agentId = await resolveAgentId(opts.agent);
2559
+ if (!agentId) {
2560
+ spinner.fail(`Agent "${opts.agent}" not found.`);
2561
+ process.exitCode = 1;
2562
+ return;
2563
+ }
2564
+ try {
2565
+ const data = await api.post("/host/my-kanban", {
2566
+ agent_id: agentId
2567
+ });
2568
+ spinner.stop();
2569
+ if (json) {
2570
+ jsonOutput({ ok: true, items: data.items });
2571
+ return;
2572
+ }
2573
+ if (!data.items.length) {
2574
+ info(`Board for ${opts.agent} is empty.`);
2575
+ return;
2576
+ }
2577
+ const rows = data.items.map((item) => [
2578
+ priorityLabel(item.priority),
2579
+ statusLabel(item.status),
2580
+ item.title.length > 50 ? item.title.slice(0, 47) + "\u2026" : item.title,
2581
+ item.estimated_minutes ? `~${item.estimated_minutes}min` : "",
2582
+ item.id.slice(0, 8)
2583
+ ]);
2584
+ console.log(chalk16.bold(`
2585
+ Kanban: ${opts.agent}
2586
+ `));
2587
+ table(["Pri", "Status", "Title", "Est", "ID"], rows);
2588
+ } catch (err) {
2589
+ spinner.fail("Failed to fetch board.");
2590
+ if (json) {
2591
+ jsonOutput({ ok: false, error: err.message });
2592
+ } else {
2593
+ error(err.message);
2594
+ }
2595
+ process.exitCode = 1;
2596
+ }
2597
+ }
2598
+ async function kanbanAddCommand(title, opts) {
2599
+ const teamSlug = requireTeam();
2600
+ if (!teamSlug) return;
2601
+ const json = isJsonMode();
2602
+ const agentId = await resolveAgentId(opts.agent);
2603
+ if (!agentId) {
2604
+ error(`Agent "${opts.agent}" not found.`);
2605
+ process.exitCode = 1;
2606
+ return;
2607
+ }
2608
+ try {
2609
+ const data = await api.post("/host/kanban", {
2610
+ agent_id: agentId,
2611
+ add: [
2612
+ {
2613
+ title,
2614
+ description: opts.description,
2615
+ priority: opts.priority ? Number(opts.priority) : 2,
2616
+ status: opts.status ?? "today",
2617
+ estimated_minutes: opts.estimate ? Number(opts.estimate) : void 0,
2618
+ deliverable: opts.deliverable,
2619
+ source: "manual"
2620
+ }
2621
+ ]
2622
+ });
2623
+ if (json) {
2624
+ jsonOutput({ ok: true, added: data.added });
2625
+ } else {
2626
+ success(`Added "${title}" to ${opts.agent}'s board.`);
2627
+ }
2628
+ } catch (err) {
2629
+ if (json) {
2630
+ jsonOutput({ ok: false, error: err.message });
2631
+ } else {
2632
+ error(err.message);
2633
+ }
2634
+ process.exitCode = 1;
2635
+ }
2636
+ }
2637
+ async function kanbanMoveCommand(titleOrId, status, opts) {
2638
+ const teamSlug = requireTeam();
2639
+ if (!teamSlug) return;
2640
+ const json = isJsonMode();
2641
+ const validStatuses = ["backlog", "today", "in_progress", "done"];
2642
+ if (!validStatuses.includes(status)) {
2643
+ error(`Invalid status "${status}". Must be one of: ${validStatuses.join(", ")}`);
2644
+ process.exitCode = 1;
2645
+ return;
2646
+ }
2647
+ const agentId = await resolveAgentId(opts.agent);
2648
+ if (!agentId) {
2649
+ error(`Agent "${opts.agent}" not found.`);
2650
+ process.exitCode = 1;
2651
+ return;
2652
+ }
2653
+ const isUuid = /^[0-9a-f]{8}-/.test(titleOrId);
2654
+ const update = {
2655
+ status,
2656
+ notes: opts.notes
2657
+ };
2658
+ if (isUuid) {
2659
+ update.id = titleOrId;
2660
+ } else {
2661
+ update.title = titleOrId;
2662
+ }
2663
+ try {
2664
+ const data = await api.post("/host/kanban", {
2665
+ agent_id: agentId,
2666
+ update: [update]
2667
+ });
2668
+ if (json) {
2669
+ jsonOutput({ ok: true, updated: data.updated });
2670
+ } else if (data.updated > 0) {
2671
+ success(`Moved "${titleOrId}" \u2192 ${status}.`);
2672
+ } else {
2673
+ error(`Item "${titleOrId}" not found.`);
2674
+ process.exitCode = 1;
2675
+ }
2676
+ } catch (err) {
2677
+ if (json) {
2678
+ jsonOutput({ ok: false, error: err.message });
2679
+ } else {
2680
+ error(err.message);
2681
+ }
2682
+ process.exitCode = 1;
2683
+ }
2684
+ }
2685
+ async function kanbanUpdateCommand(titleOrId, opts) {
2686
+ const teamSlug = requireTeam();
2687
+ if (!teamSlug) return;
2688
+ const json = isJsonMode();
2689
+ const agentId = await resolveAgentId(opts.agent);
2690
+ if (!agentId) {
2691
+ error(`Agent "${opts.agent}" not found.`);
2692
+ process.exitCode = 1;
2693
+ return;
2694
+ }
2695
+ let board;
2696
+ try {
2697
+ board = await api.post("/host/my-kanban", {
2698
+ agent_id: agentId
2699
+ });
2700
+ } catch (err) {
2701
+ if (json) {
2702
+ jsonOutput({ ok: false, error: err.message });
2703
+ } else {
2704
+ error(err.message);
2705
+ }
2706
+ process.exitCode = 1;
2707
+ return;
2708
+ }
2709
+ const isUuid = /^[0-9a-f]{8}-/.test(titleOrId);
2710
+ const match = board.items.find(
2711
+ (i) => isUuid && i.id === titleOrId || i.title === titleOrId
2712
+ );
2713
+ if (!match) {
2714
+ if (json) {
2715
+ jsonOutput({ ok: false, error: `Item "${titleOrId}" not found.` });
2716
+ } else {
2717
+ error(`Item "${titleOrId}" not found.`);
2718
+ }
2719
+ process.exitCode = 1;
2720
+ return;
2721
+ }
2722
+ try {
2723
+ const data = await api.post("/host/kanban", {
2724
+ agent_id: agentId,
2725
+ update: [
2726
+ {
2727
+ id: match.id,
2728
+ status: match.status,
2729
+ notes: opts.notes,
2730
+ result: opts.result
2731
+ }
2732
+ ]
2733
+ });
2734
+ if (json) {
2735
+ jsonOutput({ ok: true, updated: data.updated });
2736
+ } else {
2737
+ success(`Updated "${match.title}".`);
2738
+ }
2739
+ } catch (err) {
2740
+ if (json) {
2741
+ jsonOutput({ ok: false, error: err.message });
2742
+ } else {
2743
+ error(err.message);
2744
+ }
2745
+ process.exitCode = 1;
2746
+ }
2747
+ }
2748
+ async function kanbanDoneCommand(titleOrId, opts) {
2749
+ const teamSlug = requireTeam();
2750
+ if (!teamSlug) return;
2751
+ const json = isJsonMode();
2752
+ const agentId = await resolveAgentId(opts.agent);
2753
+ if (!agentId) {
2754
+ error(`Agent "${opts.agent}" not found.`);
2755
+ process.exitCode = 1;
2756
+ return;
2757
+ }
2758
+ const isUuid = /^[0-9a-f]{8}-/.test(titleOrId);
2759
+ const update = {
2760
+ status: "done",
2761
+ result: opts.result,
2762
+ notes: opts.notes
2763
+ };
2764
+ if (isUuid) {
2765
+ update.id = titleOrId;
2766
+ } else {
2767
+ update.title = titleOrId;
2768
+ }
2769
+ try {
2770
+ const data = await api.post("/host/kanban", {
2771
+ agent_id: agentId,
2772
+ update: [update]
2773
+ });
2774
+ if (json) {
2775
+ jsonOutput({ ok: true, updated: data.updated });
2776
+ } else if (data.updated > 0) {
2777
+ success(`Marked "${titleOrId}" as done.`);
2778
+ } else {
2779
+ error(`Item "${titleOrId}" not found.`);
2780
+ process.exitCode = 1;
2781
+ }
2782
+ } catch (err) {
2783
+ if (json) {
2784
+ jsonOutput({ ok: false, error: err.message });
2785
+ } else {
2786
+ error(err.message);
2787
+ }
2788
+ process.exitCode = 1;
2789
+ }
2790
+ }
2791
+
2792
+ // ../../packages/core/dist/acp/agent-registry.js
2793
+ var ACP_AGENT_REGISTRY = [
2794
+ {
2795
+ name: "claude",
2796
+ label: "Claude Code",
2797
+ adapter_package: "claude-agent-acp",
2798
+ command: "npx",
2799
+ args: ["-y", "claude-agent-acp"],
2800
+ type: "npx-wrapper"
2801
+ },
2802
+ {
2803
+ name: "openclaw",
2804
+ label: "OpenClaw",
2805
+ command: "openclaw",
2806
+ args: ["acp"],
2807
+ type: "native"
2808
+ },
2809
+ {
2810
+ name: "codex",
2811
+ label: "Codex",
2812
+ adapter_package: "codex-acp",
2813
+ command: "npx",
2814
+ args: ["-y", "codex-acp"],
2815
+ type: "npx-wrapper"
2816
+ }
2817
+ ];
2818
+ var agentMap = new Map(ACP_AGENT_REGISTRY.map((a) => [a.name, a]));
2819
+ function getAcpAgent(name) {
2820
+ return agentMap.get(name);
2821
+ }
2822
+
2823
+ // ../../packages/core/dist/acp/client.js
2824
+ import { spawn } from "child_process";
2825
+ function resolveAgentCommand(agentName) {
2826
+ const adapter = getAcpAgent(agentName);
2827
+ if (adapter) {
2828
+ return {
2829
+ command: "acpx",
2830
+ args: [adapter.name]
2831
+ };
2832
+ }
2833
+ return {
2834
+ command: "acpx",
2835
+ args: ["--agent", agentName]
2836
+ };
2837
+ }
2838
+ async function runAcpx(args, options = {}) {
2839
+ return new Promise((resolve2) => {
2840
+ const child = spawn("acpx", args, {
2841
+ cwd: options.cwd,
2842
+ stdio: ["pipe", "pipe", "pipe"],
2843
+ env: { ...process.env }
2844
+ });
2845
+ let stdout = "";
2846
+ let stderr = "";
2847
+ const events = [];
2848
+ child.stdout?.on("data", (data) => {
2849
+ const chunk = data.toString();
2850
+ stdout += chunk;
2851
+ for (const line of chunk.split("\n").filter(Boolean)) {
2852
+ try {
2853
+ const parsed = JSON.parse(line);
2854
+ if (parsed.eventVersion === 1) {
2855
+ events.push(parsed);
2856
+ }
2857
+ } catch {
2858
+ }
2859
+ }
2860
+ });
2861
+ child.stderr?.on("data", (data) => {
2862
+ stderr += data.toString();
2863
+ });
2864
+ const timer = options.timeout ? setTimeout(() => child.kill("SIGTERM"), options.timeout * 1e3) : void 0;
2865
+ child.on("close", (code) => {
2866
+ if (timer)
2867
+ clearTimeout(timer);
2868
+ resolve2({ exitCode: code ?? 1, stdout, stderr, events });
2869
+ });
2870
+ });
2871
+ }
2872
+ async function acpSpawnSession(opts) {
2873
+ const { command: _cmd, args: agentArgs } = resolveAgentCommand(opts.agent);
2874
+ const args = [...agentArgs, "sessions", "ensure"];
2875
+ if (opts.sessionName)
2876
+ args.push("-s", opts.sessionName);
2877
+ if (opts.approveAll)
2878
+ args.unshift("--approve-all");
2879
+ if (opts.ttl !== void 0)
2880
+ args.unshift("--ttl", String(opts.ttl));
2881
+ args.unshift("--format", "json");
2882
+ return runAcpx(args, { cwd: opts.cwd });
2883
+ }
2884
+ async function acpPrompt(opts) {
2885
+ const { command: _cmd, args: agentArgs } = resolveAgentCommand(opts.agent);
2886
+ const args = [...agentArgs, "prompt", opts.prompt];
2887
+ if (opts.sessionName)
2888
+ args.push("-s", opts.sessionName);
2889
+ if (opts.noWait)
2890
+ args.push("--no-wait");
2891
+ if (opts.format)
2892
+ args.unshift("--format", opts.format);
2893
+ if (opts.timeout)
2894
+ args.unshift("--timeout", String(opts.timeout));
2895
+ return runAcpx(args, { cwd: opts.cwd, timeout: opts.timeout });
2896
+ }
2897
+ async function acpExec(agent2, prompt, options = {}) {
2898
+ const { command: _cmd, args: agentArgs } = resolveAgentCommand(agent2);
2899
+ const args = [...agentArgs, "exec", prompt];
2900
+ if (options.format)
2901
+ args.unshift("--format", options.format);
2902
+ if (options.timeout)
2903
+ args.unshift("--timeout", String(options.timeout));
2904
+ return runAcpx(args, { cwd: options.cwd, timeout: options.timeout });
2905
+ }
2906
+ async function acpListSessions(agent2, options = {}) {
2907
+ const { command: _cmd, args: agentArgs } = resolveAgentCommand(agent2);
2908
+ const args = [...agentArgs, "sessions", "list", "--format", "json"];
2909
+ return runAcpx(args, { cwd: options.cwd });
2910
+ }
2911
+ async function acpCancel(agent2, options = {}) {
2912
+ const { command: _cmd, args: agentArgs } = resolveAgentCommand(agent2);
2913
+ const args = [...agentArgs, "cancel"];
2914
+ if (options.sessionName)
2915
+ args.push("-s", options.sessionName);
2916
+ return runAcpx(args, { cwd: options.cwd });
2917
+ }
2918
+ async function acpCloseSession(agent2, options = {}) {
2919
+ const { command: _cmd, args: agentArgs } = resolveAgentCommand(agent2);
2920
+ const args = [...agentArgs, "sessions", "close"];
2921
+ if (options.sessionName)
2922
+ args.push(options.sessionName);
2923
+ return runAcpx(args, { cwd: options.cwd });
2924
+ }
2925
+
2926
+ // src/commands/acpx.ts
2927
+ async function acpxSpawnCommand(agent2, _opts, cmd) {
2928
+ const parentOpts = cmd.parent.opts();
2929
+ const cwd = parentOpts.cwd ?? process.cwd();
2930
+ const sessionName = parentOpts.name;
2931
+ const approveAll = Boolean(parentOpts.approveAll);
2932
+ const ttl = parentOpts.ttl !== void 0 ? Number(parentOpts.ttl) : void 0;
2933
+ info(`Spawning ACP session for "${agent2}"...`);
2934
+ const result = await acpSpawnSession({
2935
+ agent: agent2,
2936
+ cwd,
2937
+ sessionName,
2938
+ approveAll,
2939
+ ttl
2940
+ });
2941
+ if (result.exitCode !== 0) {
2942
+ error(`Failed to spawn session: ${result.stderr || "unknown error"}`);
2943
+ process.exitCode = 1;
2944
+ return;
2945
+ }
2946
+ if (isJsonMode()) {
2947
+ process.stdout.write(result.stdout);
2948
+ } else {
2949
+ success(`ACP session spawned for "${agent2}"`);
2950
+ if (sessionName) info(`Session name: ${sessionName}`);
2951
+ info(`Working directory: ${cwd}`);
2952
+ }
2953
+ }
2954
+ async function acpxPromptCommand(agent2, prompt, _opts, cmd) {
2955
+ const parentOpts = cmd.parent.opts();
2956
+ const cwd = parentOpts.cwd ?? process.cwd();
2957
+ const sessionName = parentOpts.name;
2958
+ const format = parentOpts.format ?? "text";
2959
+ const noWait = Boolean(parentOpts.noWait);
2960
+ const timeout = parentOpts.timeout !== void 0 ? Number(parentOpts.timeout) : void 0;
2961
+ if (!isJsonMode()) info(`Sending prompt to "${agent2}"...`);
2962
+ const result = await acpPrompt({
2963
+ agent: agent2,
2964
+ prompt,
2965
+ cwd,
2966
+ sessionName,
2967
+ format,
2968
+ noWait,
2969
+ timeout
2970
+ });
2971
+ process.stdout.write(result.stdout);
2972
+ if (result.stderr) process.stderr.write(result.stderr);
2973
+ process.exitCode = result.exitCode;
2974
+ }
2975
+ async function acpxExecCommand(agent2, prompt, _opts, cmd) {
2976
+ const parentOpts = cmd.parent.opts();
2977
+ const cwd = parentOpts.cwd ?? process.cwd();
2978
+ const format = parentOpts.format ?? "text";
2979
+ const timeout = parentOpts.timeout !== void 0 ? Number(parentOpts.timeout) : void 0;
2980
+ if (!isJsonMode()) info(`Running one-shot exec with "${agent2}"...`);
2981
+ const result = await acpExec(agent2, prompt, { cwd, format, timeout });
2982
+ process.stdout.write(result.stdout);
2983
+ if (result.stderr) process.stderr.write(result.stderr);
2984
+ process.exitCode = result.exitCode;
2985
+ }
2986
+ async function acpxListSessionsCommand(agent2, _opts, cmd) {
2987
+ const parentOpts = cmd.parent.opts();
2988
+ const cwd = parentOpts.cwd ?? process.cwd();
2989
+ const result = await acpListSessions(agent2, { cwd });
2990
+ if (isJsonMode()) {
2991
+ process.stdout.write(result.stdout);
2992
+ } else {
2993
+ if (result.exitCode !== 0) {
2994
+ error(`Failed to list sessions: ${result.stderr || "unknown error"}`);
2995
+ process.exitCode = 1;
2996
+ return;
2997
+ }
2998
+ try {
2999
+ const sessions = JSON.parse(result.stdout);
3000
+ if (Array.isArray(sessions) && sessions.length > 0) {
3001
+ info(`Active sessions for "${agent2}":`);
3002
+ process.stdout.write(result.stdout);
3003
+ } else {
3004
+ info(`No active sessions for "${agent2}".`);
3005
+ }
3006
+ } catch {
3007
+ process.stdout.write(result.stdout);
3008
+ }
3009
+ }
3010
+ }
3011
+ async function acpxCancelCommand(agent2, _opts, cmd) {
3012
+ const parentOpts = cmd.parent.opts();
3013
+ const cwd = parentOpts.cwd ?? process.cwd();
3014
+ const sessionName = parentOpts.name;
3015
+ info(`Sending cancel to "${agent2}"...`);
3016
+ const result = await acpCancel(agent2, { cwd, sessionName });
3017
+ if (result.exitCode !== 0) {
3018
+ error(`Cancel failed: ${result.stderr || "unknown error"}`);
3019
+ process.exitCode = 1;
3020
+ return;
3021
+ }
3022
+ success("Cancel sent (cooperative).");
3023
+ }
3024
+ async function acpxCloseCommand(agent2, _opts, cmd) {
3025
+ const parentOpts = cmd.parent.opts();
3026
+ const cwd = parentOpts.cwd ?? process.cwd();
3027
+ const sessionName = parentOpts.name;
3028
+ info(`Closing ACP session for "${agent2}"...`);
3029
+ const result = await acpCloseSession(agent2, { cwd, sessionName });
3030
+ if (result.exitCode !== 0) {
3031
+ error(`Close failed: ${result.stderr || "unknown error"}`);
3032
+ process.exitCode = 1;
3033
+ return;
3034
+ }
3035
+ success("Session closed (soft-close \u2014 history retained).");
3036
+ }
3037
+
3038
+ // src/commands/update.ts
3039
+ import { execSync } from "child_process";
3040
+ import chalk17 from "chalk";
3041
+ import ora13 from "ora";
3042
+ var cliVersion = true ? "0.6.6" : "dev";
3043
+ async function fetchLatestVersion() {
3044
+ const host2 = AGT_HOST;
3045
+ if (!host2) return null;
3046
+ try {
3047
+ const controller = new AbortController();
3048
+ const timeout = setTimeout(() => controller.abort(), 5e3);
3049
+ timeout.unref();
3050
+ const res = await fetch(`${host2}/cli/version`, {
3051
+ signal: controller.signal
3052
+ });
3053
+ clearTimeout(timeout);
3054
+ if (!res.ok) return null;
3055
+ return await res.json();
3056
+ } catch {
3057
+ return null;
3058
+ }
3059
+ }
3060
+ function isNewerVersion(local, remote) {
3061
+ if (local === "dev") return false;
3062
+ const parse = (v) => v.replace(/^v/, "").split(".").map(Number);
3063
+ const lParts = parse(local);
3064
+ const rParts = parse(remote);
3065
+ const lMaj = lParts[0] ?? 0, lMin = lParts[1] ?? 0, lPat = lParts[2] ?? 0;
3066
+ const rMaj = rParts[0] ?? 0, rMin = rParts[1] ?? 0, rPat = rParts[2] ?? 0;
3067
+ if (rMaj !== lMaj) return rMaj > lMaj;
3068
+ if (rMin !== lMin) return rMin > lMin;
3069
+ return rPat > lPat;
3070
+ }
3071
+ function performUpdate(version) {
3072
+ execSync(`npm install -g @integrity-labs/agt-cli@${version} --registry=https://npm.pkg.github.com`, {
3073
+ stdio: "inherit"
3074
+ });
3075
+ }
3076
+ async function updateCommand() {
3077
+ const json = isJsonMode();
3078
+ const spinner = ora13({ text: "Checking for updates\u2026", isSilent: json });
3079
+ spinner.start();
3080
+ const versionInfo = await fetchLatestVersion();
3081
+ if (!versionInfo) {
3082
+ spinner.stop();
3083
+ if (json) {
3084
+ jsonOutput({ ok: false, error: "Could not reach API to check for updates" });
3085
+ } else {
3086
+ warn("Could not reach the API to check for updates. Check AGT_HOST is set and the API is reachable.");
3087
+ }
3088
+ process.exitCode = 1;
3089
+ return;
3090
+ }
3091
+ const updateAvailable = isNewerVersion(cliVersion, versionInfo.latest);
3092
+ if (!updateAvailable) {
3093
+ spinner.stop();
3094
+ if (json) {
3095
+ jsonOutput({ ok: true, current: cliVersion, latest: versionInfo.latest, update_available: false });
3096
+ } else {
3097
+ success(`You're on the latest version (${chalk17.bold(cliVersion)})`);
3098
+ }
3099
+ return;
3100
+ }
3101
+ spinner.stop();
3102
+ if (!json) {
3103
+ info(`Update available: ${chalk17.dim(cliVersion)} \u2192 ${chalk17.bold.green(versionInfo.latest)}`);
3104
+ if (versionInfo.changelog_url) {
3105
+ info(`Changelog: ${versionInfo.changelog_url}`);
3106
+ }
3107
+ }
3108
+ const updateSpinner = ora13({ text: "Installing update\u2026", isSilent: json });
3109
+ updateSpinner.start();
3110
+ try {
3111
+ performUpdate(versionInfo.latest);
3112
+ updateSpinner.stop();
3113
+ if (json) {
3114
+ jsonOutput({
3115
+ ok: true,
3116
+ current: cliVersion,
3117
+ latest: versionInfo.latest,
3118
+ update_available: true,
3119
+ updated: true
3120
+ });
3121
+ } else {
3122
+ success(`Updated to ${chalk17.bold(versionInfo.latest)}`);
3123
+ }
3124
+ } catch (err) {
3125
+ updateSpinner.fail("Update failed");
3126
+ if (json) {
3127
+ jsonOutput({
3128
+ ok: false,
3129
+ current: cliVersion,
3130
+ latest: versionInfo.latest,
3131
+ update_available: true,
3132
+ updated: false,
3133
+ error: err.message
3134
+ });
3135
+ } else {
3136
+ error(`Failed to install update: ${err.message}`);
3137
+ info(`You can manually update with: npm install -g @integrity-labs/agt-cli@${versionInfo.latest} --registry=https://npm.pkg.github.com`);
3138
+ }
3139
+ process.exitCode = 1;
3140
+ }
3141
+ }
3142
+ async function checkForUpdateOnStartup() {
3143
+ try {
3144
+ const versionInfo = await fetchLatestVersion();
3145
+ if (!versionInfo) return;
3146
+ if (isNewerVersion(cliVersion, versionInfo.latest)) {
3147
+ console.error(
3148
+ chalk17.yellow(`
3149
+ Update available: ${cliVersion} \u2192 ${versionInfo.latest}. Run ${chalk17.bold("agt update")} to install.
3150
+ `)
3151
+ );
3152
+ }
3153
+ } catch {
3154
+ }
3155
+ }
3156
+
3157
+ // src/bin/agt.ts
3158
+ var cliVersion2 = true ? "0.6.6" : "dev";
3159
+ var program = new Command();
3160
+ program.name("agt").description("Augmented CLI \u2014 agent provisioning and management").version(cliVersion2).option("--json", "Emit machine-readable JSON output (suppress spinners and colors)").option("--skip-update-check", "Skip the automatic update check on startup");
3161
+ program.hook("preAction", (thisCommand) => {
3162
+ const root = thisCommand.optsWithGlobals();
3163
+ if (root.json) {
3164
+ setJsonMode(true);
3165
+ }
3166
+ });
3167
+ program.command("whoami").description("Show the authenticated host, team, and user from AGT_API_KEY").action(whoamiCommand);
3168
+ var team = program.command("team").description("Manage teams");
3169
+ team.command("list").description("List teams you belong to").action(teamListCommand);
3170
+ team.command("create <name>").description("Create a new team and set it as active").action(teamCreateCommand);
3171
+ team.command("switch <slug>").description("Switch the active team").action(teamSwitchCommand);
3172
+ program.command("init").description("Create a new agent (interactive wizard, or non-interactive with --name)").option("--name <display-name>", "Agent display name (triggers non-interactive mode)").option("--code-name <code-name>", "Agent code name (kebab-case, derived from --name if omitted)").option("--description <text>", "Short agent description").option("--env <environment>", "Environment: dev | stage | prod", "dev").option("--risk-tier <tier>", "Risk tier: Low | Medium | High", "Low").option("--budget-type <type>", "Budget type: tokens | dollars | both", "tokens").option("--budget-tokens <number>", "Token budget limit", "10000").option("--budget-dollars <number>", "Dollar budget limit", "10").option("--budget-window <window>", "Budget window: daily | weekly | monthly", "daily").option("--budget-enforcement <mode>", "Budget enforcement: block | throttle | alert | degrade", "block").option("--channels <list>", "Comma-separated channel IDs").option("--logging <mode>", "Logging mode: redacted | hash-only | full-local", "redacted").action(initCommand);
3173
+ program.command("lint").argument("[path]", "Path to agent directory (default: all agents in .augmented/)").description("Lint CHARTER.md and TOOLS.md files").action(lintCommand);
3174
+ var channels = program.command("channels").description("Manage and inspect channel configuration");
3175
+ channels.command("list").description("Print the full channel registry with security metadata").action(channelsListCommand);
3176
+ channels.command("check <agent>").description("Resolve the effective channel list for an agent").action(channelsCheckCommand);
3177
+ var slack = channels.command("slack").description("Manage Slack channel configuration");
3178
+ slack.command("setup <agent-code-name>").description("Configure Slack bot scopes, generate manifest, and store credentials").option("--preset <preset>", "Scope preset: minimal | standard | full").option("--scopes <scopes>", "Comma-separated list of Slack bot scopes").option("--skip-create", "Skip Slack CLI app creation step").option("--bot-token <token>", "Slack bot token (xoxb-\u2026)").option("--signing-secret <secret>", "Slack signing secret").option("--config-token <token>", "Slack app configuration token (xoxe-\u2026) for API-based app creation").action(channelSlackSetupCommand);
3179
+ slack.command("status <agent-code-name>").description("Show current Slack configuration for an agent").action(channelSlackStatusCommand);
3180
+ slack.command("remove <agent-code-name>").description("Remove Slack bot configuration for an agent").action(channelSlackRemoveCommand);
3181
+ var beam = channels.command("beam").description("Manage Beam Protocol channel configuration");
3182
+ beam.command("setup <agent-code-name>").description("Generate Beam identity, register in directory, and configure channel").option("--directory-url <url>", "Beam directory URL (default: hosted)").option("--skip-register", "Skip directory registration").option("--auto-publish", "Auto-publish capabilities from TOOLS.md", true).action(channelBeamSetupCommand);
3183
+ beam.command("status <agent-code-name>").description("Show Beam configuration and directory status").action(channelBeamStatusCommand);
3184
+ beam.command("remove <agent-code-name>").description("Remove Beam identity for an agent").action(channelBeamRemoveCommand);
3185
+ program.command("provision <code-name>").description("Provision an agent: build OpenClaw config, generate files, store snapshot").option("--target <target>", "Deployment target", "local_docker").option("--output <dir>", "Output directory for generated files").option("--dry-run", "Print what would be generated without writing files").action(provisionCommand);
3186
+ program.command("deploy").description("Generate deployment config from a template").option("--template <id>", "Template ID (triggers non-interactive mode)").option("--port <number>", "Base port for gateway", "9000").action(deployCommand);
3187
+ var drift = program.command("drift").description("Detect configuration drift from provisioned state");
3188
+ drift.command("check <code-name>").description("Compare live state against provisioned snapshot").option("--json", "Output as JSON").option("--audit", "Run openclaw security audit").option("--config <path>", "Path to openclaw.json").action(driftCheckCommand);
3189
+ drift.command("watch <code-name>").description("Continuously monitor for drift").option("--interval <seconds>", "Check interval in seconds", "60").option("--webhook <url>", "POST webhook URL on new findings").option("--json", "Output as JSON").option("--config <path>", "Path to openclaw.json").action(driftWatchCommand);
3190
+ var host = program.command("host").description("Manage hosts (OpenClaw servers/gateways) and their API keys");
3191
+ host.command("list").description("List hosts in the active team").action(hostListCommand);
3192
+ host.command("assign <host-name> <agent-code-names...>").description("Assign agents to a host").option("--force", "Reassign agents already assigned to another host").action(hostAssignCommand);
3193
+ host.command("unassign <host-name> <agent-code-names...>").description("Unassign agents from a host").action(hostUnassignCommand);
3194
+ host.command("agents [host-name]").description("List agents assigned to a host (omit name to auto-resolve from AGT_API_KEY)").action(hostAgentsCommand);
3195
+ host.command("rotate-key <host-name>").description("Rotate the API key for a host (revokes the old key)").action(hostRotateKeyCommand);
3196
+ host.command("decommission <host-name>").description("Decommission a host (revokes key, marks inactive)").action(hostDecommissionCommand);
3197
+ var manager = program.command("manager").description("Host config sync daemon \u2014 keeps local agent files in sync with API");
3198
+ manager.command("start").description("Start the manager daemon (polls API for config changes and detects local drift)").option("--interval <seconds>", "Poll interval in seconds (min 5)", "10").option("--config-dir <dir>", "Config directory for agent files", join10(homedir2(), ".augmented")).action(managerStartCommand);
3199
+ manager.command("stop").description("Stop the running manager daemon").action(managerStopCommand);
3200
+ manager.command("status").description("Show the current manager daemon status and discovered agents").action(managerStatusCommand);
3201
+ var agent = program.command("agent").description("Inspect and manage agents");
3202
+ agent.command("show <code-name>").description("Display an agent's provisioned OpenClaw configuration").option("--config-dir <dir>", "Config directory", join10(homedir2(), ".augmented")).option("--all-channels", "Show all channels (including disabled)").action(agentShowCommand);
3203
+ var kanban = program.command("kanban").description("Manage agent kanban boards");
3204
+ kanban.command("list").description("List kanban board items for an agent").requiredOption("--agent <code-name>", "Agent code name").action(kanbanListCommand);
3205
+ kanban.command("add <title>").description("Add a new item to an agent kanban board").requiredOption("--agent <code-name>", "Agent code name").option("--priority <1|2|3>", "Priority: 1=high, 2=medium, 3=low", "2").option("--status <status>", "Initial status: backlog | today | in_progress", "today").option("--description <text>", "Item description").option("--estimate <minutes>", "Estimated time in minutes").option("--deliverable <text>", "Expected output/deliverable").action(kanbanAddCommand);
3206
+ kanban.command("move <title-or-id> <status>").description("Move a kanban item to a different status").requiredOption("--agent <code-name>", "Agent code name").option("--notes <text>", "Progress notes").action(kanbanMoveCommand);
3207
+ kanban.command("update <title-or-id>").description("Update notes or result on a kanban item").requiredOption("--agent <code-name>", "Agent code name").option("--notes <text>", "Progress notes").option("--result <text>", "Result/output produced").action(kanbanUpdateCommand);
3208
+ kanban.command("done <title-or-id>").description("Mark a kanban item as done").requiredOption("--agent <code-name>", "Agent code name").option("--result <text>", "What was produced/delivered").option("--notes <text>", "Completion notes").action(kanbanDoneCommand);
3209
+ var acpx = program.command("acpx").description("Agent Client Protocol (ACP) session management via acpx").option("--cwd <path>", "Working directory for session routing").option("-s, --name <name>", "Named session for parallel workstreams").option("--approve-all", "Auto-approve all agent tool permissions").option("--format <type>", "Output format: text | json | json-strict | quiet", "text").option("--ttl <seconds>", "Queue owner idle TTL in seconds (0 = indefinite)").option("--timeout <seconds>", "Prompt execution timeout").option("--no-wait", "Submit prompt without waiting for completion");
3210
+ acpx.command("spawn <agent>").description("Spawn or ensure an ACP session for an agent (claude, codex, openclaw)").action(acpxSpawnCommand);
3211
+ acpx.command("prompt <agent> <prompt>").description("Send a prompt to an ACP session").action(acpxPromptCommand);
3212
+ acpx.command("exec <agent> <prompt>").description("One-shot execution (no session reuse)").action(acpxExecCommand);
3213
+ acpx.command("list-sessions <agent>").description("List active ACP sessions for an agent").action(acpxListSessionsCommand);
3214
+ acpx.command("cancel <agent>").description("Send cooperative cancel to the running ACP session").action(acpxCancelCommand);
3215
+ acpx.command("close <agent>").description("Soft-close an ACP session (preserves history)").action(acpxCloseCommand);
3216
+ program.command("update").description("Check for and install CLI updates").action(updateCommand);
3217
+ var skipUpdateCheck = process.argv.includes("--skip-update-check") || process.argv.includes("--json") || process.argv[2] === "update";
3218
+ if (!skipUpdateCheck) {
3219
+ checkForUpdateOnStartup().catch(() => {
3220
+ });
3221
+ }
3222
+ program.parse();
3223
+ //# sourceMappingURL=agt.js.map