@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 +545 -165
- 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/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
|
{
|
|
@@ -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/
|
|
9150
|
+
// src/commands/channel.ts
|
|
8883
9151
|
var import_picocolors23 = __toESM(require_picocolors(), 1);
|
|
8884
|
-
|
|
8885
|
-
|
|
8886
|
-
|
|
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,
|
|
9224
|
+
const res = await api(ctx, path()(projectId));
|
|
8890
9225
|
ok(res, () => {
|
|
8891
9226
|
const c = res?.connection;
|
|
8892
|
-
if (!c)
|
|
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 ??
|
|
8895
|
-
|
|
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(
|
|
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
|
|
8904
|
-
|
|
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(
|
|
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(
|
|
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,
|
|
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
|
|
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(
|
|
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,
|
|
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(
|
|
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,
|
|
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(
|
|
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(
|
|
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
|
|
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))
|
|
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 '{"
|
|
9399
|
+
`--params must be a JSON object, e.g. --params '{"chat_id":123,"text":"hi"}'`
|
|
9020
9400
|
);
|
|
9021
9401
|
}
|
|
9022
|
-
async function
|
|
9023
|
-
return api(ctx, `/v1/projects/${projectId}/
|
|
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 ?? "
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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",
|
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`.
|
|
@@ -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`.
|