twentythree-cli 1.3.8 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. package/dist/commands/agentic/session/list.cjs +79 -0
  2. package/dist/commands/agentic/session/metrics.cjs +50 -0
  3. package/dist/commands/agentic/session/status.cjs +86 -0
  4. package/dist/commands/auth/credentials.cjs +101 -14
  5. package/dist/commands/seo/metrics.cjs +56 -0
  6. package/dist/commands/webinar/create.cjs +57 -5
  7. package/dist/commands/webinar/mail/add.cjs +44 -5
  8. package/dist/commands/webinar/mail/update.cjs +44 -0
  9. package/dist/commands/webinar/speaker/add.cjs +32 -2
  10. package/dist/commands/webinar/speaker/update.cjs +32 -2
  11. package/dist/commands/webinar/update.cjs +60 -1
  12. package/docs/commands/action/add.md +1 -1
  13. package/docs/commands/action/delete.md +1 -1
  14. package/docs/commands/action/exclude.md +1 -1
  15. package/docs/commands/action/get.md +1 -1
  16. package/docs/commands/action/include.md +1 -1
  17. package/docs/commands/action/list.md +1 -1
  18. package/docs/commands/action/types.md +1 -1
  19. package/docs/commands/action/update.md +1 -1
  20. package/docs/commands/action/upload.md +1 -1
  21. package/docs/commands/action.md +9 -9
  22. package/docs/commands/agentic/session.md +104 -0
  23. package/docs/commands/agentic.md +94 -0
  24. package/docs/commands/analytics/conversions.md +3 -3
  25. package/docs/commands/analytics/live.md +9 -9
  26. package/docs/commands/analytics/usage.md +18 -18
  27. package/docs/commands/analytics/video.md +12 -12
  28. package/docs/commands/analytics.md +42 -42
  29. package/docs/commands/app/add.md +1 -1
  30. package/docs/commands/app/delete.md +1 -1
  31. package/docs/commands/app/list.md +1 -1
  32. package/docs/commands/app/remove-thumbnail.md +1 -1
  33. package/docs/commands/app/set-thumbnail.md +1 -1
  34. package/docs/commands/app/update.md +1 -1
  35. package/docs/commands/app.md +6 -6
  36. package/docs/commands/audience/companies.md +1 -1
  37. package/docs/commands/audience/field.md +4 -4
  38. package/docs/commands/audience/funnel.md +1 -1
  39. package/docs/commands/audience/identity-sources.md +1 -1
  40. package/docs/commands/audience/list-collectors.md +1 -1
  41. package/docs/commands/audience/list.md +1 -1
  42. package/docs/commands/audience/metrics.md +1 -1
  43. package/docs/commands/audience/register.md +1 -1
  44. package/docs/commands/audience/remove.md +1 -1
  45. package/docs/commands/audience/search.md +1 -1
  46. package/docs/commands/audience/timelines.md +1 -1
  47. package/docs/commands/audience/unregister.md +1 -1
  48. package/docs/commands/audience.md +15 -15
  49. package/docs/commands/auth/credentials.md +1 -1
  50. package/docs/commands/auth/status.md +1 -1
  51. package/docs/commands/auth/switch.md +1 -1
  52. package/docs/commands/auth.md +25 -4
  53. package/docs/commands/autocomplete.md +1 -1
  54. package/docs/commands/category/create.md +1 -1
  55. package/docs/commands/category/delete.md +1 -1
  56. package/docs/commands/category/list.md +1 -1
  57. package/docs/commands/category/update.md +1 -1
  58. package/docs/commands/category.md +5 -5
  59. package/docs/commands/collector/exclude.md +1 -1
  60. package/docs/commands/collector/include.md +1 -1
  61. package/docs/commands/collector/list.md +1 -1
  62. package/docs/commands/collector.md +3 -3
  63. package/docs/commands/comment/add.md +1 -1
  64. package/docs/commands/comment/clone.md +1 -1
  65. package/docs/commands/comment/delete.md +1 -1
  66. package/docs/commands/comment/list.md +1 -1
  67. package/docs/commands/comment/promote.md +1 -1
  68. package/docs/commands/comment/reaction.md +3 -3
  69. package/docs/commands/comment/set-order.md +1 -1
  70. package/docs/commands/comment/update.md +1 -1
  71. package/docs/commands/comment.md +10 -10
  72. package/docs/commands/doctor.md +1 -1
  73. package/docs/commands/openupload/list.md +1 -1
  74. package/docs/commands/openupload/update-file.md +1 -1
  75. package/docs/commands/openupload/upload-file.md +1 -1
  76. package/docs/commands/openupload.md +3 -3
  77. package/docs/commands/player/delete.md +1 -1
  78. package/docs/commands/player/embed-versions.md +1 -1
  79. package/docs/commands/player/embed.md +1 -1
  80. package/docs/commands/player/list.md +1 -1
  81. package/docs/commands/player/remove-thumbnail.md +1 -1
  82. package/docs/commands/player/set-thumbnail.md +1 -1
  83. package/docs/commands/player/styles.md +1 -1
  84. package/docs/commands/player/update.md +1 -1
  85. package/docs/commands/player.md +8 -8
  86. package/docs/commands/poll/add.md +1 -1
  87. package/docs/commands/poll/answer.md +1 -1
  88. package/docs/commands/poll/list.md +1 -1
  89. package/docs/commands/poll/remove.md +1 -1
  90. package/docs/commands/poll/set-options.md +1 -1
  91. package/docs/commands/poll/update.md +1 -1
  92. package/docs/commands/poll.md +6 -6
  93. package/docs/commands/presentation/page.md +1 -1
  94. package/docs/commands/presentation/setting.md +2 -2
  95. package/docs/commands/presentation.md +3 -3
  96. package/docs/commands/protection/protect.md +1 -1
  97. package/docs/commands/protection/unprotect.md +1 -1
  98. package/docs/commands/protection/verify.md +1 -1
  99. package/docs/commands/protection.md +3 -3
  100. package/docs/commands/seo/get.md +1 -1
  101. package/docs/commands/seo/metrics.md +35 -0
  102. package/docs/commands/seo/status.md +1 -1
  103. package/docs/commands/seo/update.md +1 -1
  104. package/docs/commands/seo.md +30 -3
  105. package/docs/commands/session/get-token.md +1 -1
  106. package/docs/commands/session/redeem-token.md +1 -1
  107. package/docs/commands/session.md +2 -2
  108. package/docs/commands/setting/update.md +1 -1
  109. package/docs/commands/setting.md +1 -1
  110. package/docs/commands/site/get.md +1 -1
  111. package/docs/commands/site/search.md +1 -1
  112. package/docs/commands/site.md +2 -2
  113. package/docs/commands/spot/check.md +1 -1
  114. package/docs/commands/spot/create.md +1 -1
  115. package/docs/commands/spot/delete.md +1 -1
  116. package/docs/commands/spot/list.md +1 -1
  117. package/docs/commands/spot/reset-version.md +1 -1
  118. package/docs/commands/spot/set-videos.md +1 -1
  119. package/docs/commands/spot/update.md +1 -1
  120. package/docs/commands/spot.md +7 -7
  121. package/docs/commands/tag/list.md +1 -1
  122. package/docs/commands/tag/related.md +1 -1
  123. package/docs/commands/tag.md +2 -2
  124. package/docs/commands/thumbnail/add.md +1 -1
  125. package/docs/commands/thumbnail/data.md +1 -1
  126. package/docs/commands/thumbnail/delete.md +1 -1
  127. package/docs/commands/thumbnail/duplicate.md +1 -1
  128. package/docs/commands/thumbnail/file.md +3 -3
  129. package/docs/commands/thumbnail/list.md +1 -1
  130. package/docs/commands/thumbnail/preview-scss.md +1 -1
  131. package/docs/commands/thumbnail/update.md +1 -1
  132. package/docs/commands/thumbnail.md +11 -11
  133. package/docs/commands/user/create.md +1 -1
  134. package/docs/commands/user/get-login-token.md +1 -1
  135. package/docs/commands/user/get.md +1 -1
  136. package/docs/commands/user/list.md +1 -1
  137. package/docs/commands/user/redeem-login-token.md +1 -1
  138. package/docs/commands/user/send-invitation.md +1 -1
  139. package/docs/commands/user/tokens.md +1 -1
  140. package/docs/commands/user/update.md +1 -1
  141. package/docs/commands/user.md +8 -8
  142. package/docs/commands/video/delete.md +1 -1
  143. package/docs/commands/video/frame.md +1 -1
  144. package/docs/commands/video/get.md +1 -1
  145. package/docs/commands/video/list.md +1 -1
  146. package/docs/commands/video/replace.md +1 -1
  147. package/docs/commands/video/section.md +8 -8
  148. package/docs/commands/video/subtitle.md +12 -12
  149. package/docs/commands/video/transcoding-progress.md +1 -1
  150. package/docs/commands/video/update.md +1 -1
  151. package/docs/commands/video/upload.md +1 -1
  152. package/docs/commands/video.md +28 -28
  153. package/docs/commands/webhook/events.md +1 -1
  154. package/docs/commands/webhook/list.md +1 -1
  155. package/docs/commands/webhook/sample.md +1 -1
  156. package/docs/commands/webhook/subscribe.md +1 -1
  157. package/docs/commands/webhook/unsubscribe.md +1 -1
  158. package/docs/commands/webhook.md +5 -5
  159. package/docs/commands/webinar/attachment.md +4 -4
  160. package/docs/commands/webinar/clips.md +1 -1
  161. package/docs/commands/webinar/create.md +30 -12
  162. package/docs/commands/webinar/delete.md +1 -1
  163. package/docs/commands/webinar/highlights.md +1 -1
  164. package/docs/commands/webinar/list-formats.md +1 -1
  165. package/docs/commands/webinar/list.md +1 -1
  166. package/docs/commands/webinar/log.md +1 -1
  167. package/docs/commands/webinar/mail.md +35 -18
  168. package/docs/commands/webinar/metrics.md +1 -1
  169. package/docs/commands/webinar/queued-video.md +2 -2
  170. package/docs/commands/webinar/recording.md +4 -4
  171. package/docs/commands/webinar/repeat.md +1 -1
  172. package/docs/commands/webinar/room.md +4 -4
  173. package/docs/commands/webinar/section.md +4 -4
  174. package/docs/commands/webinar/series.md +12 -12
  175. package/docs/commands/webinar/speaker.md +41 -26
  176. package/docs/commands/webinar/transcription.md +4 -4
  177. package/docs/commands/webinar/update.md +25 -10
  178. package/docs/commands/webinar/upload-image.md +1 -1
  179. package/docs/commands/webinar.md +185 -109
  180. package/docs/commands/workspace/list.md +1 -1
  181. package/docs/commands/workspace/use.md +1 -1
  182. package/docs/commands/workspace.md +2 -2
  183. package/docs/guides/ai-agents.md +169 -0
  184. package/oclif.manifest.json +2022 -1308
  185. package/package.json +1 -1
@@ -0,0 +1,79 @@
1
+ const require_runtime = require("../../../_virtual/_rolldown/runtime.cjs");
2
+ const require_lib_base_command = require("../../../lib/base-command.cjs");
3
+ const require_lib_output = require("../../../lib/output.cjs");
4
+ let _oclif_core = require("@oclif/core");
5
+ let chalk = require("chalk");
6
+ chalk = require_runtime.__toESM(chalk);
7
+ //#region src/commands/agentic/session/list.ts
8
+ /**
9
+ * Agentic session list command — lists agentic (AI agent) sessions that have
10
+ * been reported to the workspace via `agentic session status`.
11
+ */
12
+ var AgenticSessionList = class AgenticSessionList extends require_lib_base_command.AuthenticatedCommand {
13
+ static description = "List reported agentic (AI agent) sessions";
14
+ static examples = ["<%= config.bin %> agentic session list", "<%= config.bin %> agentic session list --json"];
15
+ static enableJsonFlag = true;
16
+ static agentMetadata = {
17
+ api_endpoint: "POST /agentic/session/list",
18
+ auth_scope: "read",
19
+ output_shape: {
20
+ type: "table",
21
+ columns: [
22
+ "Session",
23
+ "Summary",
24
+ "Prompts",
25
+ "Duration (s)",
26
+ "Started"
27
+ ]
28
+ },
29
+ side_effects: "none"
30
+ };
31
+ static flags = {
32
+ ...require_lib_base_command.AuthenticatedCommand.baseFlags,
33
+ fields: _oclif_core.Flags.string({
34
+ description: "Comma-separated fields to return",
35
+ required: false
36
+ })
37
+ };
38
+ static args = {};
39
+ async run() {
40
+ const { flags } = await this.parse(AgenticSessionList);
41
+ this.printWorkspaceHeader();
42
+ const body = {};
43
+ if (flags.fields) body.fields = flags.fields;
44
+ const { data, error } = await this.apiClient.POST("/agentic/session/list", {
45
+ body,
46
+ headers: { "Content-Type": "application/x-www-form-urlencoded" }
47
+ });
48
+ if (error) this.error(require_lib_output.formatApiError(error), { exit: 1 });
49
+ const resp = data;
50
+ const sessions = Array.isArray(resp?.data) ? resp.data : resp?.data ? [resp.data] : [];
51
+ if (this.jsonEnabled()) return require_lib_output.formatJsonOutput({
52
+ ok: true,
53
+ data: sessions,
54
+ summary: `${sessions.length} session${sessions.length === 1 ? "" : "s"}`,
55
+ breadcrumbs: [{ domain: this.activeWorkspace.domain }, { resource: "agentic" }]
56
+ });
57
+ if (sessions.length === 0) {
58
+ this.log("No agentic sessions found.");
59
+ return;
60
+ }
61
+ const table = require_lib_output.renderTable([
62
+ "Session",
63
+ "Summary",
64
+ "Prompts",
65
+ "Duration (s)",
66
+ "Started"
67
+ ], sessions.map((s) => [
68
+ String(s.session_identifier ?? ""),
69
+ String(s.summary ?? ""),
70
+ String(s.number_of_prompts ?? ""),
71
+ String(s.session_duration_seconds ?? ""),
72
+ s.session_start_time_epoch ? (/* @__PURE__ */ new Date(Number(s.session_start_time_epoch) * 1e3)).toISOString() : ""
73
+ ]));
74
+ this.log(table.toString());
75
+ this.log(chalk.default.dim(`${sessions.length} session${sessions.length === 1 ? "" : "s"}`));
76
+ }
77
+ };
78
+ //#endregion
79
+ module.exports = AgenticSessionList;
@@ -0,0 +1,50 @@
1
+ require("../../../_virtual/_rolldown/runtime.cjs");
2
+ const require_lib_base_command = require("../../../lib/base-command.cjs");
3
+ const require_lib_output = require("../../../lib/output.cjs");
4
+ let _oclif_core = require("@oclif/core");
5
+ //#region src/commands/agentic/session/metrics.ts
6
+ /**
7
+ * Agentic session metrics command — retrieves aggregate metrics across all
8
+ * reported agentic (AI agent) sessions in the workspace.
9
+ */
10
+ var AgenticSessionMetrics = class AgenticSessionMetrics extends require_lib_base_command.AuthenticatedCommand {
11
+ static description = "Get aggregate metrics for reported agentic (AI agent) sessions";
12
+ static examples = ["<%= config.bin %> agentic session metrics", "<%= config.bin %> agentic session metrics --json"];
13
+ static enableJsonFlag = true;
14
+ static agentMetadata = {
15
+ api_endpoint: "POST /agentic/session/metrics",
16
+ auth_scope: "read",
17
+ output_shape: { type: "key-value" },
18
+ side_effects: "none"
19
+ };
20
+ static flags = {
21
+ ...require_lib_base_command.AuthenticatedCommand.baseFlags,
22
+ fields: _oclif_core.Flags.string({
23
+ description: "Comma-separated fields to return",
24
+ required: false
25
+ })
26
+ };
27
+ async run() {
28
+ const { flags } = await this.parse(AgenticSessionMetrics);
29
+ this.printWorkspaceHeader();
30
+ const body = {};
31
+ if (flags.fields) body.fields = flags.fields;
32
+ const { data, error } = await this.apiClient.POST("/agentic/session/metrics", {
33
+ body,
34
+ headers: { "Content-Type": "application/x-www-form-urlencoded" }
35
+ });
36
+ if (error) this.error(require_lib_output.formatApiError(error), { exit: 1 });
37
+ if (this.jsonEnabled()) return require_lib_output.formatJsonOutput({
38
+ ok: true,
39
+ data,
40
+ summary: "Agentic session metrics",
41
+ breadcrumbs: [{ domain: this.activeWorkspace.domain }, { resource: "agentic" }]
42
+ });
43
+ const resp = data?.data ?? data;
44
+ this.log(`session_count: ${String(resp?.session_count ?? "")}`);
45
+ this.log(`number_of_prompts: ${String(resp?.number_of_prompts ?? "")}`);
46
+ this.log(`session_duration_seconds: ${String(resp?.session_duration_seconds ?? "")}`);
47
+ }
48
+ };
49
+ //#endregion
50
+ module.exports = AgenticSessionMetrics;
@@ -0,0 +1,86 @@
1
+ const require_runtime = require("../../../_virtual/_rolldown/runtime.cjs");
2
+ const require_lib_base_command = require("../../../lib/base-command.cjs");
3
+ const require_lib_output = require("../../../lib/output.cjs");
4
+ let _oclif_core = require("@oclif/core");
5
+ let chalk = require("chalk");
6
+ chalk = require_runtime.__toESM(chalk);
7
+ //#region src/commands/agentic/session/status.ts
8
+ /**
9
+ * Agentic session status command — reports (stores) the outcome of an agentic
10
+ * (AI agent) session: what it accomplished, prompt count, duration, the AI
11
+ * provider used, and the TwentyThree skill version.
12
+ */
13
+ var AgenticSessionStatus = class AgenticSessionStatus extends require_lib_base_command.AuthenticatedCommand {
14
+ static description = "Report (store) the status of an agentic (AI agent) session";
15
+ static examples = ["<%= config.bin %> agentic session status --session-identifier abc123 --summary \"Uploaded 3 videos\" --number-of-prompts 12 --session-duration-seconds 540 --ai-provider \"claude code\" --twentythree-skill-version 1.4.0", "<%= config.bin %> agentic session status --session-identifier abc123 --summary \"Created a webinar\" --number-of-prompts 5 --session-duration-seconds 120 --ai-provider \"codex\" --twentythree-skill-version 1.4.0 --json"];
16
+ static enableJsonFlag = true;
17
+ static agentMetadata = {
18
+ api_endpoint: "POST /agentic/session/status",
19
+ auth_scope: "read",
20
+ output_shape: { type: "none" },
21
+ side_effects: "creates"
22
+ };
23
+ static flags = {
24
+ ...require_lib_base_command.AuthenticatedCommand.baseFlags,
25
+ "session-identifier": _oclif_core.Flags.string({
26
+ description: "Unique identifier for the agent session being reported",
27
+ required: true
28
+ }),
29
+ summary: _oclif_core.Flags.string({
30
+ description: "Short summary of what the agent session accomplished",
31
+ required: true
32
+ }),
33
+ "number-of-prompts": _oclif_core.Flags.integer({
34
+ description: "Number of user prompts in the agent session (0 if unknown)",
35
+ required: true
36
+ }),
37
+ "session-duration-seconds": _oclif_core.Flags.integer({
38
+ description: "Duration of the agent session, in seconds (0 if unknown)",
39
+ required: true
40
+ }),
41
+ "ai-provider": _oclif_core.Flags.string({
42
+ description: "AI/LLM provider used in the session (e.g. \"claude code\", \"codex\")",
43
+ required: true
44
+ }),
45
+ "twentythree-skill-version": _oclif_core.Flags.string({
46
+ description: "Version of the TwentyThree skill used (\"unknown\" if unknown)",
47
+ required: true
48
+ }),
49
+ fields: _oclif_core.Flags.string({
50
+ description: "Comma-separated fields to return",
51
+ required: false
52
+ })
53
+ };
54
+ static args = {};
55
+ async run() {
56
+ const { flags } = await this.parse(AgenticSessionStatus);
57
+ this.printWorkspaceHeader();
58
+ const body = {
59
+ session_identifier: flags["session-identifier"],
60
+ summary: flags.summary,
61
+ number_of_prompts: flags["number-of-prompts"],
62
+ session_duration_seconds: flags["session-duration-seconds"],
63
+ ai_provider: flags["ai-provider"],
64
+ twentythree_skill_version: flags["twentythree-skill-version"]
65
+ };
66
+ if (flags.fields) body.fields = flags.fields;
67
+ const { data, error } = await this.apiClient.POST("/agentic/session/status", {
68
+ body,
69
+ headers: { "Content-Type": "application/x-www-form-urlencoded" }
70
+ });
71
+ if (error) this.error(require_lib_output.formatApiError(error), { exit: 1 });
72
+ if (this.jsonEnabled()) return require_lib_output.formatJsonOutput({
73
+ ok: true,
74
+ data,
75
+ summary: "Agentic session status reported",
76
+ breadcrumbs: [{ domain: this.activeWorkspace.domain }, {
77
+ resource: "agentic",
78
+ id: flags["session-identifier"]
79
+ }]
80
+ });
81
+ this.log(chalk.default.green("Agentic session status reported"));
82
+ this.log(`Session: ${flags["session-identifier"]}`);
83
+ }
84
+ };
85
+ //#endregion
86
+ module.exports = AgenticSessionStatus;
@@ -6,17 +6,114 @@ let _oclif_core = require("@oclif/core");
6
6
  let _clack_prompts = require("@clack/prompts");
7
7
  _clack_prompts = require_runtime.__toESM(_clack_prompts);
8
8
  //#region src/commands/auth/credentials.ts
9
+ /**
10
+ * Build the workspace entry used for anonymous (domain-only) access — no token,
11
+ * no workspace discovery. Shared by the interactive and non-interactive paths.
12
+ */
13
+ function buildDomainOnlyEntry(domain) {
14
+ return {
15
+ domain,
16
+ display_name: domain,
17
+ bearer_token: "",
18
+ expiration_time: "",
19
+ api_base_url: `https://${domain}/`,
20
+ site_name: domain,
21
+ canonical_user_p: false,
22
+ starred_p: false
23
+ };
24
+ }
9
25
  var Credentials = class Credentials extends _oclif_core.Command {
10
26
  static description = "Configure domain and bearer token for a TwentyThree workspace";
27
+ static enableJsonFlag = true;
11
28
  static agentMetadata = {
12
29
  api_endpoint: "interactive",
13
30
  auth_scope: "none",
14
31
  output_shape: { type: "none" },
15
32
  side_effects: "creates"
16
33
  };
17
- static examples = ["<%= config.bin %> auth credentials"];
34
+ static flags = {
35
+ domain: _oclif_core.Flags.string({ description: "Workspace domain (e.g. company.video23.com). Passing this runs the command non-interactively (no prompts)." }),
36
+ token: _oclif_core.Flags.string({ description: "Bearer/login token. Falls back to the TWENTYTHREE_TOKEN env var. Omit for anonymous (domain-only) access." }),
37
+ workspace: _oclif_core.Flags.string({ description: "Which discovered workspace to set active (domain or display name) when the token unlocks several. Non-interactive mode only." })
38
+ };
39
+ static examples = [
40
+ "<%= config.bin %> auth credentials",
41
+ "<%= config.bin %> auth credentials --domain company.video23.com --token <token>",
42
+ "<%= config.bin %> auth credentials --domain company.video23.com --token <token> --workspace \"Marketing\"",
43
+ "TWENTYTHREE_TOKEN=<token> <%= config.bin %> auth credentials --domain company.video23.com --json"
44
+ ];
18
45
  async run() {
19
- await this.parse(Credentials);
46
+ const { flags } = await this.parse(Credentials);
47
+ if (flags.domain !== void 0) return this.runNonInteractive(flags.domain, flags.token ?? process.env.TWENTYTHREE_TOKEN, flags.workspace);
48
+ if (!process.stdin.isTTY) this.error("No interactive terminal detected. Run non-interactively instead:\n twentythree auth credentials --domain <domain> [--token <token>]\n(the token can also be supplied via the TWENTYTHREE_TOKEN env var).", { exit: 1 });
49
+ return this.runInteractive();
50
+ }
51
+ /**
52
+ * Non-interactive configuration driven entirely by flags / env vars.
53
+ * Returns a JSON-serializable summary when --json is set.
54
+ */
55
+ async runNonInteractive(domain, token, workspaceSelector) {
56
+ if (!domain.includes(".")) this.error(`Invalid domain '${domain}' — expected something like company.video23.com`, { exit: 1 });
57
+ const trimmedToken = (token ?? "").trim();
58
+ const json = this.jsonEnabled();
59
+ if (!trimmedToken) {
60
+ require_auth_workspace_config.setWorkspaces([buildDomainOnlyEntry(domain)]);
61
+ require_auth_workspace_config.setActiveWorkspace(domain);
62
+ if (json) return {
63
+ domain,
64
+ mode: "anonymous",
65
+ active_workspace: domain,
66
+ workspaces: [domain]
67
+ };
68
+ this.log(`Anonymous mode configured for ${domain}. Only endpoints that do not require authentication are accessible.`);
69
+ this.log("Re-run with --token to add a bearer token.");
70
+ return;
71
+ }
72
+ require_auth_credential_store.setCredential(domain, trimmedToken);
73
+ require_auth_workspace_config.setCredentialDomain(domain);
74
+ let workspaces;
75
+ try {
76
+ workspaces = await require_auth_token_refresh.fetchWorkspaceTokens(domain, trimmedToken);
77
+ } catch (err) {
78
+ this.error(`Could not discover workspaces: ${err instanceof Error ? err.message : String(err)}`, { exit: 1 });
79
+ return;
80
+ }
81
+ if (workspaces.length === 0) {
82
+ this.error("No workspaces were returned for the provided token.", { exit: 1 });
83
+ return;
84
+ }
85
+ let active;
86
+ if (workspaceSelector) {
87
+ const match = require_auth_workspace_config.findWorkspace(workspaceSelector, workspaces);
88
+ if (match === null) {
89
+ this.error(`No workspace matching '${workspaceSelector}'. Available: ` + workspaces.map((w) => `${w.display_name} (${w.domain})`).join(", "), { exit: 1 });
90
+ return;
91
+ }
92
+ if (Array.isArray(match)) {
93
+ this.error(`'${workspaceSelector}' is ambiguous — matches: ${match.map((w) => w.domain).join(", ")}. Use the exact domain.`, { exit: 1 });
94
+ return;
95
+ }
96
+ active = match;
97
+ } else active = workspaces.find((w) => w.starred_p) ?? workspaces.find((w) => w.canonical_user_p) ?? workspaces[0];
98
+ require_auth_workspace_config.setWorkspaces(workspaces);
99
+ require_auth_workspace_config.setActiveWorkspace(active.domain);
100
+ if (json) return {
101
+ domain,
102
+ mode: "authenticated",
103
+ active_workspace: active.domain,
104
+ workspaces: workspaces.map((w) => ({
105
+ domain: w.domain,
106
+ display_name: w.display_name
107
+ }))
108
+ };
109
+ this.log(`Credentials saved for ${domain}.`);
110
+ if (workspaces.length > 1 && !workspaceSelector) this.log(`Discovered ${workspaces.length} workspaces; set '${active.display_name} (${active.domain})' active. Use --workspace to choose a different one.`);
111
+ else this.log(`Active workspace: ${active.display_name} (${active.domain}).`);
112
+ }
113
+ /**
114
+ * Interactive configuration via @clack prompts (the original flow).
115
+ */
116
+ async runInteractive() {
20
117
  _clack_prompts.intro("TwentyThree credentials");
21
118
  const domain = await _clack_prompts.text({
22
119
  message: "Domain (e.g. company.video23.com)",
@@ -68,18 +165,8 @@ var Credentials = class Credentials extends _oclif_core.Command {
68
165
  require_auth_workspace_config.setWorkspaces(workspaces);
69
166
  require_auth_workspace_config.setActiveWorkspace(defaultDomain);
70
167
  } else {
71
- const domainStr = domain;
72
- require_auth_workspace_config.setWorkspaces([{
73
- domain: domainStr,
74
- display_name: domainStr,
75
- bearer_token: "",
76
- expiration_time: "",
77
- api_base_url: `https://${domainStr}/`,
78
- site_name: domainStr,
79
- canonical_user_p: false,
80
- starred_p: false
81
- }]);
82
- require_auth_workspace_config.setActiveWorkspace(domainStr);
168
+ require_auth_workspace_config.setWorkspaces([buildDomainOnlyEntry(domain)]);
169
+ require_auth_workspace_config.setActiveWorkspace(domain);
83
170
  this.log("Anonymous mode: only endpoints that do not require authentication are accessible.");
84
171
  this.log("Run `twentythree auth credentials` again to add a bearer token.");
85
172
  }
@@ -0,0 +1,56 @@
1
+ require("../../_virtual/_rolldown/runtime.cjs");
2
+ const require_lib_base_command = require("../../lib/base-command.cjs");
3
+ const require_lib_output = require("../../lib/output.cjs");
4
+ let _oclif_core = require("@oclif/core");
5
+ //#region src/commands/seo/metrics.ts
6
+ /**
7
+ * SEO metrics command — retrieves workspace-wide SEO and GEO metrics:
8
+ * average score across all objects, video/webinar/page counts, and the
9
+ * library's SEO health broken into high/medium/low tiers.
10
+ */
11
+ var SeoMetrics = class SeoMetrics extends require_lib_base_command.AuthenticatedCommand {
12
+ static description = "Get workspace-wide SEO and GEO metrics";
13
+ static examples = ["<%= config.bin %> seo metrics", "<%= config.bin %> seo metrics --json"];
14
+ static enableJsonFlag = true;
15
+ static agentMetadata = {
16
+ api_endpoint: "POST /seo/metrics",
17
+ auth_scope: "read",
18
+ output_shape: { type: "key-value" },
19
+ side_effects: "none"
20
+ };
21
+ static flags = {
22
+ ...require_lib_base_command.AuthenticatedCommand.baseFlags,
23
+ fields: _oclif_core.Flags.string({
24
+ description: "Comma-separated fields to return",
25
+ required: false
26
+ })
27
+ };
28
+ async run() {
29
+ const { flags } = await this.parse(SeoMetrics);
30
+ this.printWorkspaceHeader();
31
+ const body = {};
32
+ if (flags.fields) body.fields = flags.fields;
33
+ const { data, error } = await this.apiClient.POST("/seo/metrics", {
34
+ body,
35
+ headers: { "Content-Type": "application/x-www-form-urlencoded" }
36
+ });
37
+ if (error) this.error(require_lib_output.formatApiError(error), { exit: 1 });
38
+ const domain = this.activeWorkspace.domain;
39
+ if (this.jsonEnabled()) return require_lib_output.formatJsonOutput({
40
+ ok: true,
41
+ data,
42
+ summary: "SEO metrics",
43
+ breadcrumbs: [{ domain }, { resource: "seo" }]
44
+ });
45
+ const resp = data?.data ?? data;
46
+ this.log(`average_score: ${String(resp?.average_score ?? "")}`);
47
+ this.log(`video_count: ${String(resp?.video_count ?? "")}`);
48
+ this.log(`webinar_count: ${String(resp?.webinar_count ?? "")}`);
49
+ this.log(`page_count: ${String(resp?.page_count ?? "")}`);
50
+ this.log(`library_health_high: ${String(resp?.library_health_high ?? "")}`);
51
+ this.log(`library_health_medium: ${String(resp?.library_health_medium ?? "")}`);
52
+ this.log(`library_health_low: ${String(resp?.library_health_low ?? "")}`);
53
+ }
54
+ };
55
+ //#endregion
56
+ module.exports = SeoMetrics;
@@ -19,11 +19,13 @@ chalk = require_runtime.__toESM(chalk);
19
19
  * T-04-08: extends AuthenticatedCommand — anonymous mode rejected
20
20
  */
21
21
  var WebinarCreate = class WebinarCreate extends require_lib_base_command.AuthenticatedCommand {
22
- static description = "Create a new webinar";
22
+ static description = "Create a new webinar. By default the webinar is created as a draft with registration enabled (registration-mode=all); pass --no-draft/--publish or --registration-mode none to change this.";
23
23
  static examples = [
24
24
  "<%= config.bin %> webinar create --title \"My Webinar\"",
25
- "<%= config.bin %> webinar create --title \"My Webinar\" --live-date \"2024-12-01T14:00:00Z\"",
26
- "<%= config.bin %> webinar create --title \"My Webinar\" --json"
25
+ "<%= config.bin %> webinar create --title \"My Webinar\" --live-date \"2024-12-01T14:00:00Z\" --timezone Europe/Copenhagen",
26
+ "<%= config.bin %> webinar create --title \"My Webinar\" --format webinar --locale da_DK --category-id 127972488",
27
+ "<%= config.bin %> webinar create --title \"My Webinar\" --no-draft --no-private --publish-recordings",
28
+ "<%= config.bin %> webinar create --title \"Episode 3\" --series-id 67890 --json"
27
29
  ];
28
30
  static enableJsonFlag = true;
29
31
  static flags = {
@@ -58,6 +60,47 @@ var WebinarCreate = class WebinarCreate extends require_lib_base_command.Authent
58
60
  description: "Assign a webinar design by ID to this webinar",
59
61
  required: false
60
62
  }),
63
+ format: _oclif_core.Flags.string({
64
+ description: "Webinar format: \"webinar\" (registration, hub) or \"event\" (freeform live stream)",
65
+ options: ["event", "webinar"],
66
+ required: false
67
+ }),
68
+ "registration-mode": _oclif_core.Flags.string({
69
+ description: "Registration mode. Defaults to \"all\" (registration enabled); pass \"none\" to disable.",
70
+ options: [
71
+ "",
72
+ "all",
73
+ "none"
74
+ ],
75
+ default: "all",
76
+ required: false
77
+ }),
78
+ private: _oclif_core.Flags.boolean({
79
+ description: "Make the webinar private (use --no-private to make it public and appear on the hub)",
80
+ allowNo: true,
81
+ required: false
82
+ }),
83
+ "category-id": _oclif_core.Flags.integer({
84
+ description: "Assign the webinar to a category by ID (API album_id)",
85
+ required: false
86
+ }),
87
+ locale: _oclif_core.Flags.string({
88
+ description: "Webinar language/locale (e.g. en_US, da_DK)",
89
+ required: false
90
+ }),
91
+ "publish-recordings": _oclif_core.Flags.boolean({
92
+ description: "Publish the webinar recordings",
93
+ allowNo: true,
94
+ required: false
95
+ }),
96
+ "series-id": _oclif_core.Flags.integer({
97
+ description: "Attach the webinar to a webinar series by ID",
98
+ required: false
99
+ }),
100
+ timezone: _oclif_core.Flags.string({
101
+ description: "Timezone for the webinar schedule (e.g. Europe/Copenhagen)",
102
+ required: false
103
+ }),
61
104
  "draft-p": _oclif_core.Flags.string({
62
105
  hidden: true,
63
106
  required: false
@@ -81,11 +124,20 @@ var WebinarCreate = class WebinarCreate extends require_lib_base_command.Authent
81
124
  if (flags.description !== void 0) body.description = flags.description;
82
125
  if (flags.status !== void 0) body.live_status = flags.status;
83
126
  if (flags["live-date"] !== void 0) body.start_time = flags["live-date"];
84
- const draftVal = require_lib_output.parseBoolParam(flags.draft, flags["draft-p"]);
85
- if (draftVal !== void 0) body.draft_p = draftVal ? 1 : 0;
86
127
  const publishVal = require_lib_output.parseBoolParam(flags.publish, flags["published-p"]);
128
+ let draftVal = require_lib_output.parseBoolParam(flags.draft, flags["draft-p"]);
129
+ if (draftVal === void 0) draftVal = publishVal === true ? false : true;
130
+ body.draft_p = draftVal ? 1 : 0;
87
131
  if (publishVal !== void 0) body.published_p = publishVal ? 1 : 0;
88
132
  if (flags["webinar-design-id"] !== void 0) body.webinar_design_id = flags["webinar-design-id"];
133
+ if (flags.format !== void 0) body.live_format = flags.format;
134
+ if (flags["registration-mode"] !== void 0) body.registration_mode = flags["registration-mode"];
135
+ if (flags.private !== void 0) body.private_p = flags.private ? 1 : 0;
136
+ if (flags["category-id"] !== void 0) body.album_id = flags["category-id"];
137
+ if (flags.locale !== void 0) body.locale = flags.locale;
138
+ if (flags["publish-recordings"] !== void 0) body.publish_recordings_p = flags["publish-recordings"] ? 1 : 0;
139
+ if (flags["series-id"] !== void 0) body.live_series_id = flags["series-id"];
140
+ if (flags.timezone !== void 0) body.timezone = flags.timezone;
89
141
  const { data: createData, error: createError } = await this.apiClient.POST("/live/create", {
90
142
  body,
91
143
  headers: { "Content-Type": "application/x-www-form-urlencoded" }
@@ -34,6 +34,37 @@ var WebinarMailAdd = class WebinarMailAdd extends require_lib_base_command.Authe
34
34
  message: _oclif_core.Flags.string({
35
35
  description: "Email message body",
36
36
  required: false
37
+ }),
38
+ "recipient-groups": _oclif_core.Flags.string({
39
+ description: "Recipient groups (comma-separated): speakers, registered, attendees, noshows",
40
+ required: false
41
+ }),
42
+ "scheduled-at": _oclif_core.Flags.string({
43
+ description: "When to send the email (ISO 8601 timestamp)",
44
+ required: false
45
+ }),
46
+ "cta-link": _oclif_core.Flags.string({
47
+ description: "Call-to-action link URL",
48
+ required: false
49
+ }),
50
+ "cta-label": _oclif_core.Flags.string({
51
+ description: "Call-to-action button label",
52
+ required: false
53
+ }),
54
+ "send-immediately": _oclif_core.Flags.boolean({
55
+ description: "Send the email immediately",
56
+ allowNo: true,
57
+ required: false
58
+ }),
59
+ "include-live-info": _oclif_core.Flags.boolean({
60
+ description: "Include the webinar info block in the email",
61
+ allowNo: true,
62
+ required: false
63
+ }),
64
+ "include-series-archive": _oclif_core.Flags.boolean({
65
+ description: "Include the series archive block in the email",
66
+ allowNo: true,
67
+ required: false
37
68
  })
38
69
  };
39
70
  static args = { id: _oclif_core.Args.string({
@@ -67,12 +98,20 @@ var WebinarMailAdd = class WebinarMailAdd extends require_lib_base_command.Authe
67
98
  }
68
99
  if (!subject) this.error("--subject is required in non-interactive mode", { exit: 1 });
69
100
  if (!message) this.error("--message is required in non-interactive mode", { exit: 1 });
101
+ const body = {
102
+ ...contextField,
103
+ subject,
104
+ message
105
+ };
106
+ if (flags["recipient-groups"] !== void 0) body.recipient_groups = flags["recipient-groups"];
107
+ if (flags["scheduled-at"] !== void 0) body.scheduled_at = flags["scheduled-at"];
108
+ if (flags["cta-link"] !== void 0) body.cta_link = flags["cta-link"];
109
+ if (flags["cta-label"] !== void 0) body.cta_label = flags["cta-label"];
110
+ if (flags["send-immediately"] !== void 0) body.send_immediately_p = flags["send-immediately"] ? 1 : 0;
111
+ if (flags["include-live-info"] !== void 0) body.include_live_info_p = flags["include-live-info"] ? 1 : 0;
112
+ if (flags["include-series-archive"] !== void 0) body.include_series_archive_p = flags["include-series-archive"] ? 1 : 0;
70
113
  const { data, error } = await this.apiClient.POST("/live/mail/add", {
71
- body: {
72
- ...contextField,
73
- subject,
74
- message
75
- },
114
+ body,
76
115
  headers: { "Content-Type": "application/x-www-form-urlencoded" }
77
116
  });
78
117
  if (error) this.error(require_lib_term_map.applyCliTerms(require_lib_output.formatApiError(error)), { exit: 1 });
@@ -40,6 +40,42 @@ var WebinarMailUpdate = class WebinarMailUpdate extends require_lib_base_command
40
40
  message: _oclif_core.Flags.string({
41
41
  description: "Email message body",
42
42
  required: false
43
+ }),
44
+ enabled: _oclif_core.Flags.boolean({
45
+ description: "Enable or disable the email (e.g. --no-enabled to disable the \"missed\" mail)",
46
+ allowNo: true,
47
+ required: false
48
+ }),
49
+ "recipient-groups": _oclif_core.Flags.string({
50
+ description: "Recipient groups (comma-separated): speakers, registered, attendees, noshows",
51
+ required: false
52
+ }),
53
+ "scheduled-at": _oclif_core.Flags.string({
54
+ description: "When to send the email (ISO 8601 timestamp)",
55
+ required: false
56
+ }),
57
+ "cta-link": _oclif_core.Flags.string({
58
+ description: "Call-to-action link URL",
59
+ required: false
60
+ }),
61
+ "cta-label": _oclif_core.Flags.string({
62
+ description: "Call-to-action button label",
63
+ required: false
64
+ }),
65
+ "include-live-info": _oclif_core.Flags.boolean({
66
+ description: "Include the webinar info block in the email",
67
+ allowNo: true,
68
+ required: false
69
+ }),
70
+ "include-series-archive": _oclif_core.Flags.boolean({
71
+ description: "Include the series archive block in the email",
72
+ allowNo: true,
73
+ required: false
74
+ }),
75
+ "require-recording": _oclif_core.Flags.boolean({
76
+ description: "Only send once a recording is available",
77
+ allowNo: true,
78
+ required: false
43
79
  })
44
80
  };
45
81
  static args = { id: _oclif_core.Args.string({
@@ -63,6 +99,14 @@ var WebinarMailUpdate = class WebinarMailUpdate extends require_lib_base_command
63
99
  };
64
100
  if (flags.subject !== void 0) body.subject = flags.subject;
65
101
  if (flags.message !== void 0) body.message = flags.message;
102
+ if (flags.enabled !== void 0) body.enabled_p = flags.enabled ? 1 : 0;
103
+ if (flags["recipient-groups"] !== void 0) body.recipient_groups = flags["recipient-groups"];
104
+ if (flags["scheduled-at"] !== void 0) body.scheduled_at = flags["scheduled-at"];
105
+ if (flags["cta-link"] !== void 0) body.cta_link = flags["cta-link"];
106
+ if (flags["cta-label"] !== void 0) body.cta_label = flags["cta-label"];
107
+ if (flags["include-live-info"] !== void 0) body.include_live_info_p = flags["include-live-info"] ? 1 : 0;
108
+ if (flags["include-series-archive"] !== void 0) body.include_series_archive_p = flags["include-series-archive"] ? 1 : 0;
109
+ if (flags["require-recording"] !== void 0) body.require_recording_p = flags["require-recording"] ? 1 : 0;
66
110
  const { data, error } = await this.apiClient.POST("/live/mail/update", {
67
111
  body,
68
112
  headers: { "Content-Type": "application/x-www-form-urlencoded" }
@@ -38,8 +38,32 @@ var WebinarSpeakerAdd = class WebinarSpeakerAdd extends require_lib_base_command
38
38
  description: "Speaker title or job title",
39
39
  required: false
40
40
  }),
41
+ bio: _oclif_core.Flags.string({
42
+ description: "Speaker bio shown in the UI",
43
+ required: false
44
+ }),
41
45
  description: _oclif_core.Flags.string({
42
- description: "Speaker bio or description",
46
+ description: "Alias for --bio (sets the speaker bio shown in the UI)",
47
+ required: false
48
+ }),
49
+ company: _oclif_core.Flags.string({
50
+ description: "Speaker company / organization",
51
+ required: false
52
+ }),
53
+ website: _oclif_core.Flags.string({
54
+ description: "Speaker website URL",
55
+ required: false
56
+ }),
57
+ linkedin: _oclif_core.Flags.string({
58
+ description: "Speaker LinkedIn URL or handle",
59
+ required: false
60
+ }),
61
+ facebook: _oclif_core.Flags.string({
62
+ description: "Speaker Facebook URL or handle",
63
+ required: false
64
+ }),
65
+ twitter: _oclif_core.Flags.string({
66
+ description: "Speaker Twitter/X handle",
43
67
  required: false
44
68
  }),
45
69
  "connection-type": _oclif_core.Flags.string({
@@ -86,7 +110,13 @@ var WebinarSpeakerAdd = class WebinarSpeakerAdd extends require_lib_base_command
86
110
  };
87
111
  if (email !== void 0) body.email = email;
88
112
  if (flags.title !== void 0) body.title = flags.title;
89
- if (flags.description !== void 0) body.description = flags.description;
113
+ const bio = flags.bio ?? flags.description;
114
+ if (bio !== void 0) body.bio = bio;
115
+ if (flags.company !== void 0) body.company = flags.company;
116
+ if (flags.website !== void 0) body.link = flags.website;
117
+ if (flags.linkedin !== void 0) body.linkedin = flags.linkedin;
118
+ if (flags.facebook !== void 0) body.facebook = flags.facebook;
119
+ if (flags.twitter !== void 0) body.twitter = flags.twitter;
90
120
  if (flags["connection-type"] !== void 0) body.connection_type = flags["connection-type"];
91
121
  if (flags["connection-type-pull-url"] !== void 0) body.connection_type_pull_url = flags["connection-type-pull-url"];
92
122
  const { data, error } = await this.apiClient.POST("/live/speaker/add", {