@workser/cli 0.6.28 → 0.6.30

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
@@ -3810,6 +3810,12 @@ sending it. That matters: the API silently drops unknown fields, so a typo
3810
3810
  would otherwise be accepted, dropped, and reported as success \u2014 leaving an
3811
3811
  agent that had been told nothing.
3812
3812
 
3813
+ ## Nothing starts it until you give it a trigger
3814
+
3815
+ A published agent runs when asked and at no other time. A time, a chat message
3816
+ or an app event is a **trigger**, and it works the moment it is saved \u2014 it is
3817
+ not part of the agent's version. \`workser help agent-triggers\`.
3818
+
3813
3819
  ## Nothing takes effect until you publish
3814
3820
 
3815
3821
  **This is the step to not forget.** The runtime resolves the PUBLISHED version
@@ -3955,15 +3961,85 @@ watching sees the agent think. See the \`workser-sdk\` skill, \`reference/agents
3955
3961
  single biggest cause of an agent that "doesn't work".
3956
3962
 
3957
3963
  3. **Every plan can run agents once the shared wallet has enough credits.** A
3958
- \`402\` with \`spend_limit_reached\` means the wallet needs a top-up (or the owner
3959
- reached their own spend cap). Tell them exactly what it says and hand over
3960
- the credit action; never suggest a subscription upgrade for this refusal.
3964
+ \`402\` with \`insufficient_credits\` needs a top-up; \`spend_limit_reached\` means
3965
+ the owner reached the guard rail they set. Tell them exactly what it says and
3966
+ hand over the matching action; never suggest a subscription upgrade.
3961
3967
 
3962
3968
  4. **Say who it is for.** An agent acting for one of the app's customers needs
3963
3969
  \`referenceUserId\`, or its memory and audit trail belong to nobody.
3964
3970
 
3965
3971
  5. **Do not invent an agent the user did not ask for.** Creating one is cheap;
3966
3972
  an agent nobody wanted, quietly costing money per run, is not.
3973
+ `
3974
+ },
3975
+ {
3976
+ topic: "agent-triggers",
3977
+ title: "What starts an Agent Cloud agent",
3978
+ summary: "Schedules, chat channels and app events \u2014 a trigger works the moment it is saved, unlike everything else on an agent.",
3979
+ commands: [],
3980
+ source: "skills/workser/reference/agent-triggers.md",
3981
+ body: `# What starts an Agent Cloud agent
3982
+
3983
+ A published agent runs when somebody asks it to and at no other time. A trigger
3984
+ is what makes it start on its own \u2014 a time, a chat message, or an event in a
3985
+ connected app.
3986
+
3987
+ \`\`\`
3988
+ workser agent-cloud triggers <id> # what starts it today
3989
+ workser agent-cloud trigger-add <id> schedule cron="0 9 * * 1-5" name="Morning report"
3990
+ workser agent-cloud trigger-add <id> chat app_type=line
3991
+ workser agent-cloud trigger-add <id> app_event event_type=GMAIL_NEW_GMAIL_MESSAGE connected_account_id=<id>
3992
+ workser agent-cloud trigger-setup <id> <triggerId> # the webhook URL to paste
3993
+ workser agent-cloud trigger-events <id> # what fired, and what it started
3994
+ workser agent-cloud trigger-remove <id> <triggerId>
3995
+ \`\`\`
3996
+
3997
+ **A trigger works the moment it is saved** \u2014 unlike everything above, it is not
3998
+ part of the agent's version and does not wait for a publish.
3999
+
4000
+ \`rule=\` is optional and is the FIRING rule, not the agent's instructions:
4001
+ \`rule="only when the message mentions an order"\`. Leave it empty on a chat
4002
+ channel and every message reaches the agent, which is what a support agent
4003
+ wants \u2014 the agent decides what to do with each one.
4004
+
4005
+ For a chat trigger, **\`trigger-setup\` is not optional**: it returns the webhook
4006
+ URL to paste into LINE's or Slack's console, and it says whether anything has
4007
+ arrived yet. Without that step the trigger sits there looking finished and
4008
+ never receives anything.
4009
+
4010
+ To let the agent ANSWER on that channel, the project also needs the account
4011
+ connected \u2014 \`workser line connect --token \u2026\`. See \`workser help chat-channels\`.
4012
+
4013
+ ## The two halves of a chat agent
4014
+
4015
+ Receiving and answering are separate, and each has its own setup:
4016
+
4017
+ | | What it does | How |
4018
+ | --- | --- | --- |
4019
+ | The trigger | a message STARTS the agent | \`trigger-add <id> chat app_type=line\`, then \`trigger-setup\` |
4020
+ | The connection | the agent can ANSWER | \`workser line connect --token \u2026\` |
4021
+
4022
+ Do only the first and the agent listens and never speaks. Do only the second and
4023
+ nothing ever wakes it up.
4024
+
4025
+ With both in place, a run started by a message is handed the reply token and the
4026
+ sender, and gets every operation of that account as a tool \u2014 \`line_reply\`,
4027
+ \`slack_send_message\` and the rest. Sends go through the agent's approval gate;
4028
+ reads do not.
4029
+
4030
+ ## Schedules, in plain words
4031
+
4032
+ \`cron=\` takes a standard five-field expression. The four people ask for:
4033
+
4034
+ \`\`\`
4035
+ cron="0 9 * * 1-5" every weekday at 9am
4036
+ cron="0 9 * * *" every day at 9am
4037
+ cron="0 * * * *" every hour
4038
+ cron="0 9 * * 1" every Monday at 9am
4039
+ \`\`\`
4040
+
4041
+ \`timezone=\` defaults to UTC, which is almost never what somebody means by "9am".
4042
+ Set it: \`timezone="Asia/Bangkok"\`.
3967
4043
  `
3968
4044
  },
3969
4045
  {
@@ -4381,6 +4457,113 @@ copy is the current one.
4381
4457
  \`workser business\` is how **you** inspect and fix data while building. The app reads
4382
4458
  the same records at runtime through \`workser.business\` in \`@workser/app\` \u2014 see the
4383
4459
  \`workser-sdk\` skill. An app shelling out to this CLI per request is wrong.
4460
+ `
4461
+ },
4462
+ {
4463
+ topic: "chat-channels",
4464
+ title: "LINE, Telegram, Discord, Slack",
4465
+ summary: "Connect a chat account once, then send, reply and read from the CLI or from an agent \u2014 LINE gets its whole Messaging API.",
4466
+ commands: ["line", "telegram", "discord", "slack"],
4467
+ source: "skills/workser/reference/chat-channels.md",
4468
+ body: `# LINE, Telegram, Discord, Slack
4469
+
4470
+ Connect the business's chat account to this project **once**. After that it is
4471
+ reachable from here, from Workser Code, and from any Agent Cloud agent in this
4472
+ project \u2014 including the one a chat message starts.
4473
+
4474
+ Every provider takes the same six verbs:
4475
+
4476
+ \`\`\`
4477
+ workser <provider> status # is anything connected?
4478
+ workser <provider> connect --token <token>
4479
+ workser <provider> verify # does the credential still work?
4480
+ workser <provider> disconnect
4481
+ workser <provider> ops [--group <name>] # everything it can do
4482
+ workser <provider> call <operation> --params '<json>'
4483
+ \`\`\`
4484
+
4485
+ where \`<provider>\` is \`line\`, \`telegram\`, \`discord\` or \`slack\`.
4486
+
4487
+ ## Getting the token
4488
+
4489
+ | Provider | Where | Flag |
4490
+ | --- | --- | --- |
4491
+ | LINE | Developers console \u2192 Messaging API \u2192 Channel access token | \`--token\`, plus \`--secret\` for the channel secret |
4492
+ | Telegram | @BotFather \u2192 \`/newbot\` or \`/token\` | \`--token\` |
4493
+ | Discord | Developer Portal \u2192 your app \u2192 Bot \u2192 Reset Token | \`--token\` (the bot must also be invited to the server) |
4494
+ | Slack | Your app \u2192 OAuth & Permissions \u2192 Bot User OAuth Token (\`xoxb-\`) | \`--token\`, plus \`--signing-secret\` |
4495
+
4496
+ \`connect\` calls the provider immediately to prove the credential works, so a
4497
+ mistyped token fails right there instead of an hour later as a customer message
4498
+ nobody answered.
4499
+
4500
+ ## \`call\` is the whole API
4501
+
4502
+ \`ops\` prints every operation with a one-line summary. The \`\u25CF\` marks the ones
4503
+ that send something, spend quota, or change the account.
4504
+
4505
+ \`\`\`
4506
+ workser line call push --params '{"to":"U4af\u2026","messages":[{"type":"text","text":"Your order shipped"}]}'
4507
+ workser line call richmenu_list
4508
+ workser telegram call send_chat_action --params '{"chat_id":123,"action":"typing"}'
4509
+ workser slack call list_conversations --params '{"types":"public_channel"}'
4510
+ workser discord call create_thread --params '{"channel_id":"\u2026","message_id":"\u2026","name":"Order #4021"}'
4511
+ \`\`\`
4512
+
4513
+ Parameters are flat \u2014 path, query and body fields all go in \`--params\` together
4514
+ and the server sorts them. Fields it has never heard of are **forwarded
4515
+ untouched**, so a flex component or a Slack block the vendor shipped last week
4516
+ works today.
4517
+
4518
+ ## Shortcuts
4519
+
4520
+ \`\`\`
4521
+ workser line send <to> "<text>" | reply <replyToken> "<text>" | broadcast "<text>"
4522
+ workser line quota | profile <userId>
4523
+ workser telegram send <chatId> "<text>"
4524
+ workser discord send <channelId> "<text>"
4525
+ workser slack send <channel> "<text>"
4526
+ \`\`\`
4527
+
4528
+ ## Reply beats push, on LINE
4529
+
4530
+ \`reply\` uses the token that came with the incoming message. It is **free** and
4531
+ does not touch the monthly quota; \`push\` costs one message per recipient. The
4532
+ token is single-use and expires within a minute or so, so reply first and fall
4533
+ back to push.
4534
+
4535
+ \`broadcast\` sends to every follower and spends one message each. On a large
4536
+ account that is the most expensive call available \u2014 say what it will cost
4537
+ before running it.
4538
+
4539
+ ## How complete each one is
4540
+
4541
+ **LINE is the deep one** \u2014 its entire Messaging API, about seventy operations:
4542
+ messaging, rich menus, audiences and narrowcast, insight, quota, content,
4543
+ groups and rooms, and the webhook endpoint itself.
4544
+
4545
+ **Telegram, Discord and Slack carry what a chat agent needs** \u2014 send, reply,
4546
+ edit, delete, react, typing indicator, threads and DMs, plus reading people and
4547
+ conversations. Not their whole APIs. \`ops\` is the truth; ask it rather than
4548
+ assuming an operation exists.
4549
+
4550
+ ## Rich menus, in order (LINE)
4551
+
4552
+ A rich menu does nothing until it has both a picture and a place to appear:
4553
+
4554
+ 1. \`richmenu_create\` \u2014 the layout and tappable areas. Returns an id.
4555
+ 2. \`richmenu_upload_image\` \u2014 \`content\` is the file **base64-encoded**, with
4556
+ \`content_type\`. The image must match the declared size exactly.
4557
+ 3. \`richmenu_set_default\` (everybody) or \`richmenu_link_user\` (one person).
4558
+
4559
+ \`richmenu_validate\` checks a layout without creating it.
4560
+
4561
+ ## When it is an agent doing this
4562
+
4563
+ An Agent Cloud agent in a project with a connected account gets these as tools
4564
+ automatically \u2014 there is nothing to bind, and no capability to switch on. Sends
4565
+ go through the agent's approval gate; reads do not. See
4566
+ \`workser help agent-cloud\`.
4384
4567
  `
4385
4568
  },
4386
4569
  {
@@ -5177,87 +5360,6 @@ deployment keeps using whatever it was built with until it's redeployed \u2014
5177
5360
  run \`workser deploy\` (\`--prod\` for the production key) to pick up the new
5178
5361
  value. \`key rotate\` prints the new secret exactly once; it is never shown
5179
5362
  again or stored anywhere in the clear.
5180
- `
5181
- },
5182
- {
5183
- topic: "line",
5184
- title: "LINE Official Account",
5185
- summary: "Connect a LINE OA once, then send, reply, broadcast, build rich menus and read insight \u2014 from the CLI or from an agent.",
5186
- commands: ["line"],
5187
- source: "skills/workser/reference/line.md",
5188
- body: `# LINE Official Account
5189
-
5190
- Connect the business's LINE account to this project **once**. After that the same
5191
- account is reachable from here, from Workser Code, and from any Agent Cloud agent
5192
- in this project \u2014 including the one a LINE message starts.
5193
-
5194
- \`\`\`
5195
- workser line status # is anything connected?
5196
- workser line connect --token <channelAccessToken> [--secret <channelSecret>]
5197
- workser line verify # ask LINE if the token still works
5198
- workser line disconnect
5199
-
5200
- workser line ops [--group messaging|richmenu|audience|insight|people|group|quota|content|account]
5201
- workser line call <operation> --params '<json>' # any of the 70 operations
5202
-
5203
- workser line send <to> "<text>" # push to a user, group or room id
5204
- workser line reply <replyToken> "<text>" # answer a message \u2014 free, single-use
5205
- workser line broadcast "<text>" # EVERY follower, one message each
5206
- workser line quota # allowance left this month
5207
- workser line profile <userId>
5208
- \`\`\`
5209
-
5210
- ## Getting the token
5211
-
5212
- LINE Developers console \u2192 your Messaging API channel \u2192 **Messaging API** tab \u2192
5213
- *Channel access token* (long-lived). \`--secret\` is the **Basic settings** \u2192
5214
- *Channel secret*, and is only needed if you want LINE's own signature check on
5215
- incoming webhooks.
5216
-
5217
- \`connect\` calls LINE immediately to prove the token works, so a mistyped token
5218
- fails here rather than an hour later as a customer message nobody answered.
5219
-
5220
- ## \`call\` is the whole API
5221
-
5222
- \`ops\` prints every operation with a one-line summary. The \`\u25CF\` marks the ones that
5223
- send something, spend quota, or change the account.
5224
-
5225
- \`\`\`
5226
- workser line call push --params '{"to":"U4af\u2026","messages":[{"type":"text","text":"Your order shipped"}]}'
5227
- workser line call richmenu_list
5228
- workser line call insight_followers --params '{"date":"20260909"}'
5229
- workser line call webhook_endpoint_set --params '{"endpoint":"https://\u2026"}'
5230
- \`\`\`
5231
-
5232
- Parameters are flat \u2014 path, query and body fields all go in \`--params\` together and
5233
- the server sorts them. Fields it has never heard of are **forwarded to LINE
5234
- untouched**, so a flex component LINE shipped last week works today.
5235
-
5236
- ## Reply beats push
5237
-
5238
- \`reply\` uses the token that came with the incoming message. It is **free** and does
5239
- not touch the monthly quota; \`push\` costs one message per recipient. The token is
5240
- single-use and expires within a minute or so, so reply first and fall back to push.
5241
-
5242
- \`broadcast\` sends to every follower and spends one message each. On a large account
5243
- that is the most expensive call in the list \u2014 say what it will cost before running it.
5244
-
5245
- ## Rich menus, in order
5246
-
5247
- A rich menu does nothing until it has both a picture and a place to appear:
5248
-
5249
- 1. \`richmenu_create\` \u2014 the layout and tappable areas. Returns an id.
5250
- 2. \`richmenu_upload_image\` \u2014 \`content\` is the file **base64-encoded**, with
5251
- \`content_type\`. The image must match the declared size exactly.
5252
- 3. \`richmenu_set_default\` (everybody) or \`richmenu_link_user\` (one person).
5253
-
5254
- \`richmenu_validate\` checks a layout without creating it.
5255
-
5256
- ## When it is an agent doing this
5257
-
5258
- An Agent Cloud agent in a project with a connected LINE account gets these as tools
5259
- automatically \u2014 there is nothing to bind. Sends go through the agent's approval gate;
5260
- reads do not. See \`workser help agent-cloud\`.
5261
5363
  `
5262
5364
  },
5263
5365
  {
@@ -5405,7 +5507,7 @@ Two caveats worth saying out loud rather than letting the owner find later:
5405
5507
  topic: "roles",
5406
5508
  title: "Delegate to roles",
5407
5509
  summary: "Hand a focused subtask to another configured local agent.",
5408
- commands: ["agent"],
5510
+ commands: ["agent", "team"],
5409
5511
  source: "skills/workser/reference/roles.md",
5410
5512
  body: `# Delegate to roles
5411
5513
 
@@ -5420,6 +5522,9 @@ workser agent spawn <agent> "<task>" [--role <label>] [--instructions <text>] [-
5420
5522
  workser agent main # show the configured main agent
5421
5523
  \`\`\`
5422
5524
 
5525
+ \`workser team\` is the same command. The Team screen and your own briefing both
5526
+ call these people a team, so both words work here.
5527
+
5423
5528
  ## How to use it
5424
5529
 
5425
5530
  Run \`workser agent list --json\` first \u2014 it tells you which roles exist, which are
@@ -8009,7 +8114,7 @@ Pick another model, or leave ${flag} off to use the agent's own default.`,
8009
8114
  // src/commands/agent.ts
8010
8115
  var SPAWNABLE_AGENTS = ["claude_code", "codex", "kimi", "opencode", "grok"];
8011
8116
  function registerAgent(program3) {
8012
- const agent = program3.command("agent").description("Delegate focused subtasks to your configured agent roles (each runs isolated)");
8117
+ const agent = program3.command("agent").alias("team").description("Delegate focused subtasks to your configured agent roles (each runs isolated)");
8013
8118
  agent.command("list").description("List the main agent (+ backup) and the configured subagents").action(
8014
8119
  action(async ({ ctx }) => {
8015
8120
  const cfg = await api(ctx, "/v1/agents");
@@ -8372,6 +8477,118 @@ function registerAgentCloud(program3) {
8372
8477
  });
8373
8478
  })
8374
8479
  );
8480
+ cloud.command("triggers <agentId>").description("What starts this agent \u2014 schedules, chat channels, app events").action(
8481
+ action(async ({ ctx, args }) => {
8482
+ const res = await api(
8483
+ ctx,
8484
+ `/v1/agent-cloud/${encodeURIComponent(args[0])}/triggers`
8485
+ );
8486
+ const items = res?.triggers ?? [];
8487
+ ok(res, () => {
8488
+ if (!items.length) {
8489
+ return line(
8490
+ import_picocolors17.default.dim("Nothing starts this agent yet \u2014 it only runs when asked.")
8491
+ );
8492
+ }
8493
+ for (const t of items) {
8494
+ const state = t.status === "active" ? import_picocolors17.default.green("on") : import_picocolors17.default.yellow(t.status);
8495
+ const when = t.schedule_config?.cron_expression ?? t.app_event_config?.event_source ?? t.app_event_config?.event_type ?? "";
8496
+ line(
8497
+ `${import_picocolors17.default.bold(t.name ?? t.trigger_type)} ${state} ${import_picocolors17.default.dim(when)}`
8498
+ );
8499
+ line(" " + import_picocolors17.default.dim(t.id));
8500
+ }
8501
+ });
8502
+ })
8503
+ );
8504
+ cloud.command("trigger-add <agentId> <kind> [pairs...]").description(
8505
+ "Start an agent on a schedule, a chat channel or an app event. kind is schedule | chat | app_event. e.g. trigger-add <id> chat app_type=line"
8506
+ ).action(
8507
+ action(async ({ ctx, args }) => {
8508
+ const kind = String(args[1]);
8509
+ const pairs = parsePairs(
8510
+ args[2] ?? [],
8511
+ [
8512
+ "name",
8513
+ "rule",
8514
+ "cron",
8515
+ "timezone",
8516
+ "app_type",
8517
+ "event_type",
8518
+ "connected_account_id"
8519
+ ]
8520
+ );
8521
+ const body = buildTriggerBody(kind, pairs);
8522
+ const res = await api(
8523
+ ctx,
8524
+ `/v1/agent-cloud/${encodeURIComponent(args[0])}/triggers`,
8525
+ { method: "POST", body }
8526
+ );
8527
+ ok(res, () => {
8528
+ line(import_picocolors17.default.green("Added.") + " " + import_picocolors17.default.dim(res?.trigger?.id ?? ""));
8529
+ if (kind === "chat") {
8530
+ line(
8531
+ import_picocolors17.default.dim("Now get the webhook URL to paste into the platform: ") + import_picocolors17.default.bold(
8532
+ `workser agent-cloud trigger-setup ${args[0]} ${res?.trigger?.id ?? "<triggerId>"}`
8533
+ )
8534
+ );
8535
+ }
8536
+ });
8537
+ })
8538
+ );
8539
+ cloud.command("trigger-setup <agentId> <triggerId>").description("The webhook URL and console steps for a chat trigger").action(
8540
+ action(async ({ ctx, args }) => {
8541
+ const res = await api(
8542
+ ctx,
8543
+ `/v1/agent-cloud/${encodeURIComponent(args[0])}/triggers/${encodeURIComponent(args[1])}/setup`
8544
+ );
8545
+ ok(res, () => {
8546
+ line(import_picocolors17.default.bold(`Connect ${res?.label ?? "this channel"}`));
8547
+ line("");
8548
+ line(import_picocolors17.default.bold("Webhook URL"));
8549
+ line(" " + (res?.webhook_url ?? import_picocolors17.default.dim("\u2014")));
8550
+ if (res?.verify_token) {
8551
+ line(import_picocolors17.default.bold("Verify token"));
8552
+ line(" " + res.verify_token);
8553
+ }
8554
+ line("");
8555
+ for (const step of res?.setup_steps ?? []) line(" " + import_picocolors17.default.dim(step));
8556
+ line("");
8557
+ line(
8558
+ res?.is_verified ? import_picocolors17.default.green(`Receiving \u2014 ${res.event_count ?? 0} events so far.`) : import_picocolors17.default.yellow("Nothing has arrived yet.")
8559
+ );
8560
+ });
8561
+ })
8562
+ );
8563
+ cloud.command("trigger-remove <agentId> <triggerId>").description("Stop something from starting this agent").action(
8564
+ action(async ({ ctx, args }) => {
8565
+ const res = await api(
8566
+ ctx,
8567
+ `/v1/agent-cloud/${encodeURIComponent(args[0])}/triggers/${encodeURIComponent(args[1])}`,
8568
+ { method: "DELETE" }
8569
+ );
8570
+ ok(res, () => line("Removed."));
8571
+ })
8572
+ );
8573
+ cloud.command("trigger-events <agentId>").description("What has fired lately, and whether it started the agent").option("--limit <n>", "how many to show").action(
8574
+ action(async ({ ctx, args, opts }) => {
8575
+ const res = await api(
8576
+ ctx,
8577
+ `/v1/agent-cloud/${encodeURIComponent(args[0])}/trigger-events`,
8578
+ { query: { limit: opts.limit } }
8579
+ );
8580
+ const items = res?.events ?? [];
8581
+ ok(res, () => {
8582
+ if (!items.length) return line(import_picocolors17.default.dim("Nothing has fired yet."));
8583
+ for (const e of items) {
8584
+ const state = e.status === "matched" ? import_picocolors17.default.green("started the agent") : e.status === "failed" ? import_picocolors17.default.red("failed") : import_picocolors17.default.yellow(e.status);
8585
+ line(`${state} ${e.summary ?? e.trigger_name ?? ""}`);
8586
+ if (e.error_message) line(" " + import_picocolors17.default.red(e.error_message));
8587
+ else if (e.reasoning) line(" " + import_picocolors17.default.dim(e.reasoning));
8588
+ }
8589
+ });
8590
+ })
8591
+ );
8375
8592
  cloud.command("publish <agentId>").description("Put the current setup live \u2014 nothing takes effect until this runs").option("--note <text>", "What changed, for the version history").action(
8376
8593
  action(async ({ ctx, args, opts }) => {
8377
8594
  const res = await api(ctx, `/v1/agent-cloud/${encodeURIComponent(args[0])}/publish`, {
@@ -8554,6 +8771,60 @@ function parsePairs(pairs, allowed) {
8554
8771
  }
8555
8772
  return out;
8556
8773
  }
8774
+ function buildTriggerBody(kind, pairs) {
8775
+ const base = {
8776
+ name: pairs.name,
8777
+ rule: pairs.rule
8778
+ };
8779
+ if (kind === "schedule") {
8780
+ if (!pairs.cron) {
8781
+ throw new Error(
8782
+ 'A schedule needs cron=, e.g. cron="0 9 * * 1-5" for every weekday at 9am.'
8783
+ );
8784
+ }
8785
+ return {
8786
+ ...base,
8787
+ trigger_type: "schedule",
8788
+ name: pairs.name ?? "Scheduled run",
8789
+ schedule_config: {
8790
+ cron_expression: pairs.cron,
8791
+ timezone: pairs.timezone ?? "UTC"
8792
+ }
8793
+ };
8794
+ }
8795
+ if (kind === "chat") {
8796
+ if (!pairs.app_type) {
8797
+ throw new Error(
8798
+ "A chat trigger needs app_type=, one of: line, telegram, discord, slack, facebook_messenger, instagram, whatsapp, twitter, custom."
8799
+ );
8800
+ }
8801
+ return {
8802
+ ...base,
8803
+ trigger_type: "chat_webhook",
8804
+ chat_webhook_config: {
8805
+ app_type: pairs.app_type,
8806
+ event_type: pairs.event_type ?? "message"
8807
+ }
8808
+ };
8809
+ }
8810
+ if (kind === "app_event") {
8811
+ if (!pairs.event_type || !pairs.connected_account_id) {
8812
+ throw new Error(
8813
+ "An app-event trigger needs event_type= and connected_account_id= (see `workser connection list`)."
8814
+ );
8815
+ }
8816
+ return {
8817
+ ...base,
8818
+ trigger_type: "app_event",
8819
+ name: pairs.name ?? pairs.event_type,
8820
+ app_event_config: {
8821
+ event_type: pairs.event_type,
8822
+ connected_account_id: pairs.connected_account_id
8823
+ }
8824
+ };
8825
+ }
8826
+ throw new Error(`Unknown kind "${kind}". One of: schedule | chat | app_event`);
8827
+ }
8557
8828
 
8558
8829
  // src/commands/verify.ts
8559
8830
  var import_picocolors18 = __toESM(require_picocolors(), 1);
@@ -8879,59 +9150,142 @@ function registerConnection(program3) {
8879
9150
  );
8880
9151
  }
8881
9152
 
8882
- // src/commands/line.ts
9153
+ // src/commands/channel.ts
8883
9154
  var import_picocolors23 = __toESM(require_picocolors(), 1);
8884
- function registerLine(program3) {
8885
- const cmd = program3.command("line").description("Connect a LINE Official Account and use the LINE Messaging API");
8886
- cmd.command("status").description("Show this project's LINE connection").action(
9155
+ var SHAPES = [
9156
+ {
9157
+ id: "line",
9158
+ label: "LINE",
9159
+ description: "Connect a LINE Official Account and use the LINE Messaging API",
9160
+ tokenField: "channel_access_token",
9161
+ tokenHelp: "Messaging API tab \u2192 Channel access token",
9162
+ extra: {
9163
+ flag: "--secret <channelSecret>",
9164
+ field: "channel_secret",
9165
+ help: "Basic settings \u2192 Channel secret (for webhook signatures)"
9166
+ },
9167
+ send: {
9168
+ operation: "push",
9169
+ build: (to, text) => ({ to, messages: [{ type: "text", text }] })
9170
+ },
9171
+ reply: {
9172
+ operation: "reply",
9173
+ build: (replyToken, text) => ({
9174
+ replyToken,
9175
+ messages: [{ type: "text", text }]
9176
+ })
9177
+ }
9178
+ },
9179
+ {
9180
+ id: "telegram",
9181
+ label: "Telegram",
9182
+ description: "Connect a Telegram bot and message chats, groups and channels",
9183
+ tokenField: "bot_token",
9184
+ tokenHelp: "The token @BotFather gave you",
9185
+ send: {
9186
+ operation: "send_message",
9187
+ build: (chat_id, text) => ({ chat_id, text })
9188
+ }
9189
+ },
9190
+ {
9191
+ id: "discord",
9192
+ label: "Discord",
9193
+ description: "Connect a Discord bot and post in servers and DMs",
9194
+ tokenField: "bot_token",
9195
+ tokenHelp: "Developer Portal \u2192 your application \u2192 Bot \u2192 Reset Token",
9196
+ send: {
9197
+ operation: "send_message",
9198
+ build: (channel_id, content) => ({ channel_id, content })
9199
+ }
9200
+ },
9201
+ {
9202
+ id: "slack",
9203
+ label: "Slack",
9204
+ description: "Connect a Slack app and post in channels and DMs",
9205
+ tokenField: "bot_token",
9206
+ tokenHelp: "OAuth & Permissions \u2192 Bot User OAuth Token (starts xoxb-)",
9207
+ extra: {
9208
+ flag: "--signing-secret <secret>",
9209
+ field: "signing_secret",
9210
+ help: "Basic Information \u2192 Signing Secret (for event signatures)"
9211
+ },
9212
+ send: {
9213
+ operation: "send_message",
9214
+ build: (channel, text) => ({ channel, text })
9215
+ }
9216
+ }
9217
+ ];
9218
+ function registerChannels(program3) {
9219
+ for (const shape of SHAPES) register(program3, shape);
9220
+ }
9221
+ function register(program3, shape) {
9222
+ const cmd = program3.command(shape.id).description(shape.description);
9223
+ const path = (suffix = "") => (projectId) => `/v1/projects/${projectId}/channels/${shape.id}${suffix}`;
9224
+ cmd.command("status").description(`Show this project's ${shape.label} connection`).action(
8887
9225
  action(async ({ ctx }) => {
8888
9226
  const projectId = requireProject(ctx);
8889
- const res = await api(ctx, `/v1/projects/${projectId}/line`);
9227
+ const res = await api(ctx, path()(projectId));
8890
9228
  ok(res, () => {
8891
9229
  const c = res?.connection;
8892
- if (!c) return line(import_picocolors23.default.dim("No LINE account connected. Run `workser line connect`."));
9230
+ if (!c) {
9231
+ return line(
9232
+ import_picocolors23.default.dim(
9233
+ `No ${shape.label} account connected. Run \`workser ${shape.id} connect\`.`
9234
+ )
9235
+ );
9236
+ }
8893
9237
  const state = c.status === "connected" ? import_picocolors23.default.green(c.status) : import_picocolors23.default.yellow(c.status);
8894
- line(`${import_picocolors23.default.bold(c.display_name ?? "LINE")} ${state}`);
8895
- if (c.public_config?.basic_id) line(import_picocolors23.default.dim(` ${c.public_config.basic_id}`));
9238
+ line(`${import_picocolors23.default.bold(c.display_name ?? shape.label)} ${state}`);
9239
+ for (const [k, v] of Object.entries(c.public_config ?? {})) {
9240
+ if (v) line(import_picocolors23.default.dim(` ${k}: ${v}`));
9241
+ }
8896
9242
  if (c.last_error) line(import_picocolors23.default.red(` ${c.last_error}`));
8897
9243
  });
8898
9244
  })
8899
9245
  );
8900
- cmd.command("connect").description("Connect a LINE Official Account with its channel access token").requiredOption("--token <channelAccessToken>", "Messaging API \u2192 Channel access token").option("--secret <channelSecret>", "Basic settings \u2192 Channel secret (for webhook signatures)").action(
9246
+ const connect = cmd.command("connect").description(`Connect a ${shape.label} account to this project`).requiredOption("--token <token>", shape.tokenHelp);
9247
+ if (shape.extra) connect.option(shape.extra.flag, shape.extra.help);
9248
+ connect.action(
8901
9249
  action(async ({ ctx, opts }) => {
8902
9250
  const projectId = requireProject(ctx);
8903
- const res = await api(ctx, `/v1/projects/${projectId}/line/connect`, {
8904
- body: { channel_access_token: opts.token, channel_secret: opts.secret }
8905
- });
9251
+ const body = {};
9252
+ body[shape.tokenField] = String(opts.token);
9253
+ if (shape.extra) {
9254
+ const key = shape.extra.flag.split(" ")[0].replace(/^--/, "").replace(/-([a-z])/g, (_, c) => c.toUpperCase());
9255
+ if (opts[key]) body[shape.extra.field] = opts[key];
9256
+ }
9257
+ const res = await api(ctx, path("/connect")(projectId), { body });
8906
9258
  ok(
8907
9259
  res,
8908
- () => line(`Connected ${import_picocolors23.default.bold(res?.connection?.display_name ?? "LINE")}.`)
9260
+ () => line(
9261
+ `Connected ${import_picocolors23.default.bold(res?.connection?.display_name ?? shape.label)}.`
9262
+ )
8909
9263
  );
8910
9264
  })
8911
9265
  );
8912
- cmd.command("verify").description("Ask LINE whether the stored token still works").action(
9266
+ cmd.command("verify").description(`Ask ${shape.label} whether the stored credential still works`).action(
8913
9267
  action(async ({ ctx }) => {
8914
9268
  const projectId = requireProject(ctx);
8915
- const res = await api(ctx, `/v1/projects/${projectId}/line/verify`, { body: {} });
9269
+ const res = await api(ctx, path("/verify")(projectId), { body: {} });
8916
9270
  ok(res, () => {
8917
9271
  const c = res?.connection;
8918
9272
  line(
8919
- c?.status === "connected" ? import_picocolors23.default.green("The token still works.") : import_picocolors23.default.red(c?.last_error ?? "LINE refused the stored token.")
9273
+ c?.status === "connected" ? import_picocolors23.default.green("The credential still works.") : import_picocolors23.default.red(c?.last_error ?? `${shape.label} refused it.`)
8920
9274
  );
8921
9275
  });
8922
9276
  })
8923
9277
  );
8924
- cmd.command("disconnect").description("Disconnect LINE from this project").action(
9278
+ cmd.command("disconnect").description(`Disconnect ${shape.label} from this project`).action(
8925
9279
  action(async ({ ctx }) => {
8926
9280
  const projectId = requireProject(ctx);
8927
- const res = await api(ctx, `/v1/projects/${projectId}/line`, { method: "DELETE" });
9281
+ const res = await api(ctx, path()(projectId), { method: "DELETE" });
8928
9282
  ok(res, () => line("Disconnected."));
8929
9283
  })
8930
9284
  );
8931
- cmd.command("ops").description("List every LINE operation, optionally filtered by group").option("--group <name>", "messaging, richmenu, audience, insight, people, group, quota, content, account, validation").action(
9285
+ cmd.command("ops").description(`List every ${shape.label} operation`).option("--group <name>", "filter to one group").action(
8932
9286
  action(async ({ ctx, opts }) => {
8933
9287
  const projectId = requireProject(ctx);
8934
- const res = await api(ctx, `/v1/projects/${projectId}/line/operations`);
9288
+ const res = await api(ctx, path("/operations")(projectId));
8935
9289
  const all = res?.operations ?? [];
8936
9290
  const items = opts.group ? all.filter((o) => o.group === opts.group) : all;
8937
9291
  ok(items, () => {
@@ -8946,87 +9300,116 @@ ${group}`));
8946
9300
  const mark = o.writes ? import_picocolors23.default.yellow(" \u25CF") : " ";
8947
9301
  line(`${mark} ${import_picocolors23.default.bold(o.id.padEnd(28))} ${o.summary}`);
8948
9302
  }
8949
- line(import_picocolors23.default.dim("\n\u25CF sends something, spends quota, or changes the account."));
9303
+ line(
9304
+ import_picocolors23.default.dim(
9305
+ "\n\u25CF sends something, spends quota, or changes the account."
9306
+ )
9307
+ );
9308
+ for (const note of res?.excludes ?? []) {
9309
+ line(import_picocolors23.default.dim(` not included \u2014 ${note}`));
9310
+ }
8950
9311
  });
8951
9312
  })
8952
9313
  );
8953
- cmd.command("call <operation>").description("Run any LINE operation \u2014 see `workser line ops`").option("--params <json>", "the operation's parameters as a JSON string", "{}").action(
9314
+ cmd.command("call <operation>").description(`Run any ${shape.label} operation \u2014 see \`workser ${shape.id} ops\``).option("--params <json>", "the operation's parameters as a JSON string", "{}").action(
8954
9315
  action(async ({ ctx, args, opts }) => {
8955
9316
  const projectId = requireProject(ctx);
8956
- const res = await callLine(ctx, projectId, args[0], parseParams(opts.params));
8957
- ok(res, () => line(JSON.stringify(res?.data ?? res, null, 2)));
8958
- })
8959
- );
8960
- cmd.command("send <to> <text>").description("Send a text message to a user, group or room id").action(
8961
- action(async ({ ctx, args }) => {
8962
- const projectId = requireProject(ctx);
8963
- const res = await callLine(ctx, projectId, "push", {
8964
- to: args[0],
8965
- messages: [{ type: "text", text: args[1] }]
8966
- });
8967
- ok(res, () => report(res, "Sent."));
8968
- })
8969
- );
8970
- cmd.command("reply <replyToken> <text>").description("Answer a message using its reply token (free, and single-use)").action(
8971
- action(async ({ ctx, args }) => {
8972
- const projectId = requireProject(ctx);
8973
- const res = await callLine(ctx, projectId, "reply", {
8974
- replyToken: args[0],
8975
- messages: [{ type: "text", text: args[1] }]
8976
- });
8977
- ok(res, () => report(res, "Replied."));
8978
- })
8979
- );
8980
- cmd.command("broadcast <text>").description("Send a text message to EVERY follower \u2014 spends one message each").action(
8981
- action(async ({ ctx, args }) => {
8982
- const projectId = requireProject(ctx);
8983
- const res = await callLine(ctx, projectId, "broadcast", {
8984
- messages: [{ type: "text", text: args[0] }]
8985
- });
8986
- ok(res, () => report(res, "Broadcast sent."));
8987
- })
8988
- );
8989
- cmd.command("quota").description("How much of this month's message allowance is left").action(
8990
- action(async ({ ctx }) => {
8991
- const projectId = requireProject(ctx);
8992
- const [quota, used] = await Promise.all([
8993
- callLine(ctx, projectId, "quota", {}),
8994
- callLine(ctx, projectId, "quota_consumption", {})
8995
- ]);
8996
- ok({ quota: quota?.data, consumption: used?.data }, () => {
8997
- const limit = quota?.data?.value;
8998
- const spent = used?.data?.totalUsage;
8999
- if (limit === void 0) return line(JSON.stringify(quota?.data ?? quota, null, 2));
9000
- line(`${spent ?? "?"} of ${limit} messages used this month.`);
9001
- });
9002
- })
9003
- );
9004
- cmd.command("profile <userId>").description("Look up one follower").action(
9005
- action(async ({ ctx, args }) => {
9006
- const projectId = requireProject(ctx);
9007
- const res = await callLine(ctx, projectId, "profile", { userId: args[0] });
9317
+ const res = await call(ctx, projectId, shape, args[0], parseParams(opts.params));
9008
9318
  ok(res, () => line(JSON.stringify(res?.data ?? res, null, 2)));
9009
9319
  })
9010
9320
  );
9321
+ if (shape.send) {
9322
+ const send = shape.send;
9323
+ cmd.command("send <to> <text>").description(
9324
+ shape.id === "discord" ? "Post a text message in a Discord channel id" : shape.id === "slack" ? "Post a text message in a Slack channel or DM id" : "Send a text message to a chat, user, group or room id"
9325
+ ).action(
9326
+ action(async ({ ctx, args }) => {
9327
+ const projectId = requireProject(ctx);
9328
+ const res = await call(
9329
+ ctx,
9330
+ projectId,
9331
+ shape,
9332
+ send.operation,
9333
+ send.build(args[0], args[1])
9334
+ );
9335
+ ok(res, () => report(res, "Sent."));
9336
+ })
9337
+ );
9338
+ }
9339
+ if (shape.reply) {
9340
+ const reply = shape.reply;
9341
+ cmd.command("reply <token> <text>").description("Answer a message using its reply token \u2014 free, and single-use").action(
9342
+ action(async ({ ctx, args }) => {
9343
+ const projectId = requireProject(ctx);
9344
+ const res = await call(
9345
+ ctx,
9346
+ projectId,
9347
+ shape,
9348
+ reply.operation,
9349
+ reply.build(args[0], args[1])
9350
+ );
9351
+ ok(res, () => report(res, "Replied."));
9352
+ })
9353
+ );
9354
+ }
9355
+ if (shape.id === "line") {
9356
+ cmd.command("broadcast <text>").description("Send a text message to EVERY follower \u2014 spends one message each").action(
9357
+ action(async ({ ctx, args }) => {
9358
+ const projectId = requireProject(ctx);
9359
+ const res = await call(ctx, projectId, shape, "broadcast", {
9360
+ messages: [{ type: "text", text: args[0] }]
9361
+ });
9362
+ ok(res, () => report(res, "Broadcast sent."));
9363
+ })
9364
+ );
9365
+ cmd.command("quota").description("How much of this month's message allowance is left").action(
9366
+ action(async ({ ctx }) => {
9367
+ const projectId = requireProject(ctx);
9368
+ const [quota, used] = await Promise.all([
9369
+ call(ctx, projectId, shape, "quota", {}),
9370
+ call(ctx, projectId, shape, "quota_consumption", {})
9371
+ ]);
9372
+ ok({ quota: quota?.data, consumption: used?.data }, () => {
9373
+ const limit = quota?.data?.value;
9374
+ const spent = used?.data?.totalUsage;
9375
+ if (limit === void 0) {
9376
+ return line(JSON.stringify(quota?.data ?? quota, null, 2));
9377
+ }
9378
+ line(`${spent ?? "?"} of ${limit} messages used this month.`);
9379
+ });
9380
+ })
9381
+ );
9382
+ cmd.command("profile <userId>").description("Look up one follower").action(
9383
+ action(async ({ ctx, args }) => {
9384
+ const projectId = requireProject(ctx);
9385
+ const res = await call(ctx, projectId, shape, "profile", {
9386
+ userId: args[0]
9387
+ });
9388
+ ok(res, () => line(JSON.stringify(res?.data ?? res, null, 2)));
9389
+ })
9390
+ );
9391
+ }
9011
9392
  }
9012
9393
  function parseParams(raw) {
9013
9394
  try {
9014
9395
  const parsed = JSON.parse(raw || "{}");
9015
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
9396
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
9397
+ return parsed;
9398
+ }
9016
9399
  } catch {
9017
9400
  }
9018
9401
  throw new Error(
9019
- `--params must be a JSON object, e.g. --params '{"to":"U123","messages":[{"type":"text","text":"hi"}]}'`
9402
+ `--params must be a JSON object, e.g. --params '{"chat_id":123,"text":"hi"}'`
9020
9403
  );
9021
9404
  }
9022
- async function callLine(ctx, projectId, operation, params) {
9023
- return api(ctx, `/v1/projects/${projectId}/line/call`, {
9405
+ async function call(ctx, projectId, shape, operation, params) {
9406
+ return api(ctx, `/v1/projects/${projectId}/channels/${shape.id}/call`, {
9024
9407
  body: { operation, params }
9025
9408
  });
9026
9409
  }
9027
9410
  function report(res, success2) {
9028
9411
  if (res?.ok === false) {
9029
- line(import_picocolors23.default.red(res.message ?? "LINE refused that."));
9412
+ line(import_picocolors23.default.red(res.message ?? "That was refused."));
9030
9413
  return;
9031
9414
  }
9032
9415
  line(success2);
@@ -12686,7 +13069,7 @@ function colour(d) {
12686
13069
 
12687
13070
  // src/index.ts
12688
13071
  var pkg = {
12689
- version: true ? "0.6.28" : "0.0.0-dev"
13072
+ version: true ? "0.6.30" : "0.0.0-dev"
12690
13073
  };
12691
13074
  var program2 = new Command();
12692
13075
  program2.name("workser").description(
@@ -12732,7 +13115,7 @@ registerCheckpoint(program2);
12732
13115
  registerSync(program2);
12733
13116
  registerWorkflow(program2);
12734
13117
  registerConnection(program2);
12735
- registerLine(program2);
13118
+ registerChannels(program2);
12736
13119
  registerTool(program2);
12737
13120
  registerMemory(program2);
12738
13121
  registerNote(program2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workser/cli",
3
- "version": "0.6.28",
3
+ "version": "0.6.30",
4
4
  "description": "Workser CLI — give your local AI agent native DevOps & infrastructure on Workser. The agent runs `workser …` to provision, deploy, and manage real apps.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -48,12 +48,9 @@ model call before you have ruled it out. A hand-written one is invisible to the
48
48
  Agent Cloud console, unversioned, unmetered, and needs a model credential nothing
49
49
  mints — so it answers nobody in production.
50
50
 
51
- It is available on every plan. Creating, configuring and publishing an agent do
52
- not require a subscription upgrade; a test or live run needs enough prepaid AI
53
- credits. If a run returns 402, hand the credit top-up to the owner and stop — do
54
- not quietly build something else. Ruled out for a stated product reason, there
55
- is an ORDER below it — Claude's managed agents (console.claude.com), Eve,
56
- LangChain in Python, then hand-built — and you say which rung you took and why.
51
+ Agent Cloud is on every plan. Setup and publish are free; test and live runs need
52
+ prepaid AI credits. On 402, hand over the top-up and stop do not build a hidden
53
+ substitute. If ruled out for a product reason, follow the ORDER below and say why.
57
54
  `workser help agent-cloud` has all of it, including the case that legitimately
58
55
  starts lower: a developer wanting their own agent code, knowing this exists.
59
56
 
@@ -48,6 +48,12 @@ sending it. That matters: the API silently drops unknown fields, so a typo
48
48
  would otherwise be accepted, dropped, and reported as success — leaving an
49
49
  agent that had been told nothing.
50
50
 
51
+ ## Nothing starts it until you give it a trigger
52
+
53
+ A published agent runs when asked and at no other time. A time, a chat message
54
+ or an app event is a **trigger**, and it works the moment it is saved — it is
55
+ not part of the agent's version. `workser help agent-triggers`.
56
+
51
57
  ## Nothing takes effect until you publish
52
58
 
53
59
  **This is the step to not forget.** The runtime resolves the PUBLISHED version
@@ -193,9 +199,9 @@ watching sees the agent think. See the `workser-sdk` skill, `reference/agents.md
193
199
  single biggest cause of an agent that "doesn't work".
194
200
 
195
201
  3. **Every plan can run agents once the shared wallet has enough credits.** A
196
- `402` with `spend_limit_reached` means the wallet needs a top-up (or the owner
197
- reached their own spend cap). Tell them exactly what it says and hand over
198
- the credit action; never suggest a subscription upgrade for this refusal.
202
+ `402` with `insufficient_credits` needs a top-up; `spend_limit_reached` means
203
+ the owner reached the guard rail they set. Tell them exactly what it says and
204
+ hand over the matching action; never suggest a subscription upgrade.
199
205
 
200
206
  4. **Say who it is for.** An agent acting for one of the app's customers needs
201
207
  `referenceUserId`, or its memory and audit trail belong to nobody.
@@ -0,0 +1,69 @@
1
+ ---
2
+ topic: agent-triggers
3
+ title: What starts an Agent Cloud agent
4
+ summary: Schedules, chat channels and app events — a trigger works the moment it is saved, unlike everything else on an agent.
5
+ commands: []
6
+ ---
7
+
8
+ # What starts an Agent Cloud agent
9
+
10
+ A published agent runs when somebody asks it to and at no other time. A trigger
11
+ is what makes it start on its own — a time, a chat message, or an event in a
12
+ connected app.
13
+
14
+ ```
15
+ workser agent-cloud triggers <id> # what starts it today
16
+ workser agent-cloud trigger-add <id> schedule cron="0 9 * * 1-5" name="Morning report"
17
+ workser agent-cloud trigger-add <id> chat app_type=line
18
+ workser agent-cloud trigger-add <id> app_event event_type=GMAIL_NEW_GMAIL_MESSAGE connected_account_id=<id>
19
+ workser agent-cloud trigger-setup <id> <triggerId> # the webhook URL to paste
20
+ workser agent-cloud trigger-events <id> # what fired, and what it started
21
+ workser agent-cloud trigger-remove <id> <triggerId>
22
+ ```
23
+
24
+ **A trigger works the moment it is saved** — unlike everything above, it is not
25
+ part of the agent's version and does not wait for a publish.
26
+
27
+ `rule=` is optional and is the FIRING rule, not the agent's instructions:
28
+ `rule="only when the message mentions an order"`. Leave it empty on a chat
29
+ channel and every message reaches the agent, which is what a support agent
30
+ wants — the agent decides what to do with each one.
31
+
32
+ For a chat trigger, **`trigger-setup` is not optional**: it returns the webhook
33
+ URL to paste into LINE's or Slack's console, and it says whether anything has
34
+ arrived yet. Without that step the trigger sits there looking finished and
35
+ never receives anything.
36
+
37
+ To let the agent ANSWER on that channel, the project also needs the account
38
+ connected — `workser line connect --token …`. See `workser help chat-channels`.
39
+
40
+ ## The two halves of a chat agent
41
+
42
+ Receiving and answering are separate, and each has its own setup:
43
+
44
+ | | What it does | How |
45
+ | --- | --- | --- |
46
+ | The trigger | a message STARTS the agent | `trigger-add <id> chat app_type=line`, then `trigger-setup` |
47
+ | The connection | the agent can ANSWER | `workser line connect --token …` |
48
+
49
+ Do only the first and the agent listens and never speaks. Do only the second and
50
+ nothing ever wakes it up.
51
+
52
+ With both in place, a run started by a message is handed the reply token and the
53
+ sender, and gets every operation of that account as a tool — `line_reply`,
54
+ `slack_send_message` and the rest. Sends go through the agent's approval gate;
55
+ reads do not.
56
+
57
+ ## Schedules, in plain words
58
+
59
+ `cron=` takes a standard five-field expression. The four people ask for:
60
+
61
+ ```
62
+ cron="0 9 * * 1-5" every weekday at 9am
63
+ cron="0 9 * * *" every day at 9am
64
+ cron="0 * * * *" every hour
65
+ cron="0 9 * * 1" every Monday at 9am
66
+ ```
67
+
68
+ `timezone=` defaults to UTC, which is almost never what somebody means by "9am".
69
+ Set it: `timezone="Asia/Bangkok"`.
@@ -0,0 +1,106 @@
1
+ ---
2
+ topic: chat-channels
3
+ title: LINE, Telegram, Discord, Slack
4
+ summary: Connect a chat account once, then send, reply and read from the CLI or from an agent — LINE gets its whole Messaging API.
5
+ commands: [line, telegram, discord, slack]
6
+ ---
7
+
8
+ # LINE, Telegram, Discord, Slack
9
+
10
+ Connect the business's chat account to this project **once**. After that it is
11
+ reachable from here, from Workser Code, and from any Agent Cloud agent in this
12
+ project — including the one a chat message starts.
13
+
14
+ Every provider takes the same six verbs:
15
+
16
+ ```
17
+ workser <provider> status # is anything connected?
18
+ workser <provider> connect --token <token>
19
+ workser <provider> verify # does the credential still work?
20
+ workser <provider> disconnect
21
+ workser <provider> ops [--group <name>] # everything it can do
22
+ workser <provider> call <operation> --params '<json>'
23
+ ```
24
+
25
+ where `<provider>` is `line`, `telegram`, `discord` or `slack`.
26
+
27
+ ## Getting the token
28
+
29
+ | Provider | Where | Flag |
30
+ | --- | --- | --- |
31
+ | LINE | Developers console → Messaging API → Channel access token | `--token`, plus `--secret` for the channel secret |
32
+ | Telegram | @BotFather → `/newbot` or `/token` | `--token` |
33
+ | Discord | Developer Portal → your app → Bot → Reset Token | `--token` (the bot must also be invited to the server) |
34
+ | Slack | Your app → OAuth & Permissions → Bot User OAuth Token (`xoxb-`) | `--token`, plus `--signing-secret` |
35
+
36
+ `connect` calls the provider immediately to prove the credential works, so a
37
+ mistyped token fails right there instead of an hour later as a customer message
38
+ nobody answered.
39
+
40
+ ## `call` is the whole API
41
+
42
+ `ops` prints every operation with a one-line summary. The `●` marks the ones
43
+ that send something, spend quota, or change the account.
44
+
45
+ ```
46
+ workser line call push --params '{"to":"U4af…","messages":[{"type":"text","text":"Your order shipped"}]}'
47
+ workser line call richmenu_list
48
+ workser telegram call send_chat_action --params '{"chat_id":123,"action":"typing"}'
49
+ workser slack call list_conversations --params '{"types":"public_channel"}'
50
+ workser discord call create_thread --params '{"channel_id":"…","message_id":"…","name":"Order #4021"}'
51
+ ```
52
+
53
+ Parameters are flat — path, query and body fields all go in `--params` together
54
+ and the server sorts them. Fields it has never heard of are **forwarded
55
+ untouched**, so a flex component or a Slack block the vendor shipped last week
56
+ works today.
57
+
58
+ ## Shortcuts
59
+
60
+ ```
61
+ workser line send <to> "<text>" | reply <replyToken> "<text>" | broadcast "<text>"
62
+ workser line quota | profile <userId>
63
+ workser telegram send <chatId> "<text>"
64
+ workser discord send <channelId> "<text>"
65
+ workser slack send <channel> "<text>"
66
+ ```
67
+
68
+ ## Reply beats push, on LINE
69
+
70
+ `reply` uses the token that came with the incoming message. It is **free** and
71
+ does not touch the monthly quota; `push` costs one message per recipient. The
72
+ token is single-use and expires within a minute or so, so reply first and fall
73
+ back to push.
74
+
75
+ `broadcast` sends to every follower and spends one message each. On a large
76
+ account that is the most expensive call available — say what it will cost
77
+ before running it.
78
+
79
+ ## How complete each one is
80
+
81
+ **LINE is the deep one** — its entire Messaging API, about seventy operations:
82
+ messaging, rich menus, audiences and narrowcast, insight, quota, content,
83
+ groups and rooms, and the webhook endpoint itself.
84
+
85
+ **Telegram, Discord and Slack carry what a chat agent needs** — send, reply,
86
+ edit, delete, react, typing indicator, threads and DMs, plus reading people and
87
+ conversations. Not their whole APIs. `ops` is the truth; ask it rather than
88
+ assuming an operation exists.
89
+
90
+ ## Rich menus, in order (LINE)
91
+
92
+ A rich menu does nothing until it has both a picture and a place to appear:
93
+
94
+ 1. `richmenu_create` — the layout and tappable areas. Returns an id.
95
+ 2. `richmenu_upload_image` — `content` is the file **base64-encoded**, with
96
+ `content_type`. The image must match the declared size exactly.
97
+ 3. `richmenu_set_default` (everybody) or `richmenu_link_user` (one person).
98
+
99
+ `richmenu_validate` checks a layout without creating it.
100
+
101
+ ## When it is an agent doing this
102
+
103
+ An Agent Cloud agent in a project with a connected account gets these as tools
104
+ automatically — there is nothing to bind, and no capability to switch on. Sends
105
+ go through the agent's approval gate; reads do not. See
106
+ `workser help agent-cloud`.
@@ -2,7 +2,7 @@
2
2
  topic: roles
3
3
  title: Delegate to roles
4
4
  summary: Hand a focused subtask to another configured local agent.
5
- commands: [agent]
5
+ commands: [agent, team]
6
6
  ---
7
7
 
8
8
  # Delegate to roles
@@ -18,6 +18,9 @@ workser agent spawn <agent> "<task>" [--role <label>] [--instructions <text>] [-
18
18
  workser agent main # show the configured main agent
19
19
  ```
20
20
 
21
+ `workser team` is the same command. The Team screen and your own briefing both
22
+ call these people a team, so both words work here.
23
+
21
24
  ## How to use it
22
25
 
23
26
  Run `workser agent list --json` first — it tells you which roles exist, which are
@@ -1,80 +0,0 @@
1
- ---
2
- topic: line
3
- title: LINE Official Account
4
- summary: Connect a LINE OA once, then send, reply, broadcast, build rich menus and read insight — from the CLI or from an agent.
5
- commands: [line]
6
- ---
7
-
8
- # LINE Official Account
9
-
10
- Connect the business's LINE account to this project **once**. After that the same
11
- account is reachable from here, from Workser Code, and from any Agent Cloud agent
12
- in this project — including the one a LINE message starts.
13
-
14
- ```
15
- workser line status # is anything connected?
16
- workser line connect --token <channelAccessToken> [--secret <channelSecret>]
17
- workser line verify # ask LINE if the token still works
18
- workser line disconnect
19
-
20
- workser line ops [--group messaging|richmenu|audience|insight|people|group|quota|content|account]
21
- workser line call <operation> --params '<json>' # any of the 70 operations
22
-
23
- workser line send <to> "<text>" # push to a user, group or room id
24
- workser line reply <replyToken> "<text>" # answer a message — free, single-use
25
- workser line broadcast "<text>" # EVERY follower, one message each
26
- workser line quota # allowance left this month
27
- workser line profile <userId>
28
- ```
29
-
30
- ## Getting the token
31
-
32
- LINE Developers console → your Messaging API channel → **Messaging API** tab →
33
- *Channel access token* (long-lived). `--secret` is the **Basic settings** →
34
- *Channel secret*, and is only needed if you want LINE's own signature check on
35
- incoming webhooks.
36
-
37
- `connect` calls LINE immediately to prove the token works, so a mistyped token
38
- fails here rather than an hour later as a customer message nobody answered.
39
-
40
- ## `call` is the whole API
41
-
42
- `ops` prints every operation with a one-line summary. The `●` marks the ones that
43
- send something, spend quota, or change the account.
44
-
45
- ```
46
- workser line call push --params '{"to":"U4af…","messages":[{"type":"text","text":"Your order shipped"}]}'
47
- workser line call richmenu_list
48
- workser line call insight_followers --params '{"date":"20260909"}'
49
- workser line call webhook_endpoint_set --params '{"endpoint":"https://…"}'
50
- ```
51
-
52
- Parameters are flat — path, query and body fields all go in `--params` together and
53
- the server sorts them. Fields it has never heard of are **forwarded to LINE
54
- untouched**, so a flex component LINE shipped last week works today.
55
-
56
- ## Reply beats push
57
-
58
- `reply` uses the token that came with the incoming message. It is **free** and does
59
- not touch the monthly quota; `push` costs one message per recipient. The token is
60
- single-use and expires within a minute or so, so reply first and fall back to push.
61
-
62
- `broadcast` sends to every follower and spends one message each. On a large account
63
- that is the most expensive call in the list — say what it will cost before running it.
64
-
65
- ## Rich menus, in order
66
-
67
- A rich menu does nothing until it has both a picture and a place to appear:
68
-
69
- 1. `richmenu_create` — the layout and tappable areas. Returns an id.
70
- 2. `richmenu_upload_image` — `content` is the file **base64-encoded**, with
71
- `content_type`. The image must match the declared size exactly.
72
- 3. `richmenu_set_default` (everybody) or `richmenu_link_user` (one person).
73
-
74
- `richmenu_validate` checks a layout without creating it.
75
-
76
- ## When it is an agent doing this
77
-
78
- An Agent Cloud agent in a project with a connected LINE account gets these as tools
79
- automatically — there is nothing to bind. Sends go through the agent's approval gate;
80
- reads do not. See `workser help agent-cloud`.