@workser/cli 0.6.28 → 0.6.29

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
  {
@@ -8372,6 +8474,118 @@ function registerAgentCloud(program3) {
8372
8474
  });
8373
8475
  })
8374
8476
  );
8477
+ cloud.command("triggers <agentId>").description("What starts this agent \u2014 schedules, chat channels, app events").action(
8478
+ action(async ({ ctx, args }) => {
8479
+ const res = await api(
8480
+ ctx,
8481
+ `/v1/agent-cloud/${encodeURIComponent(args[0])}/triggers`
8482
+ );
8483
+ const items = res?.triggers ?? [];
8484
+ ok(res, () => {
8485
+ if (!items.length) {
8486
+ return line(
8487
+ import_picocolors17.default.dim("Nothing starts this agent yet \u2014 it only runs when asked.")
8488
+ );
8489
+ }
8490
+ for (const t of items) {
8491
+ const state = t.status === "active" ? import_picocolors17.default.green("on") : import_picocolors17.default.yellow(t.status);
8492
+ const when = t.schedule_config?.cron_expression ?? t.app_event_config?.event_source ?? t.app_event_config?.event_type ?? "";
8493
+ line(
8494
+ `${import_picocolors17.default.bold(t.name ?? t.trigger_type)} ${state} ${import_picocolors17.default.dim(when)}`
8495
+ );
8496
+ line(" " + import_picocolors17.default.dim(t.id));
8497
+ }
8498
+ });
8499
+ })
8500
+ );
8501
+ cloud.command("trigger-add <agentId> <kind> [pairs...]").description(
8502
+ "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"
8503
+ ).action(
8504
+ action(async ({ ctx, args }) => {
8505
+ const kind = String(args[1]);
8506
+ const pairs = parsePairs(
8507
+ args[2] ?? [],
8508
+ [
8509
+ "name",
8510
+ "rule",
8511
+ "cron",
8512
+ "timezone",
8513
+ "app_type",
8514
+ "event_type",
8515
+ "connected_account_id"
8516
+ ]
8517
+ );
8518
+ const body = buildTriggerBody(kind, pairs);
8519
+ const res = await api(
8520
+ ctx,
8521
+ `/v1/agent-cloud/${encodeURIComponent(args[0])}/triggers`,
8522
+ { method: "POST", body }
8523
+ );
8524
+ ok(res, () => {
8525
+ line(import_picocolors17.default.green("Added.") + " " + import_picocolors17.default.dim(res?.trigger?.id ?? ""));
8526
+ if (kind === "chat") {
8527
+ line(
8528
+ import_picocolors17.default.dim("Now get the webhook URL to paste into the platform: ") + import_picocolors17.default.bold(
8529
+ `workser agent-cloud trigger-setup ${args[0]} ${res?.trigger?.id ?? "<triggerId>"}`
8530
+ )
8531
+ );
8532
+ }
8533
+ });
8534
+ })
8535
+ );
8536
+ cloud.command("trigger-setup <agentId> <triggerId>").description("The webhook URL and console steps for a chat trigger").action(
8537
+ action(async ({ ctx, args }) => {
8538
+ const res = await api(
8539
+ ctx,
8540
+ `/v1/agent-cloud/${encodeURIComponent(args[0])}/triggers/${encodeURIComponent(args[1])}/setup`
8541
+ );
8542
+ ok(res, () => {
8543
+ line(import_picocolors17.default.bold(`Connect ${res?.label ?? "this channel"}`));
8544
+ line("");
8545
+ line(import_picocolors17.default.bold("Webhook URL"));
8546
+ line(" " + (res?.webhook_url ?? import_picocolors17.default.dim("\u2014")));
8547
+ if (res?.verify_token) {
8548
+ line(import_picocolors17.default.bold("Verify token"));
8549
+ line(" " + res.verify_token);
8550
+ }
8551
+ line("");
8552
+ for (const step of res?.setup_steps ?? []) line(" " + import_picocolors17.default.dim(step));
8553
+ line("");
8554
+ line(
8555
+ res?.is_verified ? import_picocolors17.default.green(`Receiving \u2014 ${res.event_count ?? 0} events so far.`) : import_picocolors17.default.yellow("Nothing has arrived yet.")
8556
+ );
8557
+ });
8558
+ })
8559
+ );
8560
+ cloud.command("trigger-remove <agentId> <triggerId>").description("Stop something from starting this agent").action(
8561
+ action(async ({ ctx, args }) => {
8562
+ const res = await api(
8563
+ ctx,
8564
+ `/v1/agent-cloud/${encodeURIComponent(args[0])}/triggers/${encodeURIComponent(args[1])}`,
8565
+ { method: "DELETE" }
8566
+ );
8567
+ ok(res, () => line("Removed."));
8568
+ })
8569
+ );
8570
+ cloud.command("trigger-events <agentId>").description("What has fired lately, and whether it started the agent").option("--limit <n>", "how many to show").action(
8571
+ action(async ({ ctx, args, opts }) => {
8572
+ const res = await api(
8573
+ ctx,
8574
+ `/v1/agent-cloud/${encodeURIComponent(args[0])}/trigger-events`,
8575
+ { query: { limit: opts.limit } }
8576
+ );
8577
+ const items = res?.events ?? [];
8578
+ ok(res, () => {
8579
+ if (!items.length) return line(import_picocolors17.default.dim("Nothing has fired yet."));
8580
+ for (const e of items) {
8581
+ 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);
8582
+ line(`${state} ${e.summary ?? e.trigger_name ?? ""}`);
8583
+ if (e.error_message) line(" " + import_picocolors17.default.red(e.error_message));
8584
+ else if (e.reasoning) line(" " + import_picocolors17.default.dim(e.reasoning));
8585
+ }
8586
+ });
8587
+ })
8588
+ );
8375
8589
  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
8590
  action(async ({ ctx, args, opts }) => {
8377
8591
  const res = await api(ctx, `/v1/agent-cloud/${encodeURIComponent(args[0])}/publish`, {
@@ -8554,6 +8768,60 @@ function parsePairs(pairs, allowed) {
8554
8768
  }
8555
8769
  return out;
8556
8770
  }
8771
+ function buildTriggerBody(kind, pairs) {
8772
+ const base = {
8773
+ name: pairs.name,
8774
+ rule: pairs.rule
8775
+ };
8776
+ if (kind === "schedule") {
8777
+ if (!pairs.cron) {
8778
+ throw new Error(
8779
+ 'A schedule needs cron=, e.g. cron="0 9 * * 1-5" for every weekday at 9am.'
8780
+ );
8781
+ }
8782
+ return {
8783
+ ...base,
8784
+ trigger_type: "schedule",
8785
+ name: pairs.name ?? "Scheduled run",
8786
+ schedule_config: {
8787
+ cron_expression: pairs.cron,
8788
+ timezone: pairs.timezone ?? "UTC"
8789
+ }
8790
+ };
8791
+ }
8792
+ if (kind === "chat") {
8793
+ if (!pairs.app_type) {
8794
+ throw new Error(
8795
+ "A chat trigger needs app_type=, one of: line, telegram, discord, slack, facebook_messenger, instagram, whatsapp, twitter, custom."
8796
+ );
8797
+ }
8798
+ return {
8799
+ ...base,
8800
+ trigger_type: "chat_webhook",
8801
+ chat_webhook_config: {
8802
+ app_type: pairs.app_type,
8803
+ event_type: pairs.event_type ?? "message"
8804
+ }
8805
+ };
8806
+ }
8807
+ if (kind === "app_event") {
8808
+ if (!pairs.event_type || !pairs.connected_account_id) {
8809
+ throw new Error(
8810
+ "An app-event trigger needs event_type= and connected_account_id= (see `workser connection list`)."
8811
+ );
8812
+ }
8813
+ return {
8814
+ ...base,
8815
+ trigger_type: "app_event",
8816
+ name: pairs.name ?? pairs.event_type,
8817
+ app_event_config: {
8818
+ event_type: pairs.event_type,
8819
+ connected_account_id: pairs.connected_account_id
8820
+ }
8821
+ };
8822
+ }
8823
+ throw new Error(`Unknown kind "${kind}". One of: schedule | chat | app_event`);
8824
+ }
8557
8825
 
8558
8826
  // src/commands/verify.ts
8559
8827
  var import_picocolors18 = __toESM(require_picocolors(), 1);
@@ -8879,59 +9147,142 @@ function registerConnection(program3) {
8879
9147
  );
8880
9148
  }
8881
9149
 
8882
- // src/commands/line.ts
9150
+ // src/commands/channel.ts
8883
9151
  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(
9152
+ var SHAPES = [
9153
+ {
9154
+ id: "line",
9155
+ label: "LINE",
9156
+ description: "Connect a LINE Official Account and use the LINE Messaging API",
9157
+ tokenField: "channel_access_token",
9158
+ tokenHelp: "Messaging API tab \u2192 Channel access token",
9159
+ extra: {
9160
+ flag: "--secret <channelSecret>",
9161
+ field: "channel_secret",
9162
+ help: "Basic settings \u2192 Channel secret (for webhook signatures)"
9163
+ },
9164
+ send: {
9165
+ operation: "push",
9166
+ build: (to, text) => ({ to, messages: [{ type: "text", text }] })
9167
+ },
9168
+ reply: {
9169
+ operation: "reply",
9170
+ build: (replyToken, text) => ({
9171
+ replyToken,
9172
+ messages: [{ type: "text", text }]
9173
+ })
9174
+ }
9175
+ },
9176
+ {
9177
+ id: "telegram",
9178
+ label: "Telegram",
9179
+ description: "Connect a Telegram bot and message chats, groups and channels",
9180
+ tokenField: "bot_token",
9181
+ tokenHelp: "The token @BotFather gave you",
9182
+ send: {
9183
+ operation: "send_message",
9184
+ build: (chat_id, text) => ({ chat_id, text })
9185
+ }
9186
+ },
9187
+ {
9188
+ id: "discord",
9189
+ label: "Discord",
9190
+ description: "Connect a Discord bot and post in servers and DMs",
9191
+ tokenField: "bot_token",
9192
+ tokenHelp: "Developer Portal \u2192 your application \u2192 Bot \u2192 Reset Token",
9193
+ send: {
9194
+ operation: "send_message",
9195
+ build: (channel_id, content) => ({ channel_id, content })
9196
+ }
9197
+ },
9198
+ {
9199
+ id: "slack",
9200
+ label: "Slack",
9201
+ description: "Connect a Slack app and post in channels and DMs",
9202
+ tokenField: "bot_token",
9203
+ tokenHelp: "OAuth & Permissions \u2192 Bot User OAuth Token (starts xoxb-)",
9204
+ extra: {
9205
+ flag: "--signing-secret <secret>",
9206
+ field: "signing_secret",
9207
+ help: "Basic Information \u2192 Signing Secret (for event signatures)"
9208
+ },
9209
+ send: {
9210
+ operation: "send_message",
9211
+ build: (channel, text) => ({ channel, text })
9212
+ }
9213
+ }
9214
+ ];
9215
+ function registerChannels(program3) {
9216
+ for (const shape of SHAPES) register(program3, shape);
9217
+ }
9218
+ function register(program3, shape) {
9219
+ const cmd = program3.command(shape.id).description(shape.description);
9220
+ const path = (suffix = "") => (projectId) => `/v1/projects/${projectId}/channels/${shape.id}${suffix}`;
9221
+ cmd.command("status").description(`Show this project's ${shape.label} connection`).action(
8887
9222
  action(async ({ ctx }) => {
8888
9223
  const projectId = requireProject(ctx);
8889
- const res = await api(ctx, `/v1/projects/${projectId}/line`);
9224
+ const res = await api(ctx, path()(projectId));
8890
9225
  ok(res, () => {
8891
9226
  const c = res?.connection;
8892
- if (!c) return line(import_picocolors23.default.dim("No LINE account connected. Run `workser line connect`."));
9227
+ if (!c) {
9228
+ return line(
9229
+ import_picocolors23.default.dim(
9230
+ `No ${shape.label} account connected. Run \`workser ${shape.id} connect\`.`
9231
+ )
9232
+ );
9233
+ }
8893
9234
  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}`));
9235
+ line(`${import_picocolors23.default.bold(c.display_name ?? shape.label)} ${state}`);
9236
+ for (const [k, v] of Object.entries(c.public_config ?? {})) {
9237
+ if (v) line(import_picocolors23.default.dim(` ${k}: ${v}`));
9238
+ }
8896
9239
  if (c.last_error) line(import_picocolors23.default.red(` ${c.last_error}`));
8897
9240
  });
8898
9241
  })
8899
9242
  );
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(
9243
+ const connect = cmd.command("connect").description(`Connect a ${shape.label} account to this project`).requiredOption("--token <token>", shape.tokenHelp);
9244
+ if (shape.extra) connect.option(shape.extra.flag, shape.extra.help);
9245
+ connect.action(
8901
9246
  action(async ({ ctx, opts }) => {
8902
9247
  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
- });
9248
+ const body = {};
9249
+ body[shape.tokenField] = String(opts.token);
9250
+ if (shape.extra) {
9251
+ const key = shape.extra.flag.split(" ")[0].replace(/^--/, "").replace(/-([a-z])/g, (_, c) => c.toUpperCase());
9252
+ if (opts[key]) body[shape.extra.field] = opts[key];
9253
+ }
9254
+ const res = await api(ctx, path("/connect")(projectId), { body });
8906
9255
  ok(
8907
9256
  res,
8908
- () => line(`Connected ${import_picocolors23.default.bold(res?.connection?.display_name ?? "LINE")}.`)
9257
+ () => line(
9258
+ `Connected ${import_picocolors23.default.bold(res?.connection?.display_name ?? shape.label)}.`
9259
+ )
8909
9260
  );
8910
9261
  })
8911
9262
  );
8912
- cmd.command("verify").description("Ask LINE whether the stored token still works").action(
9263
+ cmd.command("verify").description(`Ask ${shape.label} whether the stored credential still works`).action(
8913
9264
  action(async ({ ctx }) => {
8914
9265
  const projectId = requireProject(ctx);
8915
- const res = await api(ctx, `/v1/projects/${projectId}/line/verify`, { body: {} });
9266
+ const res = await api(ctx, path("/verify")(projectId), { body: {} });
8916
9267
  ok(res, () => {
8917
9268
  const c = res?.connection;
8918
9269
  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.")
9270
+ c?.status === "connected" ? import_picocolors23.default.green("The credential still works.") : import_picocolors23.default.red(c?.last_error ?? `${shape.label} refused it.`)
8920
9271
  );
8921
9272
  });
8922
9273
  })
8923
9274
  );
8924
- cmd.command("disconnect").description("Disconnect LINE from this project").action(
9275
+ cmd.command("disconnect").description(`Disconnect ${shape.label} from this project`).action(
8925
9276
  action(async ({ ctx }) => {
8926
9277
  const projectId = requireProject(ctx);
8927
- const res = await api(ctx, `/v1/projects/${projectId}/line`, { method: "DELETE" });
9278
+ const res = await api(ctx, path()(projectId), { method: "DELETE" });
8928
9279
  ok(res, () => line("Disconnected."));
8929
9280
  })
8930
9281
  );
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(
9282
+ cmd.command("ops").description(`List every ${shape.label} operation`).option("--group <name>", "filter to one group").action(
8932
9283
  action(async ({ ctx, opts }) => {
8933
9284
  const projectId = requireProject(ctx);
8934
- const res = await api(ctx, `/v1/projects/${projectId}/line/operations`);
9285
+ const res = await api(ctx, path("/operations")(projectId));
8935
9286
  const all = res?.operations ?? [];
8936
9287
  const items = opts.group ? all.filter((o) => o.group === opts.group) : all;
8937
9288
  ok(items, () => {
@@ -8946,87 +9297,116 @@ ${group}`));
8946
9297
  const mark = o.writes ? import_picocolors23.default.yellow(" \u25CF") : " ";
8947
9298
  line(`${mark} ${import_picocolors23.default.bold(o.id.padEnd(28))} ${o.summary}`);
8948
9299
  }
8949
- line(import_picocolors23.default.dim("\n\u25CF sends something, spends quota, or changes the account."));
9300
+ line(
9301
+ import_picocolors23.default.dim(
9302
+ "\n\u25CF sends something, spends quota, or changes the account."
9303
+ )
9304
+ );
9305
+ for (const note of res?.excludes ?? []) {
9306
+ line(import_picocolors23.default.dim(` not included \u2014 ${note}`));
9307
+ }
8950
9308
  });
8951
9309
  })
8952
9310
  );
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(
9311
+ 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
9312
  action(async ({ ctx, args, opts }) => {
8955
9313
  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] });
9314
+ const res = await call(ctx, projectId, shape, args[0], parseParams(opts.params));
9008
9315
  ok(res, () => line(JSON.stringify(res?.data ?? res, null, 2)));
9009
9316
  })
9010
9317
  );
9318
+ if (shape.send) {
9319
+ const send = shape.send;
9320
+ cmd.command("send <to> <text>").description(
9321
+ 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"
9322
+ ).action(
9323
+ action(async ({ ctx, args }) => {
9324
+ const projectId = requireProject(ctx);
9325
+ const res = await call(
9326
+ ctx,
9327
+ projectId,
9328
+ shape,
9329
+ send.operation,
9330
+ send.build(args[0], args[1])
9331
+ );
9332
+ ok(res, () => report(res, "Sent."));
9333
+ })
9334
+ );
9335
+ }
9336
+ if (shape.reply) {
9337
+ const reply = shape.reply;
9338
+ cmd.command("reply <token> <text>").description("Answer a message using its reply token \u2014 free, and single-use").action(
9339
+ action(async ({ ctx, args }) => {
9340
+ const projectId = requireProject(ctx);
9341
+ const res = await call(
9342
+ ctx,
9343
+ projectId,
9344
+ shape,
9345
+ reply.operation,
9346
+ reply.build(args[0], args[1])
9347
+ );
9348
+ ok(res, () => report(res, "Replied."));
9349
+ })
9350
+ );
9351
+ }
9352
+ if (shape.id === "line") {
9353
+ cmd.command("broadcast <text>").description("Send a text message to EVERY follower \u2014 spends one message each").action(
9354
+ action(async ({ ctx, args }) => {
9355
+ const projectId = requireProject(ctx);
9356
+ const res = await call(ctx, projectId, shape, "broadcast", {
9357
+ messages: [{ type: "text", text: args[0] }]
9358
+ });
9359
+ ok(res, () => report(res, "Broadcast sent."));
9360
+ })
9361
+ );
9362
+ cmd.command("quota").description("How much of this month's message allowance is left").action(
9363
+ action(async ({ ctx }) => {
9364
+ const projectId = requireProject(ctx);
9365
+ const [quota, used] = await Promise.all([
9366
+ call(ctx, projectId, shape, "quota", {}),
9367
+ call(ctx, projectId, shape, "quota_consumption", {})
9368
+ ]);
9369
+ ok({ quota: quota?.data, consumption: used?.data }, () => {
9370
+ const limit = quota?.data?.value;
9371
+ const spent = used?.data?.totalUsage;
9372
+ if (limit === void 0) {
9373
+ return line(JSON.stringify(quota?.data ?? quota, null, 2));
9374
+ }
9375
+ line(`${spent ?? "?"} of ${limit} messages used this month.`);
9376
+ });
9377
+ })
9378
+ );
9379
+ cmd.command("profile <userId>").description("Look up one follower").action(
9380
+ action(async ({ ctx, args }) => {
9381
+ const projectId = requireProject(ctx);
9382
+ const res = await call(ctx, projectId, shape, "profile", {
9383
+ userId: args[0]
9384
+ });
9385
+ ok(res, () => line(JSON.stringify(res?.data ?? res, null, 2)));
9386
+ })
9387
+ );
9388
+ }
9011
9389
  }
9012
9390
  function parseParams(raw) {
9013
9391
  try {
9014
9392
  const parsed = JSON.parse(raw || "{}");
9015
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
9393
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
9394
+ return parsed;
9395
+ }
9016
9396
  } catch {
9017
9397
  }
9018
9398
  throw new Error(
9019
- `--params must be a JSON object, e.g. --params '{"to":"U123","messages":[{"type":"text","text":"hi"}]}'`
9399
+ `--params must be a JSON object, e.g. --params '{"chat_id":123,"text":"hi"}'`
9020
9400
  );
9021
9401
  }
9022
- async function callLine(ctx, projectId, operation, params) {
9023
- return api(ctx, `/v1/projects/${projectId}/line/call`, {
9402
+ async function call(ctx, projectId, shape, operation, params) {
9403
+ return api(ctx, `/v1/projects/${projectId}/channels/${shape.id}/call`, {
9024
9404
  body: { operation, params }
9025
9405
  });
9026
9406
  }
9027
9407
  function report(res, success2) {
9028
9408
  if (res?.ok === false) {
9029
- line(import_picocolors23.default.red(res.message ?? "LINE refused that."));
9409
+ line(import_picocolors23.default.red(res.message ?? "That was refused."));
9030
9410
  return;
9031
9411
  }
9032
9412
  line(success2);
@@ -12686,7 +13066,7 @@ function colour(d) {
12686
13066
 
12687
13067
  // src/index.ts
12688
13068
  var pkg = {
12689
- version: true ? "0.6.28" : "0.0.0-dev"
13069
+ version: true ? "0.6.29" : "0.0.0-dev"
12690
13070
  };
12691
13071
  var program2 = new Command();
12692
13072
  program2.name("workser").description(
@@ -12732,7 +13112,7 @@ registerCheckpoint(program2);
12732
13112
  registerSync(program2);
12733
13113
  registerWorkflow(program2);
12734
13114
  registerConnection(program2);
12735
- registerLine(program2);
13115
+ registerChannels(program2);
12736
13116
  registerTool(program2);
12737
13117
  registerMemory(program2);
12738
13118
  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.29",
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`.
@@ -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`.