fastclaw-cli 0.2.2 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -156,7 +156,8 @@ function resolveConfig(opts) {
156
156
  appToken: opts.appToken || process.env.FASTCLAW_APP_TOKEN || file.app_token,
157
157
  userId: opts.userId || process.env.FASTCLAW_USER_ID || file.default_user_id || "default",
158
158
  botId: opts.botId || process.env.FASTCLAW_BOT_ID || file.default_bot_id,
159
- json: opts.json ?? false
159
+ json: opts.json ?? false,
160
+ yes: opts.yes ?? false
160
161
  };
161
162
  }
162
163
  function getConfigPath() {
@@ -165,6 +166,31 @@ function getConfigPath() {
165
166
 
166
167
  // src/utils/error.ts
167
168
  import chalk from "chalk";
169
+
170
+ // src/utils/prompt.ts
171
+ import { input, select, password, confirm } from "@inquirer/prompts";
172
+ async function promptText(message, defaultValue) {
173
+ return input({ message, default: defaultValue });
174
+ }
175
+ async function promptPassword(message) {
176
+ return password({ message, mask: "\u2022" });
177
+ }
178
+ async function promptSelect(message, choices) {
179
+ return select({ message, choices });
180
+ }
181
+ async function promptConfirm(message, defaultValue = true) {
182
+ return confirm({ message, default: defaultValue });
183
+ }
184
+
185
+ // src/utils/error.ts
186
+ async function confirmAction(cfg, message) {
187
+ if (cfg.yes) return;
188
+ const ok = await promptConfirm(message, false);
189
+ if (!ok) {
190
+ console.log(chalk.dim("Cancelled."));
191
+ process.exit(0);
192
+ }
193
+ }
168
194
  var ApiError = class extends Error {
169
195
  constructor(statusCode, apiCode, message) {
170
196
  super(message);
@@ -293,7 +319,7 @@ function maskToken(token) {
293
319
  return token.slice(0, 4) + "\u2022\u2022\u2022\u2022" + token.slice(-4);
294
320
  }
295
321
 
296
- // src/commands/admin/apps.ts
322
+ // src/commands/app/index.ts
297
323
  import chalk4 from "chalk";
298
324
 
299
325
  // src/client.ts
@@ -484,6 +510,58 @@ var FastClawClient = class {
484
510
  async deleteSkill(botId, name) {
485
511
  return this.request("DELETE", `/bots/${botId}/skills/${name}`);
486
512
  }
513
+ // ── Bot Logs ──
514
+ async getBotLogs(botId, opts) {
515
+ const query = {};
516
+ if (opts?.tail !== void 0) query.tail = String(opts.tail);
517
+ if (opts?.sinceSeconds !== void 0) query.sinceSeconds = String(opts.sinceSeconds);
518
+ if (opts?.sinceTime) query.sinceTime = opts.sinceTime;
519
+ if (opts?.timestamps) query.timestamps = "true";
520
+ return this.request("GET", `/bots/${botId}/logs`, { query });
521
+ }
522
+ // ── Bot Runtime (OpenClaw CLI exec) ──
523
+ async getRuntimeStatus(botId, opts) {
524
+ const query = {};
525
+ if (opts?.all) query.all = "true";
526
+ if (opts?.usage) query.usage = "true";
527
+ if (opts?.deep) query.deep = "true";
528
+ if (opts?.timeout) query.timeout = opts.timeout;
529
+ return this.request("GET", `/bots/${botId}/runtime/status`, { query });
530
+ }
531
+ async getRuntimeSessions(botId, opts) {
532
+ const query = {};
533
+ if (opts?.agent) query.agent = opts.agent;
534
+ if (opts?.allAgents) query["all-agents"] = "true";
535
+ if (opts?.active) query.active = "true";
536
+ return this.request("GET", `/bots/${botId}/runtime/sessions`, { query });
537
+ }
538
+ async getRuntimeConfig(botId, path) {
539
+ const query = {};
540
+ if (path) query.path = path;
541
+ return this.request("GET", `/bots/${botId}/runtime/config`, { query });
542
+ }
543
+ async getRuntimeConfigValidate(botId) {
544
+ return this.request("GET", `/bots/${botId}/runtime/config/validate`);
545
+ }
546
+ async getRuntimeModels(botId, opts) {
547
+ const query = {};
548
+ if (opts?.all) query.all = "true";
549
+ if (opts?.agent) query.agent = opts.agent;
550
+ if (opts?.provider) query.provider = opts.provider;
551
+ return this.request("GET", `/bots/${botId}/runtime/models`, { query });
552
+ }
553
+ async getRuntimeSkills(botId, opts) {
554
+ const query = {};
555
+ if (opts?.eligible) query.eligible = "true";
556
+ if (opts?.verbose) query.verbose = "true";
557
+ return this.request("GET", `/bots/${botId}/runtime/skills`, { query });
558
+ }
559
+ async getRuntimeSkillsCheck(botId) {
560
+ return this.request("GET", `/bots/${botId}/runtime/skills/check`);
561
+ }
562
+ async runtimeExec(botId, req) {
563
+ return this.request("POST", `/bots/${botId}/runtime/exec`, { body: req });
564
+ }
487
565
  // ── Utility: wait for bot ready ──
488
566
  async waitForReady(botId, timeoutMs = 12e4, intervalMs = 3e3) {
489
567
  const start = Date.now();
@@ -507,19 +585,20 @@ var FastClawClient = class {
507
585
  }
508
586
  };
509
587
 
510
- // src/commands/admin/apps.ts
511
- function getClient(program2) {
512
- const opts = program2.parent.parent.opts();
588
+ // src/commands/app/index.ts
589
+ function getClient(app) {
590
+ const opts = app.parent.opts();
513
591
  const cfg = resolveConfig(opts);
514
592
  requireOption(cfg.adminToken, "admin-token");
515
593
  return { client: new FastClawClient({ url: cfg.url, adminToken: cfg.adminToken }), cfg };
516
594
  }
517
- function registerAppsCommand(admin) {
518
- const apps = admin.command("apps").description("Manage apps");
519
- apps.command("create").description("Create a new app").requiredOption("-n, --name <name>", "App name").option("--url <url>", "App website URL").option("--description <desc>", "App description").option("--owner-email <email>", "Owner email").option("--bot-domain-template <tpl>", "Bot domain template").action(
595
+ function registerAppCommand(program2) {
596
+ const app = program2.command("app").description("Manage apps (requires admin token)");
597
+ app.command("create").description("Create a new app").requiredOption("-n, --name <name>", "App name").option("--url <url>", "App website URL").option("--description <desc>", "App description").option("--owner-email <email>", "Owner email").option("--bot-domain-template <tpl>", "Bot domain template").action(
520
598
  withErrorHandler(async (cmdOpts) => {
521
- const { client, cfg } = getClient(apps);
522
- const app = await client.createApp({
599
+ const { client, cfg } = getClient(app);
600
+ await confirmAction(cfg, `Create app '${cmdOpts.name}'?`);
601
+ const result = await client.createApp({
523
602
  name: cmdOpts.name,
524
603
  url: cmdOpts.url,
525
604
  description: cmdOpts.description,
@@ -527,21 +606,21 @@ function registerAppsCommand(admin) {
527
606
  bot_domain_template: cmdOpts.botDomainTemplate
528
607
  });
529
608
  if (cfg.json) {
530
- printJson(app);
609
+ printJson(result);
531
610
  } else {
532
- printSuccess(`App created: ${app.id}`);
611
+ printSuccess(`App created: ${result.id}`);
533
612
  printKeyValue({
534
- ID: app.id,
535
- Name: app.name,
536
- Token: app.api_token,
537
- Status: app.status
613
+ ID: result.id,
614
+ Name: result.name,
615
+ Token: result.api_token,
616
+ Status: result.status
538
617
  });
539
618
  }
540
619
  })
541
620
  );
542
- apps.command("list").description("List all apps").option("-t, --show-token", "Show full API token (masked by default)").action(
621
+ app.command("list").description("List all apps").option("-t, --show-token", "Show full API token (masked by default)").action(
543
622
  withErrorHandler(async (cmdOpts) => {
544
- const { client, cfg } = getClient(apps);
623
+ const { client, cfg } = getClient(app);
545
624
  const list = await client.listApps();
546
625
  if (cfg.json) {
547
626
  printJson(list);
@@ -562,9 +641,46 @@ function registerAppsCommand(admin) {
562
641
  }
563
642
  })
564
643
  );
565
- apps.command("delete <id>").description("Delete an app").action(
644
+ app.command("get <id>").description("Get app details").action(
566
645
  withErrorHandler(async (id) => {
567
- const { client, cfg } = getClient(apps);
646
+ const { client, cfg } = getClient(app);
647
+ const result = await client.getApp(id);
648
+ if (cfg.json) {
649
+ printJson(result);
650
+ } else {
651
+ printKeyValue({
652
+ ID: result.id,
653
+ Name: result.name,
654
+ URL: result.url || "(none)",
655
+ Description: result.description || "(none)",
656
+ Status: result.status,
657
+ Created: result.created_at
658
+ });
659
+ }
660
+ })
661
+ );
662
+ app.command("update <id>").description("Update an app").option("-n, --name <name>", "App name").option("--url <url>", "App website URL").option("--description <desc>", "App description").option("--owner-email <email>", "Owner email").option("--status <status>", "App status (active/disabled)").action(
663
+ withErrorHandler(async (id, cmdOpts) => {
664
+ const { client, cfg } = getClient(app);
665
+ await confirmAction(cfg, `Update app ${id}?`);
666
+ const result = await client.updateApp(id, {
667
+ name: cmdOpts.name,
668
+ url: cmdOpts.url,
669
+ description: cmdOpts.description,
670
+ owner_email: cmdOpts.ownerEmail,
671
+ status: cmdOpts.status
672
+ });
673
+ if (cfg.json) {
674
+ printJson(result);
675
+ } else {
676
+ printSuccess(`App ${id} updated`);
677
+ }
678
+ })
679
+ );
680
+ app.command("delete <id>").description("Delete an app").action(
681
+ withErrorHandler(async (id) => {
682
+ const { client, cfg } = getClient(app);
683
+ await confirmAction(cfg, `Delete app ${id}? This cannot be undone.`);
568
684
  const res = await client.deleteApp(id);
569
685
  if (cfg.json) {
570
686
  printJson(res);
@@ -573,9 +689,10 @@ function registerAppsCommand(admin) {
573
689
  }
574
690
  })
575
691
  );
576
- apps.command("rotate-token <id>").description("Reset an app's API token").action(
692
+ app.command("rotate-token <id>").description("Reset an app's API token").action(
577
693
  withErrorHandler(async (id) => {
578
- const { client, cfg } = getClient(apps);
694
+ const { client, cfg } = getClient(app);
695
+ await confirmAction(cfg, `Rotate API token for app ${id}?`);
579
696
  const res = await client.rotateAppToken(id);
580
697
  if (cfg.json) {
581
698
  printJson(res);
@@ -587,31 +704,27 @@ function registerAppsCommand(admin) {
587
704
  );
588
705
  }
589
706
 
590
- // src/commands/admin/index.ts
591
- function registerAdminCommand(program2) {
592
- const admin = program2.command("admin").description("Admin operations");
593
- registerAppsCommand(admin);
594
- }
595
-
596
707
  // src/commands/bot/crud.ts
597
708
  import chalk5 from "chalk";
598
709
 
599
710
  // src/commands/bot/resolve.ts
711
+ function getRootOpts(cmd) {
712
+ let root = cmd;
713
+ while (root.parent) root = root.parent;
714
+ return root.opts();
715
+ }
600
716
  function getClient2(cmd) {
601
- const opts = { ...cmd.parent.opts(), ...cmd.opts() };
602
- const cfg = resolveConfig(opts);
717
+ const cfg = resolveConfig(getRootOpts(cmd));
603
718
  requireOption(cfg.appToken, "app-token");
604
719
  return { client: new FastClawClient({ url: cfg.url, appToken: cfg.appToken }), cfg };
605
720
  }
606
721
  function getAdminClient(cmd) {
607
- const opts = { ...cmd.parent.opts(), ...cmd.opts() };
608
- const cfg = resolveConfig(opts);
722
+ const cfg = resolveConfig(getRootOpts(cmd));
609
723
  requireOption(cfg.adminToken, "admin-token");
610
724
  return { client: new FastClawClient({ url: cfg.url, adminToken: cfg.adminToken }), cfg };
611
725
  }
612
726
  async function resolveClientForBot(cmd, botId) {
613
- const opts = { ...cmd.parent.opts(), ...cmd.opts() };
614
- const cfg = resolveConfig(opts);
727
+ const cfg = resolveConfig(getRootOpts(cmd));
615
728
  if (cfg.appToken) {
616
729
  try {
617
730
  const client = new FastClawClient({ url: cfg.url, appToken: cfg.appToken });
@@ -643,6 +756,7 @@ function registerBotCrudCommands(bot) {
643
756
  bot.command("create").description("Create a new bot").requiredOption("-n, --name <name>", "Bot name").option("--slug <slug>", "URL slug").option("--user-id <userId>", "User ID").option("--expires-at <date>", "Expiration date (ISO-8601)").action(
644
757
  withErrorHandler(async (cmdOpts) => {
645
758
  const { client, cfg } = getClient2(bot);
759
+ await confirmAction(cfg, `Create bot '${cmdOpts.name}'?`);
646
760
  const userId = cmdOpts.userId || cfg.userId;
647
761
  const result = await client.createBot({
648
762
  user_id: userId,
@@ -753,6 +867,7 @@ function registerBotCrudCommands(bot) {
753
867
  bot.command("update <id>").description("Update a bot").option("-n, --name <name>", "New name").option("--slug <slug>", "New slug").option("--expires-at <date>", "New expiration date").action(
754
868
  withErrorHandler(async (id, cmdOpts) => {
755
869
  const { client, cfg } = await resolveClientForBot(bot, id);
870
+ await confirmAction(cfg, `Update bot ${id}?`);
756
871
  const result = await client.updateBot(id, {
757
872
  name: cmdOpts.name,
758
873
  slug: cmdOpts.slug,
@@ -768,6 +883,7 @@ function registerBotCrudCommands(bot) {
768
883
  bot.command("delete <id>").description("Delete a bot").action(
769
884
  withErrorHandler(async (id) => {
770
885
  const { client, cfg } = await resolveClientForBot(bot, id);
886
+ await confirmAction(cfg, `Delete bot ${id}? This cannot be undone.`);
771
887
  const res = await client.deleteBot(id);
772
888
  if (cfg.json) {
773
889
  printJson(res);
@@ -811,6 +927,7 @@ function registerBotLifecycleCommands(bot) {
811
927
  bot.command("stop <id>").description("Stop a bot").action(
812
928
  withErrorHandler(async (id) => {
813
929
  const { client, cfg } = await resolveClientForBot(bot, id);
930
+ await confirmAction(cfg, `Stop bot ${id}?`);
814
931
  const res = await client.stopBot(id);
815
932
  if (cfg.json) {
816
933
  printJson(res);
@@ -822,6 +939,7 @@ function registerBotLifecycleCommands(bot) {
822
939
  bot.command("restart <id>").description("Restart a bot").action(
823
940
  withErrorHandler(async (id) => {
824
941
  const { client, cfg } = await resolveClientForBot(bot, id);
942
+ await confirmAction(cfg, `Restart bot ${id}?`);
825
943
  const res = await client.restartBot(id);
826
944
  if (cfg.json) {
827
945
  printJson(res);
@@ -882,6 +1000,7 @@ function registerBotLifecycleCommands(bot) {
882
1000
  bot.command("reset-token <id>").description("Reset bot access token").action(
883
1001
  withErrorHandler(async (id) => {
884
1002
  const { client, cfg } = await resolveClientForBot(bot, id);
1003
+ await confirmAction(cfg, `Reset access token for bot ${id}?`);
885
1004
  const res = await client.resetBotToken(id);
886
1005
  if (cfg.json) {
887
1006
  printJson(res);
@@ -896,32 +1015,162 @@ function registerBotLifecycleCommands(bot) {
896
1015
  );
897
1016
  }
898
1017
 
899
- // src/commands/bot/index.ts
900
- function registerBotCommand(program2) {
901
- const bot = program2.command("bot").description("Manage bots").option("--user-id <userId>", "User ID for bot operations");
902
- registerBotCrudCommands(bot);
903
- registerBotLifecycleCommands(bot);
1018
+ // src/commands/bot/logs.ts
1019
+ function registerBotLogsCommands(bot) {
1020
+ bot.command("logs <id>").description("Get pod logs for a running bot").option("--tail <lines>", "Number of lines from the end (default: 100)").option("--since <seconds>", "Logs newer than N seconds").option("--since-time <time>", "Logs newer than RFC3339 timestamp").option("--timestamps", "Show timestamps on each line").action(
1021
+ withErrorHandler(async (id, cmdOpts) => {
1022
+ const { client, cfg } = await resolveClientForBot(bot, id);
1023
+ const res = await client.getBotLogs(id, {
1024
+ tail: cmdOpts.tail ? parseInt(cmdOpts.tail, 10) : void 0,
1025
+ sinceSeconds: cmdOpts.since ? parseInt(cmdOpts.since, 10) : void 0,
1026
+ sinceTime: cmdOpts.sinceTime,
1027
+ timestamps: cmdOpts.timestamps
1028
+ });
1029
+ if (cfg.json) {
1030
+ printJson(res);
1031
+ } else {
1032
+ process.stdout.write(res.logs);
1033
+ }
1034
+ })
1035
+ );
904
1036
  }
905
1037
 
906
- // src/commands/model/provider.ts
907
- import chalk7 from "chalk";
908
-
909
- // src/utils/prompt.ts
910
- import { input, select, password, confirm } from "@inquirer/prompts";
911
- async function promptText(message, defaultValue) {
912
- return input({ message, default: defaultValue });
913
- }
914
- async function promptPassword(message) {
915
- return password({ message, mask: "\u2022" });
916
- }
917
- async function promptSelect(message, choices) {
918
- return select({ message, choices });
1038
+ // src/commands/bot/runtime.ts
1039
+ function registerBotRuntimeCommands(bot) {
1040
+ const runtime = bot.command("runtime").description("OpenClaw runtime introspection (requires running bot)");
1041
+ runtime.command("status <id>").description("Get OpenClaw runtime status").option("--all", "Include all status sections").option("--usage", "Include usage info").option("--deep", "Deep connectivity check").option("--timeout <ms>", "Timeout for deep checks").action(
1042
+ withErrorHandler(async (id, cmdOpts) => {
1043
+ const { client } = await resolveClientForBot(bot, id);
1044
+ const res = await client.getRuntimeStatus(id, {
1045
+ all: cmdOpts.all,
1046
+ usage: cmdOpts.usage,
1047
+ deep: cmdOpts.deep,
1048
+ timeout: cmdOpts.timeout
1049
+ });
1050
+ printJson(res);
1051
+ })
1052
+ );
1053
+ runtime.command("sessions <id>").description("List OpenClaw sessions").option("--agent <name>", "Filter by agent").option("--all-agents", "Show sessions from all agents").option("--active", "Show only active sessions").action(
1054
+ withErrorHandler(async (id, cmdOpts) => {
1055
+ const { client, cfg } = await resolveClientForBot(bot, id);
1056
+ const res = await client.getRuntimeSessions(id, {
1057
+ agent: cmdOpts.agent,
1058
+ allAgents: cmdOpts.allAgents,
1059
+ active: cmdOpts.active
1060
+ });
1061
+ if (cfg.json) {
1062
+ printJson(res);
1063
+ } else {
1064
+ const data = res;
1065
+ const sessions = data.sessions ?? [];
1066
+ if (sessions.length === 0) {
1067
+ console.log(" No sessions found");
1068
+ return;
1069
+ }
1070
+ printTable(
1071
+ ["Session ID", "Agent", "Model", "Kind"],
1072
+ sessions.map((s) => [
1073
+ s.sessionId?.slice(0, 8) ?? "?",
1074
+ s.agentId ?? "?",
1075
+ s.model ?? "?",
1076
+ s.kind ?? "?"
1077
+ ])
1078
+ );
1079
+ }
1080
+ })
1081
+ );
1082
+ runtime.command("config <id>").description("Get OpenClaw runtime config").option("--path <path>", "Config path (e.g. gateway, models)").action(
1083
+ withErrorHandler(async (id, cmdOpts) => {
1084
+ const { client } = await resolveClientForBot(bot, id);
1085
+ const res = await client.getRuntimeConfig(id, cmdOpts.path);
1086
+ printJson(res);
1087
+ })
1088
+ );
1089
+ runtime.command("config-validate <id>").description("Validate OpenClaw runtime config").action(
1090
+ withErrorHandler(async (id) => {
1091
+ const { client, cfg } = await resolveClientForBot(bot, id);
1092
+ const res = await client.getRuntimeConfigValidate(id);
1093
+ if (cfg.json) {
1094
+ printJson(res);
1095
+ } else {
1096
+ const data = res;
1097
+ printKeyValue({
1098
+ "Path": data.path ?? "?",
1099
+ "Valid": data.valid ? "yes" : "no"
1100
+ });
1101
+ }
1102
+ })
1103
+ );
1104
+ runtime.command("models <id>").description("Get OpenClaw runtime model status").option("--all", "Show all model info").option("--agent <name>", "Filter by agent").option("--provider <name>", "Filter by provider").action(
1105
+ withErrorHandler(async (id, cmdOpts) => {
1106
+ const { client } = await resolveClientForBot(bot, id);
1107
+ const res = await client.getRuntimeModels(id, {
1108
+ all: cmdOpts.all,
1109
+ agent: cmdOpts.agent,
1110
+ provider: cmdOpts.provider
1111
+ });
1112
+ printJson(res);
1113
+ })
1114
+ );
1115
+ runtime.command("skills <id>").description("List OpenClaw runtime skills").option("--eligible", "Show only eligible skills").option("--verbose", "Show detailed skill info").action(
1116
+ withErrorHandler(async (id, cmdOpts) => {
1117
+ const { client, cfg } = await resolveClientForBot(bot, id);
1118
+ const res = await client.getRuntimeSkills(id, {
1119
+ eligible: cmdOpts.eligible,
1120
+ verbose: cmdOpts.verbose
1121
+ });
1122
+ if (cfg.json) {
1123
+ printJson(res);
1124
+ } else {
1125
+ const data = res;
1126
+ const skills = data.skills ?? [];
1127
+ if (skills.length === 0) {
1128
+ console.log(" No skills found");
1129
+ return;
1130
+ }
1131
+ printTable(
1132
+ ["Name", "Eligible", "Source"],
1133
+ skills.map((s) => [
1134
+ s.name ?? "?",
1135
+ s.eligible ? "yes" : "no",
1136
+ s.source ?? "?"
1137
+ ])
1138
+ );
1139
+ }
1140
+ })
1141
+ );
1142
+ runtime.command("skills-check <id>").description("Check OpenClaw skill requirements").action(
1143
+ withErrorHandler(async (id) => {
1144
+ const { client, cfg } = await resolveClientForBot(bot, id);
1145
+ const res = await client.getRuntimeSkillsCheck(id);
1146
+ if (cfg.json) {
1147
+ printJson(res);
1148
+ } else {
1149
+ const data = res;
1150
+ const eligible = data.eligible ?? [];
1151
+ const blocked = data.blocked ?? [];
1152
+ const disabled = data.disabled ?? [];
1153
+ printKeyValue({
1154
+ "Eligible": eligible.join(", ") || "(none)",
1155
+ "Blocked": blocked.join(", ") || "(none)",
1156
+ "Disabled": disabled.join(", ") || "(none)"
1157
+ });
1158
+ }
1159
+ })
1160
+ );
1161
+ runtime.command("exec <id>").description("Execute arbitrary command in bot pod (e.g. --cmd 'id -un')").requiredOption("-c, --cmd <command>", "The command to execute in the pod").action(
1162
+ withErrorHandler(async (id, cmdOpts) => {
1163
+ const { client } = await resolveClientForBot(bot, id);
1164
+ const res = await client.runtimeExec(id, { command: cmdOpts.cmd });
1165
+ printJson(res);
1166
+ })
1167
+ );
919
1168
  }
920
1169
 
921
1170
  // src/commands/model/provider.ts
922
- function getClient3(model) {
923
- const opts = model.parent.opts();
924
- const cfg = resolveConfig(opts);
1171
+ import chalk7 from "chalk";
1172
+ function getClient3(cmd) {
1173
+ const cfg = resolveConfig(getRootOpts(cmd));
925
1174
  requireOption(cfg.appToken, "app-token");
926
1175
  return { client: new FastClawClient({ url: cfg.url, appToken: cfg.appToken }), cfg };
927
1176
  }
@@ -961,6 +1210,7 @@ function registerModelProviderCommands(model) {
961
1210
  apiKey = apiKey || await promptPassword("API Key:");
962
1211
  provider = { name: providerName, apiKey, models: [] };
963
1212
  }
1213
+ await confirmAction(cfg, `Add provider '${provider.name}' to bot ${botId}?`);
964
1214
  const result = await client.addProvider(botId, provider);
965
1215
  if (cfg.json) {
966
1216
  printJson(result);
@@ -1006,6 +1256,7 @@ function registerModelProviderCommands(model) {
1006
1256
  model.command("delete <bot-id> <provider>").description("Delete a model provider").action(
1007
1257
  withErrorHandler(async (botId, provider) => {
1008
1258
  const { client, cfg } = getClient3(model);
1259
+ await confirmAction(cfg, `Delete provider '${provider}' from bot ${botId}?`);
1009
1260
  await client.deleteProvider(botId, provider);
1010
1261
  if (cfg.json) {
1011
1262
  printJson({ message: "deleted" });
@@ -1035,6 +1286,7 @@ function registerModelProviderCommands(model) {
1035
1286
  const body = {};
1036
1287
  if (cmdOpts.primary) body.primary_model = cmdOpts.primary;
1037
1288
  if (cmdOpts.fallback) body.fallback_model = cmdOpts.fallback;
1289
+ await confirmAction(cfg, `Update default model for bot ${botId}?`);
1038
1290
  const d = await client.setDefaults(botId, body);
1039
1291
  if (cfg.json) {
1040
1292
  printJson(d);
@@ -1057,9 +1309,8 @@ function registerModelCommand(program2) {
1057
1309
 
1058
1310
  // src/commands/channel/crud.ts
1059
1311
  import chalk8 from "chalk";
1060
- function getClient4(parent) {
1061
- const opts = parent.parent.opts();
1062
- const cfg = resolveConfig(opts);
1312
+ function getClient4(cmd) {
1313
+ const cfg = resolveConfig(getRootOpts(cmd));
1063
1314
  requireOption(cfg.appToken, "app-token");
1064
1315
  return { client: new FastClawClient({ url: cfg.url, appToken: cfg.appToken }), cfg };
1065
1316
  }
@@ -1097,6 +1348,7 @@ function registerChannelCrudCommands(channel) {
1097
1348
  { name: "Disabled", value: "disabled" }
1098
1349
  ]);
1099
1350
  }
1351
+ await confirmAction(cfg, `Add channel '${channelType}' to bot ${botId}?`);
1100
1352
  const result = await client.addChannel(botId, req);
1101
1353
  if (cfg.json) {
1102
1354
  printJson(result);
@@ -1127,6 +1379,7 @@ function registerChannelCrudCommands(channel) {
1127
1379
  channel.command("remove <bot-id> <channel>").description("Remove a channel").option("--account <name>", "Specific account to remove").action(
1128
1380
  withErrorHandler(async (botId, ch, cmdOpts) => {
1129
1381
  const { client, cfg } = getClient4(channel);
1382
+ await confirmAction(cfg, `Remove channel '${ch}' from bot ${botId}?`);
1130
1383
  const result = await client.removeChannel(botId, ch, cmdOpts.account);
1131
1384
  if (cfg.json) {
1132
1385
  printJson(result);
@@ -1138,10 +1391,8 @@ function registerChannelCrudCommands(channel) {
1138
1391
  }
1139
1392
 
1140
1393
  // src/commands/channel/pairing.ts
1141
- function getClient5(parent) {
1142
- const root = parent.parent.parent;
1143
- const opts = root.opts();
1144
- const cfg = resolveConfig(opts);
1394
+ function getClient5(cmd) {
1395
+ const cfg = resolveConfig(getRootOpts(cmd));
1145
1396
  requireOption(cfg.appToken, "app-token");
1146
1397
  return { client: new FastClawClient({ url: cfg.url, appToken: cfg.appToken }), cfg };
1147
1398
  }
@@ -1161,6 +1412,7 @@ function registerPairingCommands(channel) {
1161
1412
  pairing.command("approve <bot-id> <channel>").description("Approve a pairing request").requiredOption("--code <code>", "Pairing code").action(
1162
1413
  withErrorHandler(async (botId, ch, cmdOpts) => {
1163
1414
  const { client, cfg } = getClient5(pairing);
1415
+ await confirmAction(cfg, `Approve pairing for channel '${ch}'?`);
1164
1416
  const result = await client.approvePairing(botId, ch, { code: cmdOpts.code });
1165
1417
  if (cfg.json) {
1166
1418
  printJson(result);
@@ -1172,6 +1424,7 @@ function registerPairingCommands(channel) {
1172
1424
  pairing.command("revoke <bot-id> <channel>").description("Revoke a user's pairing").requiredOption("--user-id <userId>", "User ID to revoke").action(
1173
1425
  withErrorHandler(async (botId, ch, cmdOpts) => {
1174
1426
  const { client, cfg } = getClient5(pairing);
1427
+ await confirmAction(cfg, `Revoke pairing for user '${cmdOpts.userId}' on channel '${ch}'?`);
1175
1428
  const result = await client.revokePairing(botId, ch, { user_id: cmdOpts.userId });
1176
1429
  if (cfg.json) {
1177
1430
  printJson(result);
@@ -1202,9 +1455,8 @@ function registerChannelCommand(program2) {
1202
1455
 
1203
1456
  // src/commands/device/crud.ts
1204
1457
  import chalk9 from "chalk";
1205
- function getClient6(device) {
1206
- const opts = device.parent.opts();
1207
- const cfg = resolveConfig(opts);
1458
+ function getClient6(cmd) {
1459
+ const cfg = resolveConfig(getRootOpts(cmd));
1208
1460
  requireOption(cfg.appToken, "app-token");
1209
1461
  return { client: new FastClawClient({ url: cfg.url, appToken: cfg.appToken }), cfg };
1210
1462
  }
@@ -1239,6 +1491,7 @@ function registerDeviceCrudCommands(device) {
1239
1491
  device.command("approve <bot-id> <request-id>").description("Approve a device pairing request").action(
1240
1492
  withErrorHandler(async (botId, requestId) => {
1241
1493
  const { client, cfg } = getClient6(device);
1494
+ await confirmAction(cfg, `Approve device ${requestId}?`);
1242
1495
  const result = await client.approveDevice(botId, requestId);
1243
1496
  if (cfg.json) {
1244
1497
  printJson(result);
@@ -1250,6 +1503,7 @@ function registerDeviceCrudCommands(device) {
1250
1503
  device.command("revoke <bot-id> <device-id>").description("Revoke a paired device").option("--role <role>", "Role to revoke", "operator").action(
1251
1504
  withErrorHandler(async (botId, deviceId, cmdOpts) => {
1252
1505
  const { client, cfg } = getClient6(device);
1506
+ await confirmAction(cfg, `Revoke device ${deviceId}?`);
1253
1507
  const result = await client.revokeDevice(botId, deviceId, cmdOpts.role);
1254
1508
  if (cfg.json) {
1255
1509
  printJson(result);
@@ -1269,9 +1523,8 @@ function registerDeviceCommand(program2) {
1269
1523
  // src/commands/skill/crud.ts
1270
1524
  import { readFileSync as readFileSync2 } from "fs";
1271
1525
  import chalk10 from "chalk";
1272
- function getClient7(skill) {
1273
- const opts = skill.parent.opts();
1274
- const cfg = resolveConfig(opts);
1526
+ function getClient7(cmd) {
1527
+ const cfg = resolveConfig(getRootOpts(cmd));
1275
1528
  requireOption(cfg.appToken, "app-token");
1276
1529
  return { client: new FastClawClient({ url: cfg.url, appToken: cfg.appToken }), cfg };
1277
1530
  }
@@ -1300,6 +1553,7 @@ function registerSkillCrudCommands(skill) {
1300
1553
  } else {
1301
1554
  throw new Error("Either --content or --file is required");
1302
1555
  }
1556
+ await confirmAction(cfg, `Set skill '${name}' on bot ${botId}?`);
1303
1557
  const result = await client.setSkill(botId, name, { content });
1304
1558
  if (cfg.json) {
1305
1559
  printJson(result);
@@ -1311,6 +1565,7 @@ function registerSkillCrudCommands(skill) {
1311
1565
  skill.command("delete <bot-id> <name>").description("Delete a skill").action(
1312
1566
  withErrorHandler(async (botId, name) => {
1313
1567
  const { client, cfg } = getClient7(skill);
1568
+ await confirmAction(cfg, `Delete skill '${name}' from bot ${botId}?`);
1314
1569
  const result = await client.deleteSkill(botId, name);
1315
1570
  if (cfg.json) {
1316
1571
  printJson(result);
@@ -1327,6 +1582,19 @@ function registerSkillCommand(program2) {
1327
1582
  registerSkillCrudCommands(skill);
1328
1583
  }
1329
1584
 
1585
+ // src/commands/bot/index.ts
1586
+ function registerBotCommand(program2) {
1587
+ const bot = program2.command("bot").description("Manage bots").option("--user-id <userId>", "User ID for bot operations");
1588
+ registerBotCrudCommands(bot);
1589
+ registerBotLifecycleCommands(bot);
1590
+ registerBotLogsCommands(bot);
1591
+ registerBotRuntimeCommands(bot);
1592
+ registerModelCommand(bot);
1593
+ registerChannelCommand(bot);
1594
+ registerDeviceCommand(bot);
1595
+ registerSkillCommand(bot);
1596
+ }
1597
+
1330
1598
  // src/commands/setup.ts
1331
1599
  import chalk11 from "chalk";
1332
1600
 
@@ -1481,8 +1749,8 @@ function registerSetupCommand(program2) {
1481
1749
  console.log(chalk11.dim("\nUseful commands:"));
1482
1750
  console.log(chalk11.dim(` fastclaw bot status ${bot.id}`));
1483
1751
  console.log(chalk11.dim(` fastclaw bot connect ${bot.id}`));
1484
- console.log(chalk11.dim(` fastclaw model list ${bot.id}`));
1485
- console.log(chalk11.dim(` fastclaw channel add ${bot.id}`));
1752
+ console.log(chalk11.dim(` fastclaw bot model list ${bot.id}`));
1753
+ console.log(chalk11.dim(` fastclaw bot channel add ${bot.id}`));
1486
1754
  console.log("");
1487
1755
  })
1488
1756
  );
@@ -1491,13 +1759,9 @@ function registerSetupCommand(program2) {
1491
1759
  // src/index.ts
1492
1760
  var __dirname = dirname(fileURLToPath(import.meta.url));
1493
1761
  var pkg = JSON.parse(readFileSync3(join2(__dirname, "..", "package.json"), "utf-8"));
1494
- var program = new Command().name("fastclaw").description("CLI for managing FastClaw bots").version(pkg.version).option("--url <url>", "FastClaw server URL").option("--admin-token <token>", "Admin API token").option("--app-token <token>", "App API token").option("--json", "Output as JSON");
1762
+ var program = new Command().name("fastclaw").description("CLI for managing FastClaw bots").version(pkg.version).option("--url <url>", "FastClaw server URL").option("--admin-token <token>", "Admin API token").option("--app-token <token>", "App API token").option("--json", "Output as JSON").option("-y, --yes", "Skip confirmation prompts");
1495
1763
  registerSetupCommand(program);
1496
1764
  registerConfigCommand(program);
1497
- registerAdminCommand(program);
1765
+ registerAppCommand(program);
1498
1766
  registerBotCommand(program);
1499
- registerModelCommand(program);
1500
- registerChannelCommand(program);
1501
- registerDeviceCommand(program);
1502
- registerSkillCommand(program);
1503
1767
  program.parse();
package/dist/sdk.d.ts CHANGED
@@ -172,6 +172,13 @@ interface SkillInfo {
172
172
  interface SetSkillRequest {
173
173
  content: string;
174
174
  }
175
+ interface BotLogsResponse {
176
+ logs: string;
177
+ }
178
+ interface RuntimeExecRequest {
179
+ command?: string;
180
+ args?: string[];
181
+ }
175
182
  interface UpgradeRequest {
176
183
  image?: string;
177
184
  }
@@ -252,6 +259,36 @@ declare class FastClawClient {
252
259
  listSkills(botId: string): Promise<SkillInfo[]>;
253
260
  setSkill(botId: string, name: string, req: SetSkillRequest): Promise<unknown>;
254
261
  deleteSkill(botId: string, name: string): Promise<unknown>;
262
+ getBotLogs(botId: string, opts?: {
263
+ tail?: number;
264
+ sinceSeconds?: number;
265
+ sinceTime?: string;
266
+ timestamps?: boolean;
267
+ }): Promise<BotLogsResponse>;
268
+ getRuntimeStatus(botId: string, opts?: {
269
+ all?: boolean;
270
+ usage?: boolean;
271
+ deep?: boolean;
272
+ timeout?: string;
273
+ }): Promise<unknown>;
274
+ getRuntimeSessions(botId: string, opts?: {
275
+ agent?: string;
276
+ allAgents?: boolean;
277
+ active?: boolean;
278
+ }): Promise<unknown>;
279
+ getRuntimeConfig(botId: string, path?: string): Promise<unknown>;
280
+ getRuntimeConfigValidate(botId: string): Promise<unknown>;
281
+ getRuntimeModels(botId: string, opts?: {
282
+ all?: boolean;
283
+ agent?: string;
284
+ provider?: string;
285
+ }): Promise<unknown>;
286
+ getRuntimeSkills(botId: string, opts?: {
287
+ eligible?: boolean;
288
+ verbose?: boolean;
289
+ }): Promise<unknown>;
290
+ getRuntimeSkillsCheck(botId: string): Promise<unknown>;
291
+ runtimeExec(botId: string, req: RuntimeExecRequest): Promise<unknown>;
255
292
  waitForReady(botId: string, timeoutMs?: number, intervalMs?: number): Promise<BotStatusResponse>;
256
293
  health(): Promise<boolean>;
257
294
  }
@@ -273,6 +310,7 @@ interface ResolvedConfig {
273
310
  userId: string;
274
311
  botId?: string;
275
312
  json: boolean;
313
+ yes: boolean;
276
314
  }
277
315
  /**
278
316
  * Resolve config with priority: CLI flags > env vars > config file > defaults.
@@ -285,6 +323,7 @@ declare function resolveConfig(opts: {
285
323
  userId?: string;
286
324
  botId?: string;
287
325
  json?: boolean;
326
+ yes?: boolean;
288
327
  }): ResolvedConfig;
289
328
  declare function getConfigPath(): string;
290
329
 
@@ -309,4 +348,4 @@ declare function isValidSlug(slug: string): boolean;
309
348
  declare function isValidUrl(url: string): boolean;
310
349
  declare function slugify(name: string): string;
311
350
 
312
- export { API_BASE, type AddChannelRequest, ApiError, type ApiResponse, type App, type Bot, type BotConnectResponse, type BotStatus, type BotStatusResponse, type BulkUpgradeResponse, CHANNEL_FIELDS, type ChannelResponse, type ChannelsMap, type ClientOptions, type CreateAppRequest, type CreateBotRequest, DEFAULT_URL, DM_POLICIES, type DeviceInfo, type DeviceListResponse, FastClawClient, type FastClawConfig, GROUP_POLICIES, MODEL_PRESETS, type ModelDefaults, type ModelProvider, PROVIDER_DEFAULTS, type PairedUsersResponse, type PairingApproveRequest, type PairingRequest, type PairingRevokeRequest, type ProviderModel, type ResetTokenResponse, type ResolvedConfig, type SetSkillRequest, type SkillInfo, type UpdateAppRequest, type UpdateBotRequest, type UpgradeRequest, type UpgradeResponse, getConfigPath, isValidSlug, isValidUrl, loadConfig, resolveConfig, saveConfig, slugify, updateConfig };
351
+ export { API_BASE, type AddChannelRequest, ApiError, type ApiResponse, type App, type Bot, type BotConnectResponse, type BotLogsResponse, type BotStatus, type BotStatusResponse, type BulkUpgradeResponse, CHANNEL_FIELDS, type ChannelResponse, type ChannelsMap, type ClientOptions, type CreateAppRequest, type CreateBotRequest, DEFAULT_URL, DM_POLICIES, type DeviceInfo, type DeviceListResponse, FastClawClient, type FastClawConfig, GROUP_POLICIES, MODEL_PRESETS, type ModelDefaults, type ModelProvider, PROVIDER_DEFAULTS, type PairedUsersResponse, type PairingApproveRequest, type PairingRequest, type PairingRevokeRequest, type ProviderModel, type ResetTokenResponse, type ResolvedConfig, type RuntimeExecRequest, type SetSkillRequest, type SkillInfo, type UpdateAppRequest, type UpdateBotRequest, type UpgradeRequest, type UpgradeResponse, getConfigPath, isValidSlug, isValidUrl, loadConfig, resolveConfig, saveConfig, slugify, updateConfig };
package/dist/sdk.js CHANGED
@@ -114,6 +114,11 @@ var GROUP_POLICIES = ["open", "allowlist", "disabled"];
114
114
 
115
115
  // src/utils/error.ts
116
116
  import chalk from "chalk";
117
+
118
+ // src/utils/prompt.ts
119
+ import { input, select, password, confirm } from "@inquirer/prompts";
120
+
121
+ // src/utils/error.ts
117
122
  var ApiError = class extends Error {
118
123
  constructor(statusCode, apiCode, message) {
119
124
  super(message);
@@ -311,6 +316,58 @@ var FastClawClient = class {
311
316
  async deleteSkill(botId, name) {
312
317
  return this.request("DELETE", `/bots/${botId}/skills/${name}`);
313
318
  }
319
+ // ── Bot Logs ──
320
+ async getBotLogs(botId, opts) {
321
+ const query = {};
322
+ if (opts?.tail !== void 0) query.tail = String(opts.tail);
323
+ if (opts?.sinceSeconds !== void 0) query.sinceSeconds = String(opts.sinceSeconds);
324
+ if (opts?.sinceTime) query.sinceTime = opts.sinceTime;
325
+ if (opts?.timestamps) query.timestamps = "true";
326
+ return this.request("GET", `/bots/${botId}/logs`, { query });
327
+ }
328
+ // ── Bot Runtime (OpenClaw CLI exec) ──
329
+ async getRuntimeStatus(botId, opts) {
330
+ const query = {};
331
+ if (opts?.all) query.all = "true";
332
+ if (opts?.usage) query.usage = "true";
333
+ if (opts?.deep) query.deep = "true";
334
+ if (opts?.timeout) query.timeout = opts.timeout;
335
+ return this.request("GET", `/bots/${botId}/runtime/status`, { query });
336
+ }
337
+ async getRuntimeSessions(botId, opts) {
338
+ const query = {};
339
+ if (opts?.agent) query.agent = opts.agent;
340
+ if (opts?.allAgents) query["all-agents"] = "true";
341
+ if (opts?.active) query.active = "true";
342
+ return this.request("GET", `/bots/${botId}/runtime/sessions`, { query });
343
+ }
344
+ async getRuntimeConfig(botId, path) {
345
+ const query = {};
346
+ if (path) query.path = path;
347
+ return this.request("GET", `/bots/${botId}/runtime/config`, { query });
348
+ }
349
+ async getRuntimeConfigValidate(botId) {
350
+ return this.request("GET", `/bots/${botId}/runtime/config/validate`);
351
+ }
352
+ async getRuntimeModels(botId, opts) {
353
+ const query = {};
354
+ if (opts?.all) query.all = "true";
355
+ if (opts?.agent) query.agent = opts.agent;
356
+ if (opts?.provider) query.provider = opts.provider;
357
+ return this.request("GET", `/bots/${botId}/runtime/models`, { query });
358
+ }
359
+ async getRuntimeSkills(botId, opts) {
360
+ const query = {};
361
+ if (opts?.eligible) query.eligible = "true";
362
+ if (opts?.verbose) query.verbose = "true";
363
+ return this.request("GET", `/bots/${botId}/runtime/skills`, { query });
364
+ }
365
+ async getRuntimeSkillsCheck(botId) {
366
+ return this.request("GET", `/bots/${botId}/runtime/skills/check`);
367
+ }
368
+ async runtimeExec(botId, req) {
369
+ return this.request("POST", `/bots/${botId}/runtime/exec`, { body: req });
370
+ }
314
371
  // ── Utility: wait for bot ready ──
315
372
  async waitForReady(botId, timeoutMs = 12e4, intervalMs = 3e3) {
316
373
  const start = Date.now();
@@ -367,7 +424,8 @@ function resolveConfig(opts) {
367
424
  appToken: opts.appToken || process.env.FASTCLAW_APP_TOKEN || file.app_token,
368
425
  userId: opts.userId || process.env.FASTCLAW_USER_ID || file.default_user_id || "default",
369
426
  botId: opts.botId || process.env.FASTCLAW_BOT_ID || file.default_bot_id,
370
- json: opts.json ?? false
427
+ json: opts.json ?? false,
428
+ yes: opts.yes ?? false
371
429
  };
372
430
  }
373
431
  function getConfigPath() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fastclaw-cli",
3
- "version": "0.2.2",
3
+ "version": "0.3.1",
4
4
  "description": "CLI and SDK for managing FastClaw bots",
5
5
  "type": "module",
6
6
  "main": "dist/sdk.js",