@thinkingai/ae-cli 6.0.45 → 6.0.47
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/README.md +2 -0
- package/README.zh.md +6 -0
- package/dist/{auth-77BUFLGC.js → auth-QPEVVBKT.js} +20 -15
- package/dist/auth-UMJAA72O.js +15 -0
- package/dist/{capability-ENHWPMTG.js → capability-RUSV77CO.js} +53 -15
- package/dist/{chunk-UW5UN47B.js → chunk-533JK3YX.js} +2 -0
- package/dist/{chunk-VPKZ7I72.js → chunk-6QK7SYYY.js} +5 -4
- package/dist/{chunk-TUKQZTMI.js → chunk-7K24F7T2.js} +2 -0
- package/dist/{chunk-OO6XR6LK.js → chunk-AMBFK2K3.js} +2 -0
- package/dist/{sync-DAVKYVMW.js → chunk-B5HMQAOM.js} +31 -540
- package/dist/{chunk-EE45RGOO.js → chunk-BVDST5YT.js} +5 -4
- package/dist/{chunk-VTWMIC5L.js → chunk-E2JKXMVO.js} +2 -0
- package/dist/{chunk-LYVNONC4.js → chunk-FR2EDAQM.js} +36 -0
- package/dist/{chunk-ILIU36SU.js → chunk-GJXRF7Y6.js} +6 -5
- package/dist/{chunk-DWO43OIB.js → chunk-JQ3ENZZH.js} +2 -0
- package/dist/chunk-JYMGHGCE.js +191 -0
- package/dist/{chunk-GT46FPXN.js → chunk-KEWVSO64.js} +18 -8
- package/dist/chunk-KZ6ZHF5J.js +294 -0
- package/dist/{chunk-QI524WPY.js → chunk-NSIVXKJH.js} +6 -5
- package/dist/chunk-O6K2ODUE.js +318 -0
- package/dist/chunk-QL6R6TSK.js +25215 -0
- package/dist/{chunk-4SGZG4XY.js → chunk-RWXIXOZG.js} +28 -13
- package/dist/{chunk-4NN5IWVN.js → chunk-T6OSFQZD.js} +2 -0
- package/dist/{chunk-PTE56QPL.js → chunk-TKHSULJT.js} +6 -0
- package/dist/{chunk-6EIJSNBD.js → chunk-Y74WTIKJ.js} +2 -0
- package/dist/{client-TKG4WBHN.js → client-N7NGZUNI.js} +6 -4
- package/dist/{community-report-client-FI4LNVYS.js → community-report-client-6OL6GBQW.js} +3 -3
- package/dist/{config-RE6CMGPK.js → config-XNMACCKX.js} +10 -8
- package/dist/{data-integration-XQYB4X4F.js → data-integration-4NZGZ45E.js} +2502 -272
- package/dist/index.js +130 -1243
- package/dist/{local-data-upload-client-BWHSUQQK.js → local-data-upload-client-EFHDLXFG.js} +3 -3
- package/dist/{memory-YK33G4T7.js → memory-HBJQ2GC5.js} +8 -7
- package/dist/{metadata-AXVHAAZG.js → metadata-PGDJE5RQ.js} +12 -11
- package/dist/{model-K3KLWIW6.js → model-BHLJ2QMT.js} +10 -9
- package/dist/personal-semantic-preference-E5NZ56DN.js +240 -0
- package/dist/sync-MUWPRM5I.js +522 -0
- package/dist/{te-agent-4BKBODMF.js → te-agent-WQZTMEK4.js} +12 -10
- package/dist/{te-analysis-WX7LRVAV.js → te-analysis-YWCSSLYL.js} +534 -48
- package/dist/{te-community-HLC43QKH.js → te-community-BNE6KREL.js} +8 -7
- package/dist/{te-dataops-EJP56W3K.js → te-dataops-TXLKM2W6.js} +469 -68
- package/dist/{te-engage-UBHPAJCO.js → te-engage-VEW4CJHK.js} +26 -10
- package/dist/{te-kb-SQCLHG6X.js → te-kb-BUD6N3NI.js} +312 -25
- package/dist/{te-system-YARIK4S5.js → te-system-44LZJDTQ.js} +573 -37
- package/dist/{te-team-EFKWYKMK.js → te-team-XMP47YXE.js} +10 -7
- package/dist/{update-OGPSZM5A.js → update-UODFOZ4P.js} +9 -7
- package/package.json +18 -9
- package/skills/ae-agent/SKILL.md +3 -4
- package/skills/ae-agent/references/edit-skill.md +3 -0
- package/skills/ae-agent/references/get-skill-content.md +1 -1
- package/skills/ae-agent/references/rescan-skills.md +15 -13
- package/skills/ae-agent/references/upload-skill.md +7 -4
- package/skills/ae-analysis/SKILL.md +29 -4
- package/skills/ae-analysis/metadata_resolution.md +38 -4
- package/skills/ae-analysis/references/analysis_data_retrieval.md +29 -0
- package/skills/ae-analysis/references/analysis_gateway_assets.md +1 -1
- package/skills/ae-analysis/references/asset_authentication_export.md +22 -0
- package/skills/ae-analysis/references/asset_authentication_list.md +18 -14
- package/skills/ae-analysis/references/asset_authentication_update.md +29 -14
- package/skills/ae-analysis/references/audience_models.md +18 -0
- package/skills/ae-analysis/references/command_index.md +19 -11
- package/skills/ae-analysis/references/dashboard_get.md +18 -1
- package/skills/ae-analysis/references/dashboard_update.md +3 -0
- package/skills/ae-analysis/references/personal_semantic_preference_add.md +23 -0
- package/skills/ae-analysis/references/personal_semantic_preference_delete.md +17 -0
- package/skills/ae-analysis/references/personal_semantic_preference_get.md +17 -0
- package/skills/ae-analysis/references/personal_semantic_preference_list.md +19 -0
- package/skills/ae-analysis/references/personal_semantic_preference_update.md +19 -0
- package/skills/ae-analysis/references/project_timezone_update.md +13 -4
- package/skills/ae-analysis/references/property_create.md +2 -0
- package/skills/ae-analysis/references/super_metadata_batch_create.md +2 -0
- package/skills/ae-analysis/references/user_cluster_models.md +2 -0
- package/skills/ae-analysis/references/user_cluster_update.md +8 -4
- package/skills/ae-analysis/references/user_tag_create.md +7 -1
- package/skills/ae-analysis/references/user_tag_models.md +8 -2
- package/skills/ae-data-integration/SKILL.md +24 -4
- package/skills/ae-data-integration/references/custom-layer.md +93 -0
- package/skills/ae-data-integration/references/error-handling.md +145 -0
- package/skills/ae-data-integration/references/handoff.md +77 -18
- package/skills/ae-data-integration/references/local-analysis.md +1 -1
- package/skills/ae-data-integration/references/reuse.md +9 -5
- package/skills/ae-data-integration/references/sink-upload.md +1 -1
- package/skills/ae-data-integration/references/source-inspect.md +35 -13
- package/skills/ae-data-integration/references/tracking-plan.md +7 -5
- package/skills/ae-data-integration/references/transform.md +12 -10
- package/skills/ae-data-integration/references/ue-mapping.md +33 -11
- package/skills/ae-data-integration/references/ue-routing.md +23 -0
- package/skills/ae-dataops/SKILL.md +11 -1
- package/skills/ae-dataops/references/dataops-backfill.md +135 -0
- package/skills/ae-engage/SKILL.md +3 -0
- package/skills/ae-engage/references/build-task-save-guide.md +14 -1
- package/skills/ae-engage/references/save-task.md +88 -0
- package/skills/ae-kb/SKILL.md +121 -10
- package/skills/ae-system/SKILL.md +28 -30
- package/skills/ae-system/references/channel-management.md +303 -0
- package/dist/auth-2WTQOP77.js +0 -14
- package/dist/capability-WA37LSIR.js +0 -312
- package/dist/chunk-4KVPKXFX.js +0 -98
- package/dist/chunk-C4MGVGJW.js +0 -13
- package/dist/chunk-MGU2N3HW.js +0 -556
- package/dist/chunk-P3FGXJTU.js +0 -349
- package/dist/chunk-QEFCIRNJ.js +0 -71
- package/dist/chunk-QGM4M3NI.js +0 -37
- package/dist/chunk-UOUS37JQ.js +0 -509
- package/dist/chunk-ZZUOD757.js +0 -598
- package/dist/memory-CHRU2F7W.js +0 -893
- package/dist/metadata-GGEHHPBE.js +0 -339
- package/dist/model-NR3JHFSJ.js +0 -139
- package/dist/sync-FCKOVWWS.js +0 -10261
- package/dist/te-agent-HLW4VTQK.js +0 -3893
- package/dist/te-analysis-YKFDBMA6.js +0 -8958
- package/dist/te-community-6HPBWJUZ.js +0 -1859
- package/dist/te-dataops-HDRUXY4K.js +0 -2208
- package/dist/te-engage-WLDTS5J5.js +0 -4898
- package/dist/te-system-Z77IKZFN.js +0 -2213
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
# Channel Management
|
|
2
|
+
|
|
3
|
+
Use this workflow for channel configuration, group routing, WhatsApp Web linking, and Feishu user bindings. All channel endpoints require an authenticated `root` or `agent_admin` and enforce company isolation.
|
|
4
|
+
|
|
5
|
+
## Scope
|
|
6
|
+
|
|
7
|
+
Channel configuration supports `feishu`, `lark`, `slack`, `discord`, `dingtalk`, `wecom`, `mattermost`, `google_chat`, and `whatsapp`. Administrator-driven user binding is available only for `feishu`.
|
|
8
|
+
|
|
9
|
+
The default Feishu binding is group-routing ready. It requires a verified `endpoint_id` for the channel and a `union_id` for every user. Use `--private-only` only after the user explicitly accepts private-chat-only behavior; never downgrade silently when either value is unavailable.
|
|
10
|
+
|
|
11
|
+
## Two Confirmation Phases
|
|
12
|
+
|
|
13
|
+
Treat channel configuration and personnel binding as separate writes:
|
|
14
|
+
|
|
15
|
+
1. **Channel configuration phase**: discover current state, prepare channel configuration and routing, run every applicable `ae-cli ... --dry-run`, show the redacted target/effect, and obtain one explicit confirmation before executing this phase.
|
|
16
|
+
2. **Personnel binding phase**: resolve the final roster and IDs, inspect each person's sandbox readiness, validate the complete binding batch and every required sandbox write with `--dry-run`, show counts, endpoint, private-only state, Agent assignments, sandbox creates, and sandbox enables, then obtain explicit confirmation before each planned write set. A roster containing only existing Agent members needs one final binding-and-sandbox confirmation.
|
|
17
|
+
|
|
18
|
+
A confirmation for phase 1 does not authorize phase 2. A confirmation for one roster does not authorize a changed roster.
|
|
19
|
+
|
|
20
|
+
## Phase 1: Configure a Channel
|
|
21
|
+
|
|
22
|
+
Start from current state:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
ae-cli system +list-channels
|
|
26
|
+
ae-cli system channel get --id <channel-id>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Create JSON uses snake_case. The original `+create-channel` and `+update-channel` commands also accept legacy camelCase fields, but new files should not use them.
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"name": "Support Bot",
|
|
34
|
+
"type": "feishu",
|
|
35
|
+
"config": {
|
|
36
|
+
"app_id": "<app-id>",
|
|
37
|
+
"app_secret": "<app-secret>"
|
|
38
|
+
},
|
|
39
|
+
"model": "<optional-model-id>",
|
|
40
|
+
"system_prompt": "<optional-system-prompt>",
|
|
41
|
+
"enabled": false
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Run the dry-run before the phase-1 confirmation. Credential values are replaced with `***`:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
ae-cli --dry-run system +create-channel --channel @channel.json
|
|
49
|
+
ae-cli system +create-channel --channel @channel.json
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Type-specific configuration:
|
|
53
|
+
|
|
54
|
+
| Type | Required config | Conditional config |
|
|
55
|
+
| --- | --- | --- |
|
|
56
|
+
| `feishu`, `lark` | `app_id`, `app_secret` | — |
|
|
57
|
+
| `slack` | `bot_token`, `app_token` | `client_id`, `client_secret` when used by the deployment |
|
|
58
|
+
| `discord` | `bot_token` | `client_id`, `client_secret` when used by the deployment |
|
|
59
|
+
| `dingtalk` | `client_id`, `client_secret`, `corp_id` | `interaction_card_template_id` |
|
|
60
|
+
| `wecom` | `bot_id`, `bot_secret` | If `oauth_enabled=true`: `corp_id`, `corp_secret`, `agent_id` |
|
|
61
|
+
| `mattermost` | `server_url`, `bot_token` | If `oauth_enabled=true`: `client_id`, `client_secret` |
|
|
62
|
+
| `google_chat` | `service_account_json` | `workspace_addon_service_account_email`; if `oauth_enabled=true`: `client_id`, `client_secret` |
|
|
63
|
+
| `whatsapp` | no manual config | Credentials are created by the QR lifecycle |
|
|
64
|
+
|
|
65
|
+
Update accepts a partial object containing `name`, `type`, `config`, `model`, `system_prompt`, `enabled`, or `unbind_users`. If `type` is present it must equal the channel's existing type. Omitting a secret or passing an empty string preserves the stored value; it does not clear the secret.
|
|
66
|
+
|
|
67
|
+
For every non-WhatsApp channel, verify credentials before depending on endpoints:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
ae-cli --dry-run system channel verify --id <channel-id>
|
|
71
|
+
ae-cli system channel verify --id <channel-id>
|
|
72
|
+
ae-cli system channel get --id <channel-id>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Treat configuration persistence and runtime startup as separate outcomes. An HTTP 200 may still contain `runtime_status="error"`; report that the configuration was saved without claiming the bot is online. When Google Chat creation returns its one-time `webhook_url`, ask the operator to save it immediately in an approved secret store and never copy it into logs or repository files.
|
|
76
|
+
|
|
77
|
+
For WhatsApp, create the channel without config, enable it with `+update-channel`, then start linking. Poll only until the returned `expires_at` (and never longer than two minutes) for `linked`, `needs_relink`, `failed`, or `expired`:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
ae-cli --dry-run system +update-channel --id <channel-id> --channel '{"enabled":true}'
|
|
81
|
+
ae-cli system +update-channel --id <channel-id> --channel '{"enabled":true}'
|
|
82
|
+
ae-cli --dry-run system channel whatsapp-web start --id <channel-id>
|
|
83
|
+
ae-cli system channel whatsapp-web start --id <channel-id>
|
|
84
|
+
ae-cli system channel whatsapp-web status --id <channel-id>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Show `qr_data_url` to the user for scanning. Do not call `channel verify` for WhatsApp. Before unlinking, disable the channel with `+update-channel`, then call `channel whatsapp-web unlink`; the unlink command requires its own high-risk CLI confirmation and still stays inside phase 1.
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
ae-cli --dry-run system +update-channel --id <channel-id> --channel '{"enabled":false}'
|
|
91
|
+
ae-cli system +update-channel --id <channel-id> --channel '{"enabled":false}'
|
|
92
|
+
ae-cli --dry-run system channel whatsapp-web unlink --id <channel-id>
|
|
93
|
+
ae-cli system channel whatsapp-web unlink --id <channel-id>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Group Routing
|
|
97
|
+
|
|
98
|
+
Use a verified endpoint ID from `channel verify` or `channel get`:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
ae-cli system channel routing get --endpoint-id <endpoint-id>
|
|
102
|
+
ae-cli --dry-run system channel routing set \
|
|
103
|
+
--endpoint-id <endpoint-id> \
|
|
104
|
+
--routing @routing.json
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`routing.json` replaces the full routing configuration:
|
|
108
|
+
|
|
109
|
+
```json
|
|
110
|
+
{
|
|
111
|
+
"status": "enabled",
|
|
112
|
+
"default_handler": { "kind": "agent", "id": "<agent-id>" },
|
|
113
|
+
"targets": [
|
|
114
|
+
{
|
|
115
|
+
"handler_kind": "team",
|
|
116
|
+
"handler_id": "<team-id>",
|
|
117
|
+
"slug": "billing",
|
|
118
|
+
"keywords": ["invoice", "refund"],
|
|
119
|
+
"sort_order": 10,
|
|
120
|
+
"enabled": true
|
|
121
|
+
}
|
|
122
|
+
]
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`status` is `enabled | disabled`, `default_handler` is an Agent/Team handler or `null`, and `targets` contains at most 19 entries. The server validates handler ownership, duplicate handlers/slugs, and keyword conflicts.
|
|
127
|
+
|
|
128
|
+
## Phase 2: Resolve and Bind Feishu Users
|
|
129
|
+
|
|
130
|
+
### Feishu binding transport contract
|
|
131
|
+
|
|
132
|
+
Both the single-user and batch CLI flows use the same binding endpoint:
|
|
133
|
+
|
|
134
|
+
| Operation | Method and path | CLI mapping |
|
|
135
|
+
| --- | --- | --- |
|
|
136
|
+
| Bind one Feishu identity to one channel | PUT `/api/cli/channel/v1/bindings/feishu` | `channel binding bind-feishu` sends one request. |
|
|
137
|
+
| Bind 1-100 Feishu identities to one channel | PUT `/api/cli/channel/v1/bindings/feishu` | `+bind-feishu-users` validates the full roster, then sends one binding PUT per person. |
|
|
138
|
+
| Set the private-chat default Agent after binding | PUT `/api/cli/channel/v1/bindings/{binding_id}/agent` | `channel binding set-agent`; the batch command calls it after each successful binding when an Agent is selected. |
|
|
139
|
+
|
|
140
|
+
The Feishu binding request body is a strict, flat JSON object:
|
|
141
|
+
|
|
142
|
+
| Field | Required | Binding meaning |
|
|
143
|
+
| --- | --- | --- |
|
|
144
|
+
| `channel_id` | yes | `channel_id` identifies the one target Feishu channel for the request. The channel must belong to the current company and be enabled. |
|
|
145
|
+
| `te_user_id` | yes | The selected AE Agent member's `openId`; this is the member being associated with the channel identity. |
|
|
146
|
+
| `open_id` | yes | The person's Feishu `open_id` under the same Feishu application configured by the target channel. |
|
|
147
|
+
| `union_id` | group-ready only | The person's stable Feishu identity for group routing. Supply it together with `endpoint_id`; omit both only for explicitly approved private-chat-only binding. |
|
|
148
|
+
| `endpoint_id` | group-ready only | `endpoint_id` must identify a verified endpoint that belongs to the same `channel_id`. Supply it together with `union_id`. |
|
|
149
|
+
|
|
150
|
+
```json
|
|
151
|
+
{
|
|
152
|
+
"channel_id": "channel-sales",
|
|
153
|
+
"te_user_id": "agent-member-open-id-alice",
|
|
154
|
+
"open_id": "ou_alice",
|
|
155
|
+
"union_id": "on_alice",
|
|
156
|
+
"endpoint_id": "endpoint-sales-verified"
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
The binding response returns:
|
|
161
|
+
|
|
162
|
+
| Field | Meaning |
|
|
163
|
+
| --- | --- |
|
|
164
|
+
| `binding_id` | The channel-user binding ID used for later Agent assignment or maintenance. |
|
|
165
|
+
| `channel_id` | The target channel confirmed by the server. |
|
|
166
|
+
| `te_user_id` | The AE Agent member associated with the channel identity. |
|
|
167
|
+
| `open_id` | The Feishu application-scoped identity stored on the binding. |
|
|
168
|
+
| `group_routing_ready` | Whether this binding is ready for group routing. |
|
|
169
|
+
| `agent_id` | The current private-chat default Agent, or no value when system-default resolution applies. |
|
|
170
|
+
| `endpoint_id` | The associated endpoint when the binding is group-routing ready. |
|
|
171
|
+
|
|
172
|
+
`agent_id` is not part of this request body. After the binding response returns `binding_id`, set or clear the private-chat default Agent through `PUT /api/cli/channel/v1/bindings/{binding_id}/agent`. `default_agent_id` and `private_only` are also CLI orchestration fields, not fields accepted by the Feishu binding endpoint.
|
|
173
|
+
|
|
174
|
+
One `+bind-feishu-users` invocation has one top-level `--channel-id` and one shared `--endpoint-id`; every entry in `bindings.json` is bound to that channel. Run separate confirmed batches for different channels. Do not put `channel_id` or `endpoint_id` inside individual roster entries.
|
|
175
|
+
|
|
176
|
+
Common contract mistakes:
|
|
177
|
+
|
|
178
|
+
| Mistake | Required correction |
|
|
179
|
+
| --- | --- |
|
|
180
|
+
| Reusing an `open_id` resolved under another Feishu application | Resolve the person again with the application configured by the target channel. |
|
|
181
|
+
| Pairing the target channel with an endpoint from another channel | Select a verified endpoint returned by `channel get` or `channel verify` for the same `channel_id`. |
|
|
182
|
+
| Sending `agent_id`, `default_agent_id`, or `private_only` to the Feishu binding endpoint | Keep them in CLI orchestration; assign the Agent through the binding-specific Agent endpoint. |
|
|
183
|
+
| Mixing users for multiple channels in one batch roster | Split the work by channel, dry-run every final roster, and confirm each batch separately. |
|
|
184
|
+
|
|
185
|
+
1. The current session must have the Feishu OpenAPI MCP mounted. Before resolving identities, verify all of these prerequisites:
|
|
186
|
+
|
|
187
|
+
- The MCP and the target channel use the same App ID and App Secret. Read the channel's `config.app_id` with `ae-cli system channel get --id <channel-id>` and compare it with the MCP application configuration. When the App Secret or MCP configuration is masked, obtain explicit confirmation from the configuration owner instead of assuming a match. Feishu `open_id` is application-scoped.
|
|
188
|
+
- The Feishu application has the `contact:user.id:readonly` permission.
|
|
189
|
+
- The MCP exposes the `contact.v3.users.batchGetId` tool.
|
|
190
|
+
|
|
191
|
+
Stop when any prerequisite is missing or cannot be verified. Resolve people by exact corporate email address or mobile number; `contact.v3.users.batchGetId` does not resolve names, so ask for an email address or mobile number when only a name is provided.
|
|
192
|
+
|
|
193
|
+
2. Call `contact.v3.users.batchGetId` with `user_id_type=open_id`. Put exact emails in `data.emails` or exact mobile numbers in `data.mobiles`:
|
|
194
|
+
|
|
195
|
+
```json
|
|
196
|
+
{
|
|
197
|
+
"params": { "user_id_type": "open_id" },
|
|
198
|
+
"data": { "emails": ["alice@example.com", "bob@example.com"] }
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Map each returned `user_list[].user_id` to the matching returned email or mobile number as that person's Feishu `open_id`. A zero-result lookup, missing input, duplicate input, or response that cannot be mapped one-to-one is unresolved. Stop, show the unresolved inputs, and obtain corrected exact identifiers; never choose the first result or infer a person from ordering.
|
|
203
|
+
|
|
204
|
+
3. Repeat `contact.v3.users.batchGetId` for the same exact emails or mobile numbers with `user_id_type=union_id`; reuse the corresponding `data.emails` or `data.mobiles` roster from step 2 unchanged.
|
|
205
|
+
|
|
206
|
+
Map each returned `user_list[].user_id` to the same person as that person's Feishu `union_id`. Require the `open_id` and `union_id` lookup results to cover the same uniquely identified roster. If the Feishu OpenAPI MCP cannot return a `union_id`, stop. Ask whether the user explicitly accepts `--private-only`; do not infer that choice.
|
|
207
|
+
|
|
208
|
+
4. Fetch Agent members and match each selected person to exactly one Agent member by confirmed login/display identity. Use that member's `openId` as `te_user_id`; do not use its database `userId`, and do not assume the app-scoped Feishu `open_id` equals the AE member `openId`:
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
ae-cli system +list-members --status enabled --all true
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
If a selected person is not yet an Agent member, use `+list-member-candidates` to resolve the exact candidate. When member addition is required, phase 2 has a prerequisite member-add confirmation and a final binding-and-sandbox confirmation. Dry-run and confirm `+add-members`, execute it, then refetch members before preparing the final binding and sandbox plan. Keep `--create-sandbox` false or omit it in this workflow; sandbox creation and verification happen explicitly after a successful channel binding. The member-add confirmation does not authorize the later binding or sandbox writes.
|
|
215
|
+
5. Build `bindings.json` using only snake_case:
|
|
216
|
+
|
|
217
|
+
```json
|
|
218
|
+
[
|
|
219
|
+
{
|
|
220
|
+
"te_user_id": "<agent-member-open-id>",
|
|
221
|
+
"open_id": "<feishu-open-id>",
|
|
222
|
+
"union_id": "<feishu-union-id>",
|
|
223
|
+
"agent_id": "<optional-per-user-agent-id>"
|
|
224
|
+
}
|
|
225
|
+
]
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
`--default-agent-id` applies when an item omits `agent_id`; an item's `agent_id` overrides the default. Omit both to leave the private-chat default Agent unchanged.
|
|
229
|
+
|
|
230
|
+
6. Inspect sandbox capacity and current ownership before building the phase-2 plan:
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
ae-cli system +get-sandbox-config
|
|
234
|
+
ae-cli system +list-sandboxes
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
For sandbox operations, map each selected Agent member's database `userId` to `items[].boundUsers[].userId`; never use the member's `openId`, Feishu `open_id`, or Feishu `union_id`. A channel-bound person's sandbox is ready when exactly one returned sandbox contains that `userId` and has `enabled === true`. Container `runningState` is a separate lifecycle state and does not trigger `+start-sandbox` in this workflow.
|
|
238
|
+
|
|
239
|
+
- For members with no sandbox, prepare one dry-run containing their database user IDs:
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
ae-cli --dry-run system +batch-create-sandboxes \
|
|
243
|
+
--user-ids '["<member-database-user-id>"]'
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
- For each existing sandbox with `enabled === false`, prepare its enable dry-run:
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
ae-cli --dry-run system +set-sandbox-enabled \
|
|
250
|
+
--id <sandbox-id> \
|
|
251
|
+
--enabled true
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Calculate the planned quota use from `+get-sandbox-config`: required create seats equal the number of members with no sandbox, and required active seats equal that number plus the number of existing disabled sandboxes to enable. Require `sandboxQuota.cluster.remaining` to cover required create seats and `sandboxQuota.active.remaining` to cover required active seats. Treat a missing or non-numeric quota value as unverified rather than assuming capacity; `sandboxActiveSeatsLimit` is the limit, not the remaining count.
|
|
255
|
+
|
|
256
|
+
Sandbox preflight protects sandbox writes without becoming a gate on channel binding. When sandbox management is disabled, verified capacity cannot cover the plan, quota values are unverified, a person maps to multiple sandboxes, or ownership cannot be matched exactly, mark the affected person as `sandbox_readiness_blocked` and show the reason in the phase-2 plan. Require confirmation that the channel binding may succeed while the sandbox remains not ready, and do not issue an unsafe sandbox write. A sandbox preflight failure does not cancel a confirmed channel binding.
|
|
257
|
+
|
|
258
|
+
The phase-2 confirmation also authorizes the listed sandbox readiness writes. If the roster, target user IDs, sandbox IDs, required actions, or limits change, regenerate every affected dry-run and obtain a new confirmation.
|
|
259
|
+
|
|
260
|
+
7. Validate the whole 1-100 item binding batch and show the complete phase-2 plan before asking for confirmation:
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
ae-cli --dry-run system +bind-feishu-users \
|
|
264
|
+
--channel-id <channel-id> \
|
|
265
|
+
--endpoint-id <verified-endpoint-id> \
|
|
266
|
+
--bindings @bindings.json \
|
|
267
|
+
--default-agent-id <optional-agent-id>
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
For an explicitly approved private-chat-only batch, omit every `union_id` and `--endpoint-id`, then add `--private-only true`.
|
|
271
|
+
|
|
272
|
+
8. After phase-2 confirmation, execute the same binding command without `--dry-run`. The command validates the whole batch before the first request, then processes users sequentially. An item-level validation or conflict failure does not stop later users and no automatic rollback is attempted; a final 401 or any 403 stops the batch because authentication or authorization is no longer valid. Inspect `succeeded`, `failed`, and every result's `stage` (`binding` or `agent_assignment`). Retry only failed items with a new file; the Feishu binding PUT is idempotent.
|
|
273
|
+
|
|
274
|
+
9. Only users whose channel binding succeeded proceed to sandbox readiness. A result with `binding_id` and `stage: "agent_assignment"` still proceeds to sandbox readiness. The channel binding exists, so report the Agent-assignment failure separately. Refetch `+list-sandboxes` after binding, then apply the confirmed plan against the refreshed state:
|
|
275
|
+
|
|
276
|
+
- If preflight marked the person as `sandbox_readiness_blocked`, preserve the successful binding, report `channel_bound_sandbox_not_ready`, and wait for a new safe dry-run and confirmation before any later sandbox write.
|
|
277
|
+
- If the exact user's sandbox is already `enabled === true`, make no write.
|
|
278
|
+
- If the confirmed existing sandbox is still present with `enabled === false`, execute its prepared `+set-sandbox-enabled --enabled true` command.
|
|
279
|
+
- If the confirmed user still has no sandbox, execute the prepared `+batch-create-sandboxes` command. The create call must return a successful result and a `sandboxId` for that database `userId`.
|
|
280
|
+
- Refetch after creation. The batch-create contract creates an enabled personal sandbox and binds it to the requested database `userId`. If the new sandbox instead has `enabled === false`, run a fresh `+set-sandbox-enabled` dry-run and obtain supplemental explicit confirmation for the returned `sandboxId` before enabling it.
|
|
281
|
+
- If the refreshed ownership or sandbox ID conflicts with the confirmed plan, stop that person's sandbox action and request a new dry-run and confirmation.
|
|
282
|
+
|
|
283
|
+
Inspect every create or enable result. A final 401 or any 403 stops remaining sandbox writes. A quota, validation, conflict, or other item failure does not undo earlier bindings or prevent independent confirmed users from being processed. Record and report that person as `channel_bound_sandbox_not_ready`. Do not roll back a successful channel binding when sandbox preparation fails.
|
|
284
|
+
|
|
285
|
+
10. Read back the final channel and sandbox state:
|
|
286
|
+
|
|
287
|
+
```bash
|
|
288
|
+
ae-cli system channel binding list --channel-id <channel-id>
|
|
289
|
+
ae-cli system +list-sandboxes
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
For every successful channel binding, verify that exactly one sandbox contains the Agent member's database `userId` in `boundUsers[].userId` and returns `enabled === true`. Report separate counts and identities for `channel_bound_sandbox_ready`, `channel_bound_sandbox_not_ready`, and `channel_binding_failed`; do not collapse partial readiness into overall success.
|
|
293
|
+
|
|
294
|
+
For one-off maintenance, use `channel binding bind-feishu`, `channel binding set-agent`, and `channel binding unbind` with the same dry-run, confirmation, and read-back rules. Clear a binding's private-chat Agent and restore system-default resolution with `channel binding set-agent --binding-id <binding-id> --clear`.
|
|
295
|
+
|
|
296
|
+
## Transport Status
|
|
297
|
+
|
|
298
|
+
Transition status: transitional
|
|
299
|
+
Owning module: te-agent channel management
|
|
300
|
+
Current transport: signed REST at `/api/cli/channel/v1/**`
|
|
301
|
+
Gateway target: channel Capability Gateway
|
|
302
|
+
Review after: 2026-10-24
|
|
303
|
+
Exit condition: migrate when equivalent schemas, authorization, risk, dry-run, batch orchestration, and output contracts are stable.
|
package/dist/auth-2WTQOP77.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getToken,
|
|
3
|
-
invalidateAccessTokenForRetry,
|
|
4
|
-
resolveHost,
|
|
5
|
-
validateToken
|
|
6
|
-
} from "./chunk-ZZUOD757.js";
|
|
7
|
-
import "./chunk-LYVNONC4.js";
|
|
8
|
-
import "./chunk-QGM4M3NI.js";
|
|
9
|
-
export {
|
|
10
|
-
getToken,
|
|
11
|
-
invalidateAccessTokenForRetry,
|
|
12
|
-
resolveHost,
|
|
13
|
-
validateToken
|
|
14
|
-
};
|
|
@@ -1,312 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
normalizeRiskLevel,
|
|
3
|
-
requiresConfirmation
|
|
4
|
-
} from "./chunk-OO6XR6LK.js";
|
|
5
|
-
import {
|
|
6
|
-
printError,
|
|
7
|
-
printOutput
|
|
8
|
-
} from "./chunk-GT46FPXN.js";
|
|
9
|
-
import {
|
|
10
|
-
CapabilityGatewayError,
|
|
11
|
-
dryRunCapability,
|
|
12
|
-
executeCapability,
|
|
13
|
-
inspectCapability,
|
|
14
|
-
listCapabilities,
|
|
15
|
-
validateCapability
|
|
16
|
-
} from "./chunk-ILIU36SU.js";
|
|
17
|
-
import {
|
|
18
|
-
findGatewayDomain,
|
|
19
|
-
resolveGatewayDomain
|
|
20
|
-
} from "./chunk-PTE56QPL.js";
|
|
21
|
-
import "./chunk-6EIJSNBD.js";
|
|
22
|
-
import "./chunk-4SGZG4XY.js";
|
|
23
|
-
import {
|
|
24
|
-
SecureStoreAuthError,
|
|
25
|
-
resolveHost,
|
|
26
|
-
safeJsonParse
|
|
27
|
-
} from "./chunk-ZZUOD757.js";
|
|
28
|
-
import {
|
|
29
|
-
PermissionError
|
|
30
|
-
} from "./chunk-UW5UN47B.js";
|
|
31
|
-
import "./chunk-LYVNONC4.js";
|
|
32
|
-
import "./chunk-QGM4M3NI.js";
|
|
33
|
-
|
|
34
|
-
// src/commands/capability/index.ts
|
|
35
|
-
import * as readline from "readline";
|
|
36
|
-
|
|
37
|
-
// src/commands/capability/helpers.ts
|
|
38
|
-
import { readFileSync } from "fs";
|
|
39
|
-
var CapabilityCommandValidationError = class extends Error {
|
|
40
|
-
constructor(message, hint, code) {
|
|
41
|
-
super(message);
|
|
42
|
-
this.hint = hint;
|
|
43
|
-
this.code = code;
|
|
44
|
-
this.name = "CapabilityCommandValidationError";
|
|
45
|
-
}
|
|
46
|
-
hint;
|
|
47
|
-
code;
|
|
48
|
-
};
|
|
49
|
-
function capabilityNamespace(capabilityId) {
|
|
50
|
-
return capabilityId.split(".")[0] ?? "";
|
|
51
|
-
}
|
|
52
|
-
function resolveCapabilityGatewayDomain(capabilityId, domainOverride) {
|
|
53
|
-
const cliDomain = domainOverride?.trim() || capabilityNamespace(capabilityId);
|
|
54
|
-
return resolveCapabilityListDomain(cliDomain);
|
|
55
|
-
}
|
|
56
|
-
function resolveCapabilityListDomain(cliDomain) {
|
|
57
|
-
if (!cliDomain) {
|
|
58
|
-
throw new CapabilityCommandValidationError(
|
|
59
|
-
"Cannot determine the capability domain.",
|
|
60
|
-
"Pass --domain <domain>."
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
return resolveGatewayDomain(cliDomain, findGatewayDomain(cliDomain) ?? cliDomain);
|
|
64
|
-
}
|
|
65
|
-
function parseOptionalProjectId(raw) {
|
|
66
|
-
if (raw === void 0) {
|
|
67
|
-
return void 0;
|
|
68
|
-
}
|
|
69
|
-
const value = raw.trim();
|
|
70
|
-
if (!/^[1-9]\d*$/.test(value)) {
|
|
71
|
-
throw new CapabilityCommandValidationError(
|
|
72
|
-
"--project-id must be a positive integer.",
|
|
73
|
-
"Example: --project-id 1"
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
const projectId = Number(value);
|
|
77
|
-
if (!Number.isSafeInteger(projectId)) {
|
|
78
|
-
throw new CapabilityCommandValidationError(
|
|
79
|
-
"--project-id is outside the supported integer range.",
|
|
80
|
-
"Pass a positive safe integer."
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
return projectId;
|
|
84
|
-
}
|
|
85
|
-
function normalizeCapabilityList(value) {
|
|
86
|
-
if (!Array.isArray(value)) {
|
|
87
|
-
throw new Error("Capability gateway returned an invalid catalog: expected an array.");
|
|
88
|
-
}
|
|
89
|
-
return value.filter((item) => {
|
|
90
|
-
return item !== null && typeof item === "object" && typeof item.id === "string";
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
function filterCapabilities(capabilities, domain, query) {
|
|
94
|
-
const prefix = `${domain.toLowerCase()}.`;
|
|
95
|
-
const terms = query?.trim().toLowerCase().split(/\s+/).filter(Boolean) ?? [];
|
|
96
|
-
return capabilities.filter((capability) => {
|
|
97
|
-
if (!capability.id.toLowerCase().startsWith(prefix)) {
|
|
98
|
-
return false;
|
|
99
|
-
}
|
|
100
|
-
if (terms.length === 0) {
|
|
101
|
-
return true;
|
|
102
|
-
}
|
|
103
|
-
const searchable = `${capability.id} ${capability.description ?? ""}`.toLowerCase();
|
|
104
|
-
return terms.every((term) => searchable.includes(term));
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
function parseCapabilityInput(raw) {
|
|
108
|
-
if (!raw) {
|
|
109
|
-
return {};
|
|
110
|
-
}
|
|
111
|
-
const trimmed = raw.trim();
|
|
112
|
-
let parsed;
|
|
113
|
-
try {
|
|
114
|
-
if (trimmed === "-") {
|
|
115
|
-
parsed = safeJsonParse(readFileSync(0, "utf8"));
|
|
116
|
-
} else if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
117
|
-
parsed = safeJsonParse(trimmed);
|
|
118
|
-
} else {
|
|
119
|
-
const path = trimmed.startsWith("@") ? trimmed.slice(1) : trimmed;
|
|
120
|
-
parsed = safeJsonParse(readFileSync(path, "utf8"));
|
|
121
|
-
}
|
|
122
|
-
} catch (error) {
|
|
123
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
124
|
-
throw new CapabilityCommandValidationError(
|
|
125
|
-
`Invalid capability input: ${message}`,
|
|
126
|
-
"Pass an inline JSON object, a JSON file path, @<path>, or - for stdin."
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
130
|
-
throw new CapabilityCommandValidationError(
|
|
131
|
-
"Capability input must be a JSON object.",
|
|
132
|
-
`Example: --input '{"project_id":1}'`
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
const input = parsed;
|
|
136
|
-
if (Object.prototype.hasOwnProperty.call(input, "projectId")) {
|
|
137
|
-
throw new CapabilityCommandValidationError(
|
|
138
|
-
"Unsupported capability input field: projectId.",
|
|
139
|
-
"Use the canonical snake_case field project_id.",
|
|
140
|
-
"UNSUPPORTED_INPUT_FIELDS"
|
|
141
|
-
);
|
|
142
|
-
}
|
|
143
|
-
return input;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// src/commands/capability/index.ts
|
|
147
|
-
function registerCapability(program) {
|
|
148
|
-
const capability = program.command("capability").description("Discover and invoke capability gateway operations").action(() => {
|
|
149
|
-
printError("validation", "Missing subcommand.", "Run: ae-cli capability --help");
|
|
150
|
-
process.exitCode = 1;
|
|
151
|
-
});
|
|
152
|
-
capability.command("list").description("List capability summaries in a domain").requiredOption("--domain <domain>", "Capability namespace, such as analysis or metadata").option("--project-id <project-id>", "Filter by project membership, permissions, and enabled features").action(async (opts) => {
|
|
153
|
-
await executeAndPrint(program, async (host) => {
|
|
154
|
-
const gatewayDomain = resolveCapabilityListDomain(opts.domain);
|
|
155
|
-
const projectId = parseOptionalProjectId(opts.projectId);
|
|
156
|
-
const catalog = normalizeCapabilityList(await listCapabilities(host, gatewayDomain, projectId));
|
|
157
|
-
const capabilities = filterCapabilities(catalog, opts.domain);
|
|
158
|
-
return { domain: opts.domain, count: capabilities.length, capabilities };
|
|
159
|
-
});
|
|
160
|
-
}).addHelpText(
|
|
161
|
-
"after",
|
|
162
|
-
"\nExamples:\n ae-cli capability list --domain analysis\n ae-cli capability list --domain analysis --project-id 1"
|
|
163
|
-
);
|
|
164
|
-
capability.command("search").description("Search capability IDs and descriptions in a domain").argument("<query>", "Case-insensitive search terms").requiredOption("--domain <domain>", "Capability namespace, such as analysis or metadata").option("--project-id <project-id>", "Filter by project membership, permissions, and enabled features").action(async (query, opts) => {
|
|
165
|
-
await executeAndPrint(program, async (host) => {
|
|
166
|
-
const gatewayDomain = resolveCapabilityListDomain(opts.domain);
|
|
167
|
-
const projectId = parseOptionalProjectId(opts.projectId);
|
|
168
|
-
const catalog = normalizeCapabilityList(await listCapabilities(host, gatewayDomain, projectId));
|
|
169
|
-
const capabilities = filterCapabilities(catalog, opts.domain, query);
|
|
170
|
-
return { domain: opts.domain, query, count: capabilities.length, capabilities };
|
|
171
|
-
});
|
|
172
|
-
}).addHelpText(
|
|
173
|
-
"after",
|
|
174
|
-
'\nExamples:\n ae-cli capability search "dashboard list" --domain analysis\n ae-cli capability search "dashboard list" --domain analysis --project-id 1'
|
|
175
|
-
);
|
|
176
|
-
capability.command("inspect").description("Inspect one capability schema, risk, auth, and output metadata").argument("<capability-id>", "Capability ID, such as analysis.report.list").option("--domain <domain>", "Override the capability namespace used for gateway routing").option("--project-id <project-id>", "Check availability in a project before returning metadata").action(async (capabilityId, opts) => {
|
|
177
|
-
await executeAndPrint(program, async (host) => {
|
|
178
|
-
const gatewayDomain = resolveCapabilityGatewayDomain(capabilityId, opts.domain);
|
|
179
|
-
return inspectCapability(host, gatewayDomain, capabilityId, parseOptionalProjectId(opts.projectId));
|
|
180
|
-
});
|
|
181
|
-
}).addHelpText(
|
|
182
|
-
"after",
|
|
183
|
-
"\nExamples:\n ae-cli capability inspect analysis.dashboard.list\n ae-cli capability inspect analysis.dashboard.list --project-id 1"
|
|
184
|
-
);
|
|
185
|
-
capability.command("validate").description(
|
|
186
|
-
"Validate and normalize capability input only (schema/params). Does not execute business logic. Use when crafting complex payloads (nested objects, qp, share/member payloads) before dry-run/run. Success data: valid, capability_id, normalized_input. Unlike dry-run, does not return risk/output_mode/supports_cancel preview."
|
|
187
|
-
).argument("<capability-id>", "Capability ID, such as metadata.data_table.sql_write").option("--domain <domain>", "Override the capability namespace used for gateway routing").option("--input <json-or-path>", "Input JSON object, file path, @<path>, or - for stdin").action(async (capabilityId, opts) => {
|
|
188
|
-
await executeAndPrint(program, async (host) => {
|
|
189
|
-
const gatewayDomain = resolveCapabilityGatewayDomain(capabilityId, opts.domain);
|
|
190
|
-
return validateCapability(host, gatewayDomain, capabilityId, parseCapabilityInput(opts.input));
|
|
191
|
-
});
|
|
192
|
-
}).addHelpText(
|
|
193
|
-
"after",
|
|
194
|
-
`
|
|
195
|
-
When to use:
|
|
196
|
-
Prefer validate while iterating complex input (required fields, types, qp shape), then run.
|
|
197
|
-
Prefer dry-run alone when you need risk / output mode / cancelability or a delete gate.
|
|
198
|
-
Motto: validate = fix params; dry-run = confirm ready to run.
|
|
199
|
-
Do not stack validate + dry-run on the same final input by default (dry-run already validates params).
|
|
200
|
-
Neither mutates business data. Curated gateway commands: global --validate / --dry-run.
|
|
201
|
-
|
|
202
|
-
Examples:
|
|
203
|
-
ae-cli capability validate metadata.data_table.sql_write --input '{"project_id":1,"operation":"create",...}'
|
|
204
|
-
ae-cli metadata data-table sql-write --project-id 1 ... --validate
|
|
205
|
-
ae-cli capability validate analysis.project_space.create --input input.json`
|
|
206
|
-
);
|
|
207
|
-
capability.command("dry-run").description(
|
|
208
|
-
"Full pre-execution confirmation without running business logic: validates/normalizes input and returns risk, output_mode, and supports_cancel. Use alone when you need that preview or a delete gate (do not stack after validate on the same final input by default). Global --dry-run on curated/capability run also hits this endpoint."
|
|
209
|
-
).argument("<capability-id>", "Capability ID, such as analysis.query.export").option("--domain <domain>", "Override the capability namespace used for gateway routing").option("--input <json-or-path>", "Input JSON object, file path, @<path>, or - for stdin").action(async (capabilityId, opts) => {
|
|
210
|
-
await executeAndPrint(program, async (host) => {
|
|
211
|
-
const gatewayDomain = resolveCapabilityGatewayDomain(capabilityId, opts.domain);
|
|
212
|
-
return dryRunCapability(host, gatewayDomain, capabilityId, parseCapabilityInput(opts.input));
|
|
213
|
-
});
|
|
214
|
-
}).addHelpText(
|
|
215
|
-
"after",
|
|
216
|
-
`
|
|
217
|
-
Examples:
|
|
218
|
-
ae-cli capability dry-run analysis.dashboard.list --input '{"project_id":1}'
|
|
219
|
-
ae-cli capability dry-run analysis.dashboard.list --input input.json`
|
|
220
|
-
);
|
|
221
|
-
capability.command("run").description("Execute one capability by ID").argument("<capability-id>", "Capability ID, such as analysis.report.list").option("--domain <domain>", "Override the capability namespace used for gateway routing").option("--input <json-or-path>", "Input JSON object, file path, @<path>, or - for stdin").action(async (capabilityId, opts) => {
|
|
222
|
-
await executeAndPrint(program, async (host, globalOpts) => {
|
|
223
|
-
const gatewayDomain = resolveCapabilityGatewayDomain(capabilityId, opts.domain);
|
|
224
|
-
const input = parseCapabilityInput(opts.input);
|
|
225
|
-
if (globalOpts.validate && globalOpts.dryRun) {
|
|
226
|
-
throw new CapabilityCommandValidationError(
|
|
227
|
-
"Cannot combine --validate and --dry-run.",
|
|
228
|
-
"Use --validate to fix parameters; use --dry-run as the pre-execution confirmation."
|
|
229
|
-
);
|
|
230
|
-
}
|
|
231
|
-
if (globalOpts.validate) {
|
|
232
|
-
return validateCapability(host, gatewayDomain, capabilityId, input);
|
|
233
|
-
}
|
|
234
|
-
if (globalOpts.dryRun) {
|
|
235
|
-
return dryRunCapability(host, gatewayDomain, capabilityId, input);
|
|
236
|
-
}
|
|
237
|
-
if (!globalOpts.yes) {
|
|
238
|
-
const metadata = await inspectCapability(host, gatewayDomain, capabilityId);
|
|
239
|
-
const risk = normalizeRiskLevel(typeof metadata?.risk === "string" ? metadata.risk : void 0);
|
|
240
|
-
if (requiresConfirmation(risk)) {
|
|
241
|
-
const confirmed = await confirm(`This capability is marked ${risk} (${capabilityId}). Continue?`);
|
|
242
|
-
if (!confirmed) {
|
|
243
|
-
process.stderr.write("Aborted.\n");
|
|
244
|
-
return void 0;
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
return executeCapability(host, gatewayDomain, capabilityId, input);
|
|
249
|
-
});
|
|
250
|
-
}).addHelpText(
|
|
251
|
-
"after",
|
|
252
|
-
`
|
|
253
|
-
Examples:
|
|
254
|
-
ae-cli capability run analysis.dashboard.list --input '{"project_id":1}'
|
|
255
|
-
ae-cli capability run analysis.dashboard.list --input input.json`
|
|
256
|
-
);
|
|
257
|
-
}
|
|
258
|
-
async function executeAndPrint(program, action) {
|
|
259
|
-
const globalOpts = program.opts();
|
|
260
|
-
const host = resolveHost(globalOpts.host);
|
|
261
|
-
if (!host) {
|
|
262
|
-
printError("config", "No AE host configured.", "Run: ae-cli config set-host <url>");
|
|
263
|
-
process.exitCode = 1;
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
try {
|
|
267
|
-
const result = await action(host, globalOpts);
|
|
268
|
-
if (result !== void 0) {
|
|
269
|
-
await printOutput(result, globalOpts.format ?? "json", globalOpts.jq);
|
|
270
|
-
}
|
|
271
|
-
} catch (error) {
|
|
272
|
-
if (error?.type === "validation" && String(error?.message || "").includes("--jq")) {
|
|
273
|
-
printError("validation", error.message, error.hint);
|
|
274
|
-
process.exitCode = 1;
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
277
|
-
printCapabilityError(error);
|
|
278
|
-
process.exitCode = 1;
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
function printCapabilityError(error) {
|
|
282
|
-
if (error instanceof CapabilityCommandValidationError) {
|
|
283
|
-
printError("validation", error.message, error.hint, error.code);
|
|
284
|
-
return;
|
|
285
|
-
}
|
|
286
|
-
if (error instanceof SecureStoreAuthError) {
|
|
287
|
-
printError("auth", error.message, "Run: ae-cli auth login");
|
|
288
|
-
return;
|
|
289
|
-
}
|
|
290
|
-
if (error instanceof PermissionError) {
|
|
291
|
-
printError("permission", error.message, void 0, error.code);
|
|
292
|
-
return;
|
|
293
|
-
}
|
|
294
|
-
if (error instanceof CapabilityGatewayError) {
|
|
295
|
-
printError("api", error.message, error.hint, error.code);
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
298
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
299
|
-
printError("api", message);
|
|
300
|
-
}
|
|
301
|
-
async function confirm(message) {
|
|
302
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
303
|
-
return new Promise((resolve) => {
|
|
304
|
-
rl.question(`${message} [y/N] `, (answer) => {
|
|
305
|
-
rl.close();
|
|
306
|
-
resolve(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
|
|
307
|
-
});
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
export {
|
|
311
|
-
registerCapability
|
|
312
|
-
};
|