@erdoai/cli 0.131.0 → 0.133.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 (3) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +154 -34
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -111,7 +111,7 @@ erdo datasets upload ./leads.csv # create a dataset from a file
111
111
 
112
112
  ```bash
113
113
  erdo agent ask "what was revenue last week?" --datasets sales
114
- erdo agent thread --name "landing build" # -> thread id
114
+ erdo threads create --name "landing build" # -> thread id
115
115
  erdo agent send <thread> "Build a landing page for ACME ..." --agent erdo.artifact-builder
116
116
  erdo agent send <thread> "What should I do next?" --context "Current screen: checkout experiment"
117
117
 
package/dist/index.js CHANGED
@@ -1071,6 +1071,34 @@ var ErdoClient = class {
1071
1071
  input
1072
1072
  );
1073
1073
  }
1074
+ listPersonas() {
1075
+ return this.request(
1076
+ "GET",
1077
+ "/v1/personas"
1078
+ );
1079
+ }
1080
+ openDirectConversation(personaID) {
1081
+ return this.request(
1082
+ "POST",
1083
+ "/v1/threads-direct",
1084
+ { persona_id: personaID }
1085
+ );
1086
+ }
1087
+ markThreadSeen(threadID) {
1088
+ return this.request("POST", `/v1/threads/${encodeURIComponent(threadID)}/seen`, {});
1089
+ }
1090
+ listSeenThreads() {
1091
+ return this.request("GET", "/v1/threads-seen");
1092
+ }
1093
+ getThread(threadID) {
1094
+ return this.request("GET", `/v1/threads/${encodeURIComponent(threadID)}`);
1095
+ }
1096
+ addThreadParticipant(threadID, participant) {
1097
+ return this.request("POST", `/v1/threads/${encodeURIComponent(threadID)}/participants`, participant);
1098
+ }
1099
+ removeThreadParticipant(threadID, participantID) {
1100
+ return this.request("DELETE", `/v1/threads/${encodeURIComponent(threadID)}/participants/${encodeURIComponent(participantID)}`);
1101
+ }
1074
1102
  async deleteThread(threadID) {
1075
1103
  try {
1076
1104
  await this.request("DELETE", `/v1/threads/${encodeURIComponent(threadID)}`);
@@ -4147,50 +4175,139 @@ thread: ${response.thread_id}`);
4147
4175
  fail(e);
4148
4176
  }
4149
4177
  });
4150
- agentCmd.command("thread").description("Create a thread; prints its id").option("-n, --name <name>", "thread name").option("-d, --datasets <csv>", "dataset UUIDs to attach").option("--hidden", "keep the thread out of the organization's thread list (for diagnostic or scripted threads)").action(async (opts) => {
4178
+ agentCmd.command("delete").description("Permanently delete a thread and its messages (needs admin on the thread: you created it, or you are an org admin)").argument("<threadId>", "thread UUID").action(async (threadId) => {
4179
+ try {
4180
+ await new ErdoClient().deleteThread(threadId);
4181
+ console.log(`deleted ${threadId}`);
4182
+ } catch (e) {
4183
+ fail(e);
4184
+ }
4185
+ });
4186
+ async function resolveConversationPersona(client, selector) {
4187
+ const { personas } = await client.listPersonas();
4188
+ const needle = selector.trim().toLocaleLowerCase();
4189
+ const matches = personas.filter(
4190
+ (persona) => [persona.id, persona.key, persona.human_name, persona.name].some((value) => value?.trim().toLocaleLowerCase() === needle)
4191
+ );
4192
+ if (matches.length === 1) return matches[0];
4193
+ if (matches.length > 1) throw new Error(`Agent "${selector}" is ambiguous; use its persona key or ID`);
4194
+ throw new Error(`No enabled agent matches "${selector}"`);
4195
+ }
4196
+ var conversationsCmd = program.command("threads").description("List and open conversations with people and named agents");
4197
+ conversationsCmd.command("list").description("List conversations with the latest message, speaker, unread status and open questions").option("-s, --scope <scope>", "own (default) | org | shared | all \u2014 org/shared require org membership").option("--order-by <order>", "created_at (default) | activity (last message time) \u2014 scope=org/shared only").option("--include-automation", "include automation/heartbeat-sourced threads \u2014 scope=org/shared only").option("-l, --limit <n>", "max conversations (default 20, max 100)", (v) => parseInt(v, 10)).option("--offset <n>", "pagination offset", (v) => parseInt(v, 10)).action(async (opts) => {
4198
+ try {
4199
+ const { threads, total } = await new ErdoClient().listThreads(opts);
4200
+ for (const thread of threads) {
4201
+ const preview = (thread.last_message_preview || "").replace(/[\t\r\n]+/g, " ").slice(0, 120);
4202
+ const speaker = thread.last_message_author_display_name || thread.last_message_author_entity_type || "";
4203
+ const creator = thread.created_by_user_name || thread.created_by_user_id;
4204
+ console.log(`${thread.id} ${thread.name || ""} ${creator} ${thread.source || "unrecorded"} ${thread.unread ? "unread" : "read"} ${speaker} ${preview} questions=${thread.open_question_count ?? 0}`);
4205
+ }
4206
+ const shown = (opts.offset ?? 0) + threads.length;
4207
+ console.error(total > shown ? `(showing ${threads.length} of ${total} \u2014 use --limit/--offset for more)` : `(${total} conversation${total === 1 ? "" : "s"})`);
4208
+ } catch (e) {
4209
+ fail(e);
4210
+ }
4211
+ });
4212
+ conversationsCmd.command("open <agent>").description("Open or create your direct conversation with an enabled agent").action(async (agent) => {
4213
+ try {
4214
+ const client = new ErdoClient();
4215
+ const persona = await resolveConversationPersona(client, agent);
4216
+ const thread = await client.openDirectConversation(persona.id);
4217
+ console.log(thread.id);
4218
+ } catch (e) {
4219
+ fail(e);
4220
+ }
4221
+ });
4222
+ conversationsCmd.command("create").description("Create a conversation, optionally bound to an enabled agent").option("--agent <agent>", "agent name, persona key or ID").option("-n, --name <name>", "conversation name").option("-d, --datasets <csv>", "dataset UUIDs to attach").option("--hidden", "keep the conversation out of the organization's conversation list").option("--parent <threadId>", "parent conversation this internal conversation supports").option("--internal", "create an internal conversation hidden until someone follows it").action(async (opts) => {
4151
4223
  try {
4152
- const res = await new ErdoClient().createThread({
4224
+ if (opts.internal && !opts.parent) throw new Error("--internal requires --parent <thread-id>");
4225
+ const client = new ErdoClient();
4226
+ const persona = opts.agent ? await resolveConversationPersona(client, opts.agent) : void 0;
4227
+ const thread = await client.createThread({
4153
4228
  name: opts.name,
4154
- dataset_ids: opts.datasets ? opts.datasets.split(",").map((s) => s.trim()) : void 0,
4155
- visibility: opts.hidden ? "hidden" : void 0
4229
+ dataset_ids: opts.datasets ? opts.datasets.split(",").map((s) => s.trim()).filter(Boolean) : void 0,
4230
+ visibility: opts.hidden ? "hidden" : void 0,
4231
+ persona_id: persona?.id,
4232
+ parent_thread_id: opts.parent,
4233
+ listing: opts.internal ? "internal" : void 0
4156
4234
  });
4157
- console.log(res.id);
4235
+ console.log(thread.id);
4158
4236
  } catch (e) {
4159
4237
  fail(e);
4160
4238
  }
4161
4239
  });
4162
- agentCmd.command("delete").description("Permanently delete a thread and its messages (needs admin on the thread: you created it, or you are an org admin)").argument("<threadId>", "thread UUID").action(async (threadId) => {
4240
+ conversationsCmd.command("follow <threadId>").description("Follow an internal conversation so it appears in your conversation list").action(async (threadID) => {
4163
4241
  try {
4164
- await new ErdoClient().deleteThread(threadId);
4165
- console.log(`deleted ${threadId}`);
4242
+ const client = new ErdoClient();
4243
+ const user = await client.me();
4244
+ await client.addThreadParticipant(threadID, { user_id: user.user_id });
4245
+ console.log(`following ${threadID}`);
4166
4246
  } catch (e) {
4167
4247
  fail(e);
4168
4248
  }
4169
4249
  });
4170
- agentCmd.command("threads").description("List threads (default: your own; --scope org/shared for organization-wide)").option("-s, --scope <scope>", "own (default) | org | shared \u2014 org/shared require org membership").option(
4171
- "--order-by <order>",
4172
- "created_at (default) | activity (last message time) \u2014 scope=org/shared only"
4173
- ).option(
4174
- "--include-automation",
4175
- "include automation/heartbeat-sourced threads, excluded by default \u2014 scope=org/shared only"
4176
- ).option("-l, --limit <n>", "max threads (default 20, max 100)", (v) => parseInt(v, 10)).option("--offset <n>", "pagination offset", (v) => parseInt(v, 10)).action(
4177
- async (opts) => {
4178
- try {
4179
- const { threads, total } = await new ErdoClient().listThreads(opts);
4180
- for (const t of threads) {
4181
- console.log(`${t.id} ${t.name} ${t.created_by_user_name || t.created_by_user_id} ${t.source || "unrecorded"}`);
4182
- }
4183
- const shown = (opts.offset ?? 0) + threads.length;
4184
- if (total > shown) {
4185
- console.error(`(showing ${threads.length} of ${total} \u2014 use --limit/--offset for more)`);
4186
- } else {
4187
- console.error(`(${total} thread${total === 1 ? "" : "s"})`);
4188
- }
4189
- } catch (e) {
4190
- fail(e);
4191
- }
4250
+ conversationsCmd.command("unfollow <threadId>").description("Unfollow an internal conversation").action(async (threadID) => {
4251
+ try {
4252
+ const client = new ErdoClient();
4253
+ const user = await client.me();
4254
+ await client.removeThreadParticipant(threadID, user.user_id);
4255
+ console.log(`unfollowed ${threadID}`);
4256
+ } catch (e) {
4257
+ fail(e);
4192
4258
  }
4193
- );
4259
+ });
4260
+ conversationsCmd.command("seen <threadId>").description("Mark a conversation as seen").action(async (threadID) => {
4261
+ try {
4262
+ await new ErdoClient().markThreadSeen(threadID);
4263
+ console.log(`seen ${threadID}`);
4264
+ } catch (e) {
4265
+ fail(e);
4266
+ }
4267
+ });
4268
+ conversationsCmd.command("seen-list").description("List conversations you have marked as seen").action(async () => {
4269
+ try {
4270
+ print(await new ErdoClient().listSeenThreads());
4271
+ } catch (e) {
4272
+ fail(e);
4273
+ }
4274
+ });
4275
+ conversationsCmd.command("agents").description("List enabled agents and their persona IDs").action(async () => {
4276
+ try {
4277
+ const { personas } = await new ErdoClient().listPersonas();
4278
+ for (const persona of personas) console.log(`${persona.id} ${persona.key} ${persona.human_name || persona.name}`);
4279
+ } catch (e) {
4280
+ fail(e);
4281
+ }
4282
+ });
4283
+ conversationsCmd.command("get <threadId>").description("Show a conversation and its participants").action(async (threadID) => {
4284
+ try {
4285
+ print(await new ErdoClient().getThread(threadID));
4286
+ } catch (e) {
4287
+ fail(e);
4288
+ }
4289
+ });
4290
+ var conversationParticipantsCmd = conversationsCmd.command("participants").description("Manage named-agent participants in a conversation");
4291
+ conversationParticipantsCmd.command("add <threadId> <agent>").description("Add an enabled agent to a conversation").action(async (threadID, agent) => {
4292
+ try {
4293
+ const client = new ErdoClient();
4294
+ const persona = await resolveConversationPersona(client, agent);
4295
+ await client.addThreadParticipant(threadID, { persona_id: persona.id });
4296
+ console.log(`added ${persona.human_name || persona.name} to ${threadID}`);
4297
+ } catch (e) {
4298
+ fail(e);
4299
+ }
4300
+ });
4301
+ conversationParticipantsCmd.command("remove <threadId> <agent>").description("Remove an enabled agent from a conversation").action(async (threadID, agent) => {
4302
+ try {
4303
+ const client = new ErdoClient();
4304
+ const persona = await resolveConversationPersona(client, agent);
4305
+ await client.removeThreadParticipant(threadID, persona.id);
4306
+ console.log(`removed ${persona.human_name || persona.name} from ${threadID}`);
4307
+ } catch (e) {
4308
+ fail(e);
4309
+ }
4310
+ });
4194
4311
  function contentBlockLabel(c) {
4195
4312
  const type = c.ui_content_type || c.content_type;
4196
4313
  const payload = c.content;
@@ -8580,7 +8697,7 @@ var autoCmd = program.command("automations").description("Scheduled automations
8580
8697
  autoCmd.command("list").description("List automations").action(async () => {
8581
8698
  try {
8582
8699
  const { heartbeats } = await new ErdoClient().listHeartbeats();
8583
- for (const h of heartbeats) console.log(`${h.id} ${h.state} ${h.name}`);
8700
+ for (const h of heartbeats) console.log(`${h.id} ${h.state} ${h.name} ${h.kind === "script" ? "script" : h.thread_id ? `thread:${h.thread_id}` : h.conversation ?? "new"}${h.persona_id ? ` persona:${h.persona_id}` : ""}`);
8584
8701
  } catch (e) {
8585
8702
  fail(e);
8586
8703
  }
@@ -8606,7 +8723,7 @@ autoCmd.command("run <id>").description("Trigger an automation now").action(asyn
8606
8723
  autoCmd.command("update <id>").description("Edit an automation \u2014 rename, reschedule, or replace its script/instructions").option("--name <name>", "New name").option("--description <text>", "New description").option("--instructions <text>", "Replacement instructions (agent automation)").addOption(new Option2("--agent-key <key>", "Executor for an agent automation; omit to keep current").choices(["erdo.heartbeat-executor", "erdo.workflow-executor"])).option("--script-js <js>", "Replacement script body (scripted automation)").option(
8607
8724
  "--script-file <path>",
8608
8725
  "Read the replacement script body from a file (scripted automation)"
8609
- ).option("--interval <minutes>", "New interval in minutes (minimum 5)", (v) => parseInt(v, 10)).option("--timezone <tz>", "New scheduling timezone, e.g. America/New_York").action(
8726
+ ).addOption(new Option2("--conversation <target>", "Target 'new' (fresh thread) or 'direct' conversation").choices(["new", "direct"])).option("--thread-id <id>", "Post each run into an existing conversation UUID").option("--persona-id <id>", "Org-enabled persona UUID to speak as").option("--interval <minutes>", "New interval in minutes (minimum 5)", (v) => parseInt(v, 10)).option("--timezone <tz>", "New scheduling timezone, e.g. America/New_York").action(
8610
8727
  async (id, opts) => {
8611
8728
  try {
8612
8729
  if (opts.scriptJs && opts.scriptFile) {
@@ -8621,7 +8738,10 @@ autoCmd.command("update <id>").description("Edit an automation \u2014 rename, re
8621
8738
  agent_key: opts.agentKey,
8622
8739
  script_js: scriptJs,
8623
8740
  interval_minutes: opts.interval,
8624
- timezone: opts.timezone
8741
+ timezone: opts.timezone,
8742
+ conversation: opts.conversation,
8743
+ thread_id: opts.threadId,
8744
+ persona_id: opts.personaId
8625
8745
  };
8626
8746
  if (Object.values(body).every((v) => v === void 0)) {
8627
8747
  fail(new Error("nothing to update \u2014 pass at least one field to change"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@erdoai/cli",
3
- "version": "0.131.0",
3
+ "version": "0.133.0",
4
4
  "description": "Erdo CLI — drive datasets, pages, and evals from the terminal or CI",
5
5
  "type": "module",
6
6
  "bin": {