@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 +550 -167
- package/package.json +1 -1
- package/skills/workser/SKILL.md +3 -6
- package/skills/workser/reference/agent-cloud.md +9 -3
- package/skills/workser/reference/agent-triggers.md +69 -0
- package/skills/workser/reference/chat-channels.md +106 -0
- package/skills/workser/reference/roles.md +4 -1
- package/skills/workser/reference/line.md +0 -80
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 \`
|
|
3959
|
-
reached
|
|
3960
|
-
the
|
|
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/
|
|
9153
|
+
// src/commands/channel.ts
|
|
8883
9154
|
var import_picocolors23 = __toESM(require_picocolors(), 1);
|
|
8884
|
-
|
|
8885
|
-
|
|
8886
|
-
|
|
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,
|
|
9227
|
+
const res = await api(ctx, path()(projectId));
|
|
8890
9228
|
ok(res, () => {
|
|
8891
9229
|
const c = res?.connection;
|
|
8892
|
-
if (!c)
|
|
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 ??
|
|
8895
|
-
|
|
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(
|
|
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
|
|
8904
|
-
|
|
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(
|
|
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(
|
|
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,
|
|
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
|
|
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(
|
|
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,
|
|
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(
|
|
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,
|
|
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(
|
|
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(
|
|
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
|
|
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))
|
|
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 '{"
|
|
9402
|
+
`--params must be a JSON object, e.g. --params '{"chat_id":123,"text":"hi"}'`
|
|
9020
9403
|
);
|
|
9021
9404
|
}
|
|
9022
|
-
async function
|
|
9023
|
-
return api(ctx, `/v1/projects/${projectId}/
|
|
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 ?? "
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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",
|
package/skills/workser/SKILL.md
CHANGED
|
@@ -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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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 `
|
|
197
|
-
reached
|
|
198
|
-
the
|
|
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`.
|