@eliya-oss/agent-slack 0.1.0 → 0.1.2
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/.agents/cli-api.md +65 -65
- package/CHANGELOG.md +12 -0
- package/README.md +7 -6
- package/assets/terminal.svg +8 -8
- package/dist/adapters/keychain/KeychainTokenStore.js +1 -1
- package/dist/adapters/live.js +11 -7
- package/dist/adapters/localhost-oauth/NodeLocalhostOAuthFlow.js +9 -6
- package/dist/adapters/profile-file/FileTokenStore.js +3 -3
- package/dist/application/commands.js +18 -8
- package/dist/application/execute.js +2 -2
- package/dist/cli/args.js +1 -0
- package/dist/cli/metadata.js +44 -39
- package/dist/main.js +4 -1
- package/dist/output/envelope.js +1 -1
- package/dist/output/human.js +287 -0
- package/package.json +5 -3
- package/skills/agent-slack/SKILL.md +13 -13
- package/skills/agent-slack/agents/openai.yaml +2 -2
package/.agents/cli-api.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Technical contract derived from `.agents/prd/001-agent-readable-slack-interface.md`. Product behavior changes belong in PRD first.
|
|
4
4
|
|
|
5
|
-
Working binary name: `
|
|
5
|
+
Working binary name: `agent-slack`. Short alias: `aslk`.
|
|
6
6
|
|
|
7
7
|
Goal: OAuth into Slack, expose everything Slack Web API permissions allow, and give agents stable commands for reading channels, threads, files, users, and search context.
|
|
8
8
|
|
|
@@ -10,18 +10,18 @@ Non-goal for v0: agent-side summarization. The CLI returns clean context; agents
|
|
|
10
10
|
|
|
11
11
|
## Design Rules
|
|
12
12
|
|
|
13
|
-
- Noun-verb commands: `
|
|
13
|
+
- Noun-verb commands: `agent-slack conversation history`, `agent-slack thread get`, `agent-slack api call`.
|
|
14
14
|
- JSON is always available with `--json`; non-TTY stdout defaults to JSON.
|
|
15
15
|
- Data goes to stdout. Progress, warnings, and errors go to stderr.
|
|
16
16
|
- Every command supports `--help --json`; every group supports `describe --json`.
|
|
17
17
|
- Large reads support `--cursor`, `--limit`, `--all`, and `--format ndjson`.
|
|
18
|
-
- Raw Slack Web API access is first-class through `
|
|
18
|
+
- Raw Slack Web API access is first-class through `agent-slack api call`.
|
|
19
19
|
- Read commands are safe by default. Any future write/destructive command needs `--yes`; `api call` blocks known write methods unless `--allow-write`.
|
|
20
20
|
|
|
21
21
|
## Global Flags
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
|
-
|
|
24
|
+
agent-slack [--profile NAME] [--team TEAM_ID] [--token user|bot|admin|app]
|
|
25
25
|
[--json] [--format json|ndjson|table] [--fields FIELD[,FIELD...]]
|
|
26
26
|
[--limit N] [--cursor CURSOR] [--all]
|
|
27
27
|
[--include users,reactions,files,permalinks,threads]
|
|
@@ -37,30 +37,30 @@ Defaults:
|
|
|
37
37
|
## Auth
|
|
38
38
|
|
|
39
39
|
```bash
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
40
|
+
agent-slack auth login
|
|
41
|
+
agent-slack auth login --profile work --scopes channels:read,channels:history --user-scopes search:read.public,search:read.private
|
|
42
|
+
agent-slack auth login --oauth --client-id CLIENT_ID --client-secret CLIENT_SECRET --auth-url-out /tmp/agent-slack-auth-url
|
|
43
|
+
agent-slack auth status
|
|
44
|
+
agent-slack auth scopes
|
|
45
|
+
agent-slack auth profiles list
|
|
46
|
+
agent-slack auth logout --profile work
|
|
47
47
|
```
|
|
48
48
|
|
|
49
49
|
Auth behavior:
|
|
50
50
|
|
|
51
51
|
- Uses Slack OAuth v2 with a localhost callback.
|
|
52
52
|
- Bot scopes go in `scope=...`; user scopes go in `user_scope=...`.
|
|
53
|
-
- Stores profiles outside the project directory. The default store is `~/.config/
|
|
53
|
+
- Stores profiles outside the project directory. The default store is `~/.config/agent-slack/profiles.json`; set `AGENT_SLACK_TOKEN_STORE=keychain` on macOS to keep token secrets in Keychain and only profile metadata on disk.
|
|
54
54
|
- Supports multiple profiles and workspaces.
|
|
55
55
|
- `auth status --json` returns token type, team, enterprise, user/bot identity, granted scopes, and missing recommended scopes.
|
|
56
56
|
|
|
57
57
|
Scope presets:
|
|
58
58
|
|
|
59
59
|
```bash
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
agent-slack auth login --preset read-user
|
|
61
|
+
agent-slack auth login --preset read-bot
|
|
62
|
+
agent-slack auth login --preset assistant-search
|
|
63
|
+
agent-slack auth login --preset admin-read
|
|
64
64
|
```
|
|
65
65
|
|
|
66
66
|
Preset intent:
|
|
@@ -77,31 +77,31 @@ Important Slack boundary: there is no token that means "read all Slack." Slack o
|
|
|
77
77
|
### Workspaces and identity
|
|
78
78
|
|
|
79
79
|
```bash
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
80
|
+
agent-slack auth test
|
|
81
|
+
agent-slack team get
|
|
82
|
+
agent-slack team profile get
|
|
83
|
+
agent-slack enterprise get
|
|
84
84
|
```
|
|
85
85
|
|
|
86
86
|
### Users
|
|
87
87
|
|
|
88
88
|
```bash
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
89
|
+
agent-slack user list [--all]
|
|
90
|
+
agent-slack user get USER_ID
|
|
91
|
+
agent-slack user lookup --email EMAIL
|
|
92
|
+
agent-slack user presence get USER_ID
|
|
93
|
+
agent-slack usergroups list
|
|
94
|
+
agent-slack usergroups users list USERGROUP_ID
|
|
95
95
|
```
|
|
96
96
|
|
|
97
97
|
### Conversations
|
|
98
98
|
|
|
99
99
|
```bash
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
100
|
+
agent-slack conversation list --types public_channel,private_channel,mpim,im [--all]
|
|
101
|
+
agent-slack conversation get CHANNEL_ID
|
|
102
|
+
agent-slack conversation members CHANNEL_ID [--all]
|
|
103
|
+
agent-slack conversation history CHANNEL_ID [--oldest TS] [--latest TS] [--all]
|
|
104
|
+
agent-slack conversation context CHANNEL_ID [--since 24h] [--include users,reactions,files,threads]
|
|
105
105
|
```
|
|
106
106
|
|
|
107
107
|
`conversation context` is the agent-friendly form: normalized messages, hydrated users, thread refs, file refs, reactions, and permalinks in one deterministic payload.
|
|
@@ -109,9 +109,9 @@ slk conversation context CHANNEL_ID [--since 24h] [--include users,reactions,fil
|
|
|
109
109
|
### Threads and messages
|
|
110
110
|
|
|
111
111
|
```bash
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
112
|
+
agent-slack thread get --channel CHANNEL_ID --ts MESSAGE_TS [--include users,reactions,files,permalinks]
|
|
113
|
+
agent-slack message get --channel CHANNEL_ID --ts MESSAGE_TS
|
|
114
|
+
agent-slack message permalink --channel CHANNEL_ID --ts MESSAGE_TS
|
|
115
115
|
```
|
|
116
116
|
|
|
117
117
|
`message get` wraps the Slack history/replies calls needed to fetch one timestamp.
|
|
@@ -119,9 +119,9 @@ slk message permalink --channel CHANNEL_ID --ts MESSAGE_TS
|
|
|
119
119
|
### Search
|
|
120
120
|
|
|
121
121
|
```bash
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
122
|
+
agent-slack search context --query QUERY [--channel-types public_channel,private_channel,mpim,im] [--content-types messages,files,channels]
|
|
123
|
+
agent-slack search messages --query QUERY
|
|
124
|
+
agent-slack search files --query QUERY
|
|
125
125
|
```
|
|
126
126
|
|
|
127
127
|
Prefer `search context` for agents. It maps to Slack's Real-time Search API where available. `search messages` and `search files` are legacy-compatible convenience commands.
|
|
@@ -129,26 +129,26 @@ Prefer `search context` for agents. It maps to Slack's Real-time Search API wher
|
|
|
129
129
|
### Files and attached context
|
|
130
130
|
|
|
131
131
|
```bash
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
132
|
+
agent-slack file list [--channel CHANNEL_ID] [--user USER_ID] [--all]
|
|
133
|
+
agent-slack file get FILE_ID
|
|
134
|
+
agent-slack file download FILE_ID --out PATH
|
|
135
|
+
agent-slack file comments list FILE_ID
|
|
136
136
|
```
|
|
137
137
|
|
|
138
138
|
### Other readable Slack surfaces
|
|
139
139
|
|
|
140
140
|
```bash
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
141
|
+
agent-slack reaction get --channel CHANNEL_ID --ts MESSAGE_TS
|
|
142
|
+
agent-slack pin list CHANNEL_ID
|
|
143
|
+
agent-slack bookmark list CHANNEL_ID
|
|
144
|
+
agent-slack canvas list [--channel CHANNEL_ID]
|
|
145
|
+
agent-slack canvas get CANVAS_ID
|
|
146
|
+
agent-slack list list [--channel CHANNEL_ID]
|
|
147
|
+
agent-slack list get LIST_ID
|
|
148
|
+
agent-slack emoji list
|
|
149
|
+
agent-slack reminder list
|
|
150
|
+
agent-slack star list
|
|
151
|
+
agent-slack dnd status [USER_ID]
|
|
152
152
|
```
|
|
153
153
|
|
|
154
154
|
## Generic Web API Escape Hatch
|
|
@@ -156,11 +156,11 @@ slk dnd status [USER_ID]
|
|
|
156
156
|
This is what gives full Web API coverage.
|
|
157
157
|
|
|
158
158
|
```bash
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
cat payload.json |
|
|
159
|
+
agent-slack api methods list [--family conversations]
|
|
160
|
+
agent-slack api method describe conversations.history
|
|
161
|
+
agent-slack api call conversations.history --payload '{"channel":"C123","limit":50}'
|
|
162
|
+
agent-slack api call conversations.history --payload @payload.json
|
|
163
|
+
cat payload.json | agent-slack api call conversations.history -
|
|
164
164
|
```
|
|
165
165
|
|
|
166
166
|
Rules:
|
|
@@ -235,10 +235,10 @@ Rate-limit behavior:
|
|
|
235
235
|
## Schema Introspection
|
|
236
236
|
|
|
237
237
|
```bash
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
238
|
+
agent-slack describe --json
|
|
239
|
+
agent-slack conversation describe --json
|
|
240
|
+
agent-slack conversation history --help --json
|
|
241
|
+
agent-slack api method describe conversations.replies --json
|
|
242
242
|
```
|
|
243
243
|
|
|
244
244
|
Schema output includes:
|
|
@@ -256,25 +256,25 @@ Schema output includes:
|
|
|
256
256
|
Read a thread:
|
|
257
257
|
|
|
258
258
|
```bash
|
|
259
|
-
|
|
259
|
+
agent-slack thread get --channel C123 --ts 1710000000.000100 --include users,reactions,files,permalinks --json
|
|
260
260
|
```
|
|
261
261
|
|
|
262
262
|
Prepare channel-summary input:
|
|
263
263
|
|
|
264
264
|
```bash
|
|
265
|
-
|
|
265
|
+
agent-slack conversation context C123 --since 24h --include users,reactions,files,threads --format ndjson
|
|
266
266
|
```
|
|
267
267
|
|
|
268
268
|
Search before reading:
|
|
269
269
|
|
|
270
270
|
```bash
|
|
271
|
-
|
|
271
|
+
agent-slack search context --query "latest on project atlas" --content-types messages,files --json
|
|
272
272
|
```
|
|
273
273
|
|
|
274
274
|
Call a new Slack method before the CLI has a wrapper:
|
|
275
275
|
|
|
276
276
|
```bash
|
|
277
|
-
|
|
277
|
+
agent-slack api call some.newMethod --payload @request.json --json
|
|
278
278
|
```
|
|
279
279
|
|
|
280
280
|
## Build Order
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @eliya-oss/agent-slack
|
|
2
2
|
|
|
3
|
+
## 0.1.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Fix the displayed CLI version and make the human command catalog copy-safe in terminals.
|
|
8
|
+
|
|
9
|
+
## 0.1.1
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- Expose `agent-slack` as the primary binary, add the `aslk` alias, and render human-friendly TTY output while preserving JSON for agent and piped runs.
|
|
14
|
+
|
|
3
15
|
## 0.1.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
# Agent Slack
|
|
4
4
|
|
|
5
|
-
Slack context for AI agents, exposed through the `
|
|
5
|
+
Slack context for AI agents, exposed through the `agent-slack` CLI.
|
|
6
|
+
Short alias: `aslk`.
|
|
6
7
|
|
|
7
8
|
```bash
|
|
8
9
|
npm install -g @eliya-oss/agent-slack
|
|
@@ -15,11 +16,11 @@ npm install -g @eliya-oss/agent-slack
|
|
|
15
16
|
## Usage
|
|
16
17
|
|
|
17
18
|
```bash
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
agent-slack auth login --oauth --client-id "$SLACK_CLIENT_ID" --client-secret "$SLACK_CLIENT_SECRET" --json
|
|
20
|
+
agent-slack conversation history C123 --limit 50 --json
|
|
21
|
+
agent-slack thread get --channel C123 --ts 1710000000.000100 --include users,permalinks --json
|
|
22
|
+
agent-slack conversation context C123 --include users,threads,permalinks --format ndjson
|
|
23
|
+
agent-slack api call conversations.info --payload '{"channel":"C123"}' --json
|
|
23
24
|
```
|
|
24
25
|
|
|
25
26
|
## Skill
|
package/assets/terminal.svg
CHANGED
|
@@ -13,20 +13,20 @@
|
|
|
13
13
|
<text x="78" y="116" fill="#E5E7EB" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="18">npm install -g @eliya-oss/agent-slack</text>
|
|
14
14
|
|
|
15
15
|
<text x="54" y="162" fill="#22C55E" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="18">$</text>
|
|
16
|
-
<text x="78" y="162" fill="#E5E7EB" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="18">
|
|
17
|
-
<text x="78" y="196" fill="#
|
|
16
|
+
<text x="78" y="162" fill="#E5E7EB" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="18">aslk auth status</text>
|
|
17
|
+
<text x="78" y="196" fill="#86EFAC" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="16">ok auth.status profile work | team T123</text>
|
|
18
18
|
|
|
19
19
|
<text x="54" y="250" fill="#22C55E" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="18">$</text>
|
|
20
|
-
<text x="78" y="250" fill="#E5E7EB" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="18">
|
|
21
|
-
<text x="78" y="284" fill="#FDE68A" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="16">
|
|
22
|
-
<text x="78" y="314" fill="#FDE68A" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="16">
|
|
20
|
+
<text x="78" y="250" fill="#E5E7EB" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="18">aslk conversation history C123 --limit 3</text>
|
|
21
|
+
<text x="78" y="284" fill="#FDE68A" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="16">ts user text</text>
|
|
22
|
+
<text x="78" y="314" fill="#FDE68A" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="16">1710000000.000100 U123 Ship status?</text>
|
|
23
23
|
|
|
24
24
|
<text x="54" y="368" fill="#22C55E" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="18">$</text>
|
|
25
|
-
<text x="78" y="368" fill="#E5E7EB" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="18">
|
|
25
|
+
<text x="78" y="368" fill="#E5E7EB" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="18">agent-slack thread get --channel C123 --ts 1710000000.000100 --include users --json</text>
|
|
26
26
|
<text x="78" y="402" fill="#C4B5FD" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="16">{ "ok": true, "method": "conversations.replies", "data": { "messages": [ ... ] } }</text>
|
|
27
27
|
|
|
28
28
|
<text x="54" y="456" fill="#22C55E" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="18">$</text>
|
|
29
|
-
<text x="78" y="456" fill="#E5E7EB" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="18">
|
|
29
|
+
<text x="78" y="456" fill="#E5E7EB" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="18">agent-slack api call conversations.info --payload '{"channel":"C123"}' --json</text>
|
|
30
30
|
<rect x="54" y="488" width="210" height="28" rx="6" fill="#122034"/>
|
|
31
|
-
<text x="70" y="508" fill="#67E8F9" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="14"
|
|
31
|
+
<text x="70" y="508" fill="#67E8F9" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="14">--json stays parseable</text>
|
|
32
32
|
</svg>
|
|
@@ -13,7 +13,7 @@ export class KeychainTokenStore {
|
|
|
13
13
|
this.secrets = secrets;
|
|
14
14
|
}
|
|
15
15
|
static fromEnv(env = process.env) {
|
|
16
|
-
const configDir = env.SLK_CONFIG_DIR ?? join(env.HOME ?? process.cwd(), ".config", "
|
|
16
|
+
const configDir = env.AGENT_SLACK_CONFIG_DIR ?? env.SLK_CONFIG_DIR ?? join(env.HOME ?? process.cwd(), ".config", "agent-slack");
|
|
17
17
|
return new KeychainTokenStore(join(configDir, "profiles.keychain.json"));
|
|
18
18
|
}
|
|
19
19
|
async getProfile(name) {
|
package/dist/adapters/live.js
CHANGED
|
@@ -4,10 +4,14 @@ import { KeychainTokenStore } from "./keychain/KeychainTokenStore.js";
|
|
|
4
4
|
import { NodeLocalhostOAuthFlow } from "./localhost-oauth/NodeLocalhostOAuthFlow.js";
|
|
5
5
|
import { FileTokenStore } from "./profile-file/FileTokenStore.js";
|
|
6
6
|
import { SlackSdkWebApi } from "./slack-sdk/SlackSdkWebApi.js";
|
|
7
|
-
export const createLiveServices = (env = process.env) =>
|
|
8
|
-
tokenStore
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
export const createLiveServices = (env = process.env) => {
|
|
8
|
+
const tokenStore = env.AGENT_SLACK_TOKEN_STORE ?? env.SLK_TOKEN_STORE;
|
|
9
|
+
const slackApiUrl = env.AGENT_SLACK_API_BASE_URL ?? env.SLK_SLACK_API_BASE_URL;
|
|
10
|
+
return {
|
|
11
|
+
tokenStore: tokenStore === "keychain" ? KeychainTokenStore.fromEnv(env) : FileTokenStore.fromEnv(env),
|
|
12
|
+
oauthFlow: NodeLocalhostOAuthFlow.fromEnv(env),
|
|
13
|
+
fileDownloader: new HttpFileDownloader(),
|
|
14
|
+
slackWebApi: new SlackSdkWebApi(slackApiUrl === undefined ? {} : { slackApiUrl }),
|
|
15
|
+
methodCatalog: new BundledMethodCatalog()
|
|
16
|
+
};
|
|
17
|
+
};
|
|
@@ -9,14 +9,17 @@ export class NodeLocalhostOAuthFlow {
|
|
|
9
9
|
this.options = options;
|
|
10
10
|
}
|
|
11
11
|
static fromEnv(env = process.env) {
|
|
12
|
-
const apiBaseUrl = env.SLK_SLACK_API_BASE_URL;
|
|
12
|
+
const apiBaseUrl = env.AGENT_SLACK_API_BASE_URL ?? env.SLK_SLACK_API_BASE_URL;
|
|
13
13
|
const emulatorBaseUrl = apiBaseUrl?.replace(/\/api\/?$/, "");
|
|
14
|
+
const redirectHost = env.AGENT_SLACK_OAUTH_REDIRECT_HOST ?? env.SLK_OAUTH_REDIRECT_HOST;
|
|
15
|
+
const redirectPath = env.AGENT_SLACK_OAUTH_REDIRECT_PATH ?? env.SLK_OAUTH_REDIRECT_PATH;
|
|
16
|
+
const timeoutMs = env.AGENT_SLACK_OAUTH_TIMEOUT_MS ?? env.SLK_OAUTH_TIMEOUT_MS;
|
|
14
17
|
return new NodeLocalhostOAuthFlow({
|
|
15
|
-
authorizeUrl: env.SLK_SLACK_OAUTH_AUTHORIZE_URL ?? (emulatorBaseUrl === undefined ? "https://slack.com/oauth/v2/authorize" : `${emulatorBaseUrl}/oauth/v2/authorize`),
|
|
16
|
-
tokenUrl: env.SLK_SLACK_OAUTH_ACCESS_URL ?? (apiBaseUrl === undefined ? "https://slack.com/api/oauth.v2.access" : `${apiBaseUrl.replace(/\/?$/, "/")}oauth.v2.access`),
|
|
17
|
-
...(
|
|
18
|
-
...(
|
|
19
|
-
...(
|
|
18
|
+
authorizeUrl: env.AGENT_SLACK_OAUTH_AUTHORIZE_URL ?? env.SLK_SLACK_OAUTH_AUTHORIZE_URL ?? (emulatorBaseUrl === undefined ? "https://slack.com/oauth/v2/authorize" : `${emulatorBaseUrl}/oauth/v2/authorize`),
|
|
19
|
+
tokenUrl: env.AGENT_SLACK_OAUTH_ACCESS_URL ?? env.SLK_SLACK_OAUTH_ACCESS_URL ?? (apiBaseUrl === undefined ? "https://slack.com/api/oauth.v2.access" : `${apiBaseUrl.replace(/\/?$/, "/")}oauth.v2.access`),
|
|
20
|
+
...(redirectHost === undefined ? {} : { defaultRedirectHost: redirectHost }),
|
|
21
|
+
...(redirectPath === undefined ? {} : { defaultRedirectPath: redirectPath }),
|
|
22
|
+
...(timeoutMs === undefined ? {} : { defaultTimeoutMs: Number(timeoutMs) })
|
|
20
23
|
});
|
|
21
24
|
}
|
|
22
25
|
async login(input) {
|
|
@@ -9,15 +9,15 @@ export class FileTokenStore {
|
|
|
9
9
|
this.envProfile = envProfile;
|
|
10
10
|
}
|
|
11
11
|
static fromEnv(env = process.env) {
|
|
12
|
-
const configDir = env.SLK_CONFIG_DIR ?? join(env.HOME ?? process.cwd(), ".config", "
|
|
13
|
-
const token = env.SLK_TOKEN ?? env.SLACK_BOT_TOKEN ?? null;
|
|
12
|
+
const configDir = env.AGENT_SLACK_CONFIG_DIR ?? env.SLK_CONFIG_DIR ?? join(env.HOME ?? process.cwd(), ".config", "agent-slack");
|
|
13
|
+
const token = env.AGENT_SLACK_TOKEN ?? env.SLK_TOKEN ?? env.SLACK_BOT_TOKEN ?? null;
|
|
14
14
|
const envProfile = token === null
|
|
15
15
|
? null
|
|
16
16
|
: {
|
|
17
17
|
name: ProfileName.default,
|
|
18
18
|
tokenType: "bot",
|
|
19
19
|
botToken: token,
|
|
20
|
-
scopes: (env.SLK_SCOPES ?? "").split(",").filter(Boolean).map(Scope.make)
|
|
20
|
+
scopes: (env.AGENT_SLACK_SCOPES ?? env.SLK_SCOPES ?? "").split(",").filter(Boolean).map(Scope.make)
|
|
21
21
|
};
|
|
22
22
|
return new FileTokenStore(join(configDir, "profiles.json"), envProfile);
|
|
23
23
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { flagBoolean, flagString, requireFlag, splitCsv } from "../cli/args.js";
|
|
2
|
-
import { describeAllCommands, describeCommandGroup, findCommandMetadata, renderCompletion, renderHumanHelp } from "../cli/metadata.js";
|
|
2
|
+
import { PRIMARY_COMMAND_NAME, describeAllCommands, describeCommandGroup, findCommandMetadata, renderCompletion, renderHumanHelp } from "../cli/metadata.js";
|
|
3
3
|
import { NotAuthenticated, ResourceNotFound, UnsupportedMethod, UsageError } from "../domain/errors.js";
|
|
4
4
|
import { ProfileName, Scope } from "../domain/ids.js";
|
|
5
5
|
import { pagingFrom, successEnvelope, toNdjson } from "../output/envelope.js";
|
|
6
|
+
import { renderHumanEnvelope } from "../output/human.js";
|
|
6
7
|
import { projectFields } from "../output/projection.js";
|
|
7
8
|
import { getProfile, parseTokenType, requireYes, tokenFor } from "./auth.js";
|
|
8
9
|
import { parseJsonPayload, requirePositional } from "./payload.js";
|
|
@@ -147,7 +148,7 @@ export const dispatch = async (parsed, services) => {
|
|
|
147
148
|
};
|
|
148
149
|
const describePath = (path, error) => {
|
|
149
150
|
const metadata = findCommandMetadata(path);
|
|
150
|
-
const value = metadata ?? describeCommandGroup(path[0] ??
|
|
151
|
+
const value = metadata ?? describeCommandGroup(path[0] ?? PRIMARY_COMMAND_NAME);
|
|
151
152
|
if (error !== null && metadata === null) {
|
|
152
153
|
throw error;
|
|
153
154
|
}
|
|
@@ -184,7 +185,7 @@ const authCommand = async (parsed, services) => {
|
|
|
184
185
|
return methodCall(parsed, services, { method: "auth.test", payload: {} });
|
|
185
186
|
}
|
|
186
187
|
if (second === "login") {
|
|
187
|
-
const token = flagString(parsed, "token") ?? process.env.SLK_TOKEN ?? process.env.SLACK_BOT_TOKEN;
|
|
188
|
+
const token = flagString(parsed, "token") ?? process.env.AGENT_SLACK_TOKEN ?? process.env.SLK_TOKEN ?? process.env.SLACK_BOT_TOKEN;
|
|
188
189
|
if (token !== undefined && !flagBoolean(parsed, "oauth")) {
|
|
189
190
|
const profile = {
|
|
190
191
|
name: ProfileName.make(profileName),
|
|
@@ -195,10 +196,10 @@ const authCommand = async (parsed, services) => {
|
|
|
195
196
|
await services.tokenStore.setProfile(profile);
|
|
196
197
|
return { method: "auth.login", profile, stdoutValue: sanitizeProfile(profile) };
|
|
197
198
|
}
|
|
198
|
-
const clientId = flagString(parsed, "client-id") ?? process.env.SLK_CLIENT_ID;
|
|
199
|
-
const clientSecret = flagString(parsed, "client-secret") ?? process.env.SLK_CLIENT_SECRET;
|
|
199
|
+
const clientId = flagString(parsed, "client-id") ?? process.env.AGENT_SLACK_CLIENT_ID ?? process.env.SLK_CLIENT_ID ?? process.env.SLACK_CLIENT_ID;
|
|
200
|
+
const clientSecret = flagString(parsed, "client-secret") ?? process.env.AGENT_SLACK_CLIENT_SECRET ?? process.env.SLK_CLIENT_SECRET ?? process.env.SLACK_CLIENT_SECRET;
|
|
200
201
|
if (clientId === undefined || clientSecret === undefined) {
|
|
201
|
-
throw new NotAuthenticated("OAuth login needs --client-id/--client-secret or
|
|
202
|
+
throw new NotAuthenticated("OAuth login needs --client-id/--client-secret or AGENT_SLACK_CLIENT_ID/AGENT_SLACK_CLIENT_SECRET");
|
|
202
203
|
}
|
|
203
204
|
const scopes = splitCsv(flagString(parsed, "scopes"));
|
|
204
205
|
const profile = await services.oauthFlow.login({
|
|
@@ -511,7 +512,7 @@ const methodCall = async (parsed, services, input) => {
|
|
|
511
512
|
items: result.items
|
|
512
513
|
};
|
|
513
514
|
};
|
|
514
|
-
export const renderDispatchResult = (parsed, result) => {
|
|
515
|
+
export const renderDispatchResult = (parsed, result, options = {}) => {
|
|
515
516
|
if (result.rawStdout !== undefined) {
|
|
516
517
|
return result.rawStdout;
|
|
517
518
|
}
|
|
@@ -526,7 +527,16 @@ export const renderDispatchResult = (parsed, result) => {
|
|
|
526
527
|
...(result.response === undefined ? {} : { paging: pagingFrom(result.response) }),
|
|
527
528
|
...(result.warnings === undefined ? {} : { warnings: result.warnings })
|
|
528
529
|
});
|
|
529
|
-
|
|
530
|
+
if (shouldRenderJson(parsed, options)) {
|
|
531
|
+
return `${JSON.stringify(envelope, null, 2)}\n`;
|
|
532
|
+
}
|
|
533
|
+
return renderHumanEnvelope(envelope, {
|
|
534
|
+
color: options.stdoutIsTty === true && options.env?.NO_COLOR === undefined && !flagBoolean(parsed, "no-color")
|
|
535
|
+
});
|
|
536
|
+
};
|
|
537
|
+
const shouldRenderJson = (parsed, options) => {
|
|
538
|
+
const format = flagString(parsed, "format");
|
|
539
|
+
return flagBoolean(parsed, "json") || format === "json" || options.stdoutIsTty !== true;
|
|
530
540
|
};
|
|
531
541
|
const numberFlag = (parsed, name) => {
|
|
532
542
|
const value = flagString(parsed, name);
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { parseArgs } from "../cli/args.js";
|
|
2
2
|
import { errorEnvelope } from "../output/envelope.js";
|
|
3
3
|
import { dispatch, renderDispatchResult } from "./commands.js";
|
|
4
|
-
export const executeCli = async (argv, services) => {
|
|
4
|
+
export const executeCli = async (argv, services, options = {}) => {
|
|
5
5
|
try {
|
|
6
6
|
const parsed = parseArgs(argv);
|
|
7
7
|
const result = await dispatch(parsed, services);
|
|
8
8
|
return {
|
|
9
9
|
exitCode: 0,
|
|
10
|
-
stdout: renderDispatchResult(parsed, result),
|
|
10
|
+
stdout: renderDispatchResult(parsed, result, options),
|
|
11
11
|
stderr: ""
|
|
12
12
|
};
|
|
13
13
|
}
|
package/dist/cli/args.js
CHANGED
package/dist/cli/metadata.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
export const PRIMARY_COMMAND_NAME = "agent-slack";
|
|
2
|
+
export const SHORT_COMMAND_NAME = "aslk";
|
|
3
|
+
export const COMMAND_NAMES = [PRIMARY_COMMAND_NAME, SHORT_COMMAND_NAME];
|
|
4
|
+
export const CLI_VERSION = "0.1.2";
|
|
1
5
|
export const commandMetadata = [
|
|
2
6
|
{
|
|
3
7
|
path: ["describe"],
|
|
@@ -5,7 +9,7 @@ export const commandMetadata = [
|
|
|
5
9
|
flags: ["--json"],
|
|
6
10
|
safety: "read",
|
|
7
11
|
output: "command metadata",
|
|
8
|
-
examples: ["
|
|
12
|
+
examples: ["agent-slack describe --json"]
|
|
9
13
|
},
|
|
10
14
|
{
|
|
11
15
|
path: ["completion"],
|
|
@@ -13,7 +17,7 @@ export const commandMetadata = [
|
|
|
13
17
|
args: ["bash|zsh"],
|
|
14
18
|
safety: "read",
|
|
15
19
|
output: "shell completion script",
|
|
16
|
-
examples: ["
|
|
20
|
+
examples: ["agent-slack completion zsh > ~/.zfunc/_agent-slack"]
|
|
17
21
|
},
|
|
18
22
|
{
|
|
19
23
|
path: ["auth", "status"],
|
|
@@ -21,7 +25,7 @@ export const commandMetadata = [
|
|
|
21
25
|
flags: ["--profile", "--json"],
|
|
22
26
|
safety: "read",
|
|
23
27
|
output: "profile status",
|
|
24
|
-
examples: ["
|
|
28
|
+
examples: ["agent-slack auth status --json"]
|
|
25
29
|
},
|
|
26
30
|
{
|
|
27
31
|
path: ["auth", "login"],
|
|
@@ -30,8 +34,8 @@ export const commandMetadata = [
|
|
|
30
34
|
safety: "read",
|
|
31
35
|
output: "profile status",
|
|
32
36
|
examples: [
|
|
33
|
-
"
|
|
34
|
-
"
|
|
37
|
+
"agent-slack auth login --oauth --client-id 123.456 --client-secret secret --scopes channels:read,channels:history --json",
|
|
38
|
+
"AGENT_SLACK_TOKEN=xoxb-... agent-slack auth login --profile work --scopes channels:read --json"
|
|
35
39
|
]
|
|
36
40
|
},
|
|
37
41
|
{
|
|
@@ -40,7 +44,7 @@ export const commandMetadata = [
|
|
|
40
44
|
flags: ["--profile", "--json"],
|
|
41
45
|
safety: "read",
|
|
42
46
|
output: "scope list",
|
|
43
|
-
examples: ["
|
|
47
|
+
examples: ["agent-slack auth scopes --json"]
|
|
44
48
|
},
|
|
45
49
|
{
|
|
46
50
|
path: ["auth", "profiles", "list"],
|
|
@@ -48,7 +52,7 @@ export const commandMetadata = [
|
|
|
48
52
|
flags: ["--json"],
|
|
49
53
|
safety: "read",
|
|
50
54
|
output: "profile list",
|
|
51
|
-
examples: ["
|
|
55
|
+
examples: ["agent-slack auth profiles list --json"]
|
|
52
56
|
},
|
|
53
57
|
{
|
|
54
58
|
path: ["auth", "logout"],
|
|
@@ -56,7 +60,7 @@ export const commandMetadata = [
|
|
|
56
60
|
flags: ["--profile", "--json"],
|
|
57
61
|
safety: "destructive",
|
|
58
62
|
output: "deleted profile status",
|
|
59
|
-
examples: ["
|
|
63
|
+
examples: ["agent-slack auth logout --profile work --yes --json"]
|
|
60
64
|
},
|
|
61
65
|
{
|
|
62
66
|
path: ["auth", "test"],
|
|
@@ -65,7 +69,7 @@ export const commandMetadata = [
|
|
|
65
69
|
scopes: [],
|
|
66
70
|
safety: "read",
|
|
67
71
|
output: "Slack auth.test response",
|
|
68
|
-
examples: ["
|
|
72
|
+
examples: ["agent-slack auth test --json"]
|
|
69
73
|
},
|
|
70
74
|
{
|
|
71
75
|
path: ["team", "get"],
|
|
@@ -74,7 +78,7 @@ export const commandMetadata = [
|
|
|
74
78
|
scopes: ["team:read"],
|
|
75
79
|
safety: "read",
|
|
76
80
|
output: "team info",
|
|
77
|
-
examples: ["
|
|
81
|
+
examples: ["agent-slack team get --json"]
|
|
78
82
|
},
|
|
79
83
|
{
|
|
80
84
|
path: ["team", "profile", "get"],
|
|
@@ -83,7 +87,7 @@ export const commandMetadata = [
|
|
|
83
87
|
scopes: ["users.profile:read"],
|
|
84
88
|
safety: "read",
|
|
85
89
|
output: "team profile fields",
|
|
86
|
-
examples: ["
|
|
90
|
+
examples: ["agent-slack team profile get --json"]
|
|
87
91
|
},
|
|
88
92
|
{
|
|
89
93
|
path: ["enterprise", "get"],
|
|
@@ -92,7 +96,7 @@ export const commandMetadata = [
|
|
|
92
96
|
scopes: ["team:read"],
|
|
93
97
|
safety: "read",
|
|
94
98
|
output: "team or enterprise info",
|
|
95
|
-
examples: ["
|
|
99
|
+
examples: ["agent-slack enterprise get --json"]
|
|
96
100
|
},
|
|
97
101
|
{
|
|
98
102
|
path: ["user", "list"],
|
|
@@ -102,7 +106,7 @@ export const commandMetadata = [
|
|
|
102
106
|
scopes: ["users:read"],
|
|
103
107
|
safety: "read",
|
|
104
108
|
output: "user list",
|
|
105
|
-
examples: ["
|
|
109
|
+
examples: ["agent-slack user list --limit 50 --json"]
|
|
106
110
|
},
|
|
107
111
|
{
|
|
108
112
|
path: ["user", "get"],
|
|
@@ -112,7 +116,7 @@ export const commandMetadata = [
|
|
|
112
116
|
scopes: ["users:read"],
|
|
113
117
|
safety: "read",
|
|
114
118
|
output: "user info",
|
|
115
|
-
examples: ["
|
|
119
|
+
examples: ["agent-slack user get U123 --json"]
|
|
116
120
|
},
|
|
117
121
|
{
|
|
118
122
|
path: ["user", "lookup"],
|
|
@@ -122,7 +126,7 @@ export const commandMetadata = [
|
|
|
122
126
|
scopes: ["users:read.email"],
|
|
123
127
|
safety: "read",
|
|
124
128
|
output: "user info",
|
|
125
|
-
examples: ["
|
|
129
|
+
examples: ["agent-slack user lookup --email dev@example.com --json"]
|
|
126
130
|
},
|
|
127
131
|
{
|
|
128
132
|
path: ["user", "presence", "get"],
|
|
@@ -132,7 +136,7 @@ export const commandMetadata = [
|
|
|
132
136
|
scopes: ["users:read"],
|
|
133
137
|
safety: "read",
|
|
134
138
|
output: "presence",
|
|
135
|
-
examples: ["
|
|
139
|
+
examples: ["agent-slack user presence get U123 --json"]
|
|
136
140
|
},
|
|
137
141
|
{
|
|
138
142
|
path: ["usergroups", "list"],
|
|
@@ -141,7 +145,7 @@ export const commandMetadata = [
|
|
|
141
145
|
scopes: ["usergroups:read"],
|
|
142
146
|
safety: "read",
|
|
143
147
|
output: "user group list",
|
|
144
|
-
examples: ["
|
|
148
|
+
examples: ["agent-slack usergroups list --json"]
|
|
145
149
|
},
|
|
146
150
|
{
|
|
147
151
|
path: ["usergroups", "users", "list"],
|
|
@@ -151,7 +155,7 @@ export const commandMetadata = [
|
|
|
151
155
|
scopes: ["usergroups:read"],
|
|
152
156
|
safety: "read",
|
|
153
157
|
output: "user IDs",
|
|
154
|
-
examples: ["
|
|
158
|
+
examples: ["agent-slack usergroups users list S123 --json"]
|
|
155
159
|
},
|
|
156
160
|
{
|
|
157
161
|
path: ["api", "call"],
|
|
@@ -161,8 +165,8 @@ export const commandMetadata = [
|
|
|
161
165
|
safety: "unknown",
|
|
162
166
|
output: "Slack Web API response",
|
|
163
167
|
examples: [
|
|
164
|
-
"
|
|
165
|
-
"cat payload.json |
|
|
168
|
+
"agent-slack api call conversations.history --payload '{\"channel\":\"C123\"}' --json",
|
|
169
|
+
"cat payload.json | agent-slack api call conversations.history -"
|
|
166
170
|
]
|
|
167
171
|
},
|
|
168
172
|
{
|
|
@@ -171,7 +175,7 @@ export const commandMetadata = [
|
|
|
171
175
|
flags: ["--family", "--json"],
|
|
172
176
|
safety: "read",
|
|
173
177
|
output: "method metadata list",
|
|
174
|
-
examples: ["
|
|
178
|
+
examples: ["agent-slack api methods list --family conversations --json"]
|
|
175
179
|
},
|
|
176
180
|
{
|
|
177
181
|
path: ["api", "method", "describe"],
|
|
@@ -180,7 +184,7 @@ export const commandMetadata = [
|
|
|
180
184
|
flags: ["--json"],
|
|
181
185
|
safety: "read",
|
|
182
186
|
output: "method metadata",
|
|
183
|
-
examples: ["
|
|
187
|
+
examples: ["agent-slack api method describe conversations.replies --json"]
|
|
184
188
|
},
|
|
185
189
|
{
|
|
186
190
|
path: ["conversation", "list"],
|
|
@@ -189,7 +193,7 @@ export const commandMetadata = [
|
|
|
189
193
|
scopes: ["channels:read", "groups:read", "im:read", "mpim:read"],
|
|
190
194
|
safety: "read",
|
|
191
195
|
output: "conversation list",
|
|
192
|
-
examples: ["
|
|
196
|
+
examples: ["agent-slack conversation list --types public_channel,private_channel --json"]
|
|
193
197
|
},
|
|
194
198
|
{
|
|
195
199
|
path: ["conversation", "history"],
|
|
@@ -199,7 +203,7 @@ export const commandMetadata = [
|
|
|
199
203
|
scopes: ["channels:history", "groups:history", "im:history", "mpim:history"],
|
|
200
204
|
safety: "read",
|
|
201
205
|
output: "message list",
|
|
202
|
-
examples: ["
|
|
206
|
+
examples: ["agent-slack conversation history C123 --limit 20 --json"]
|
|
203
207
|
},
|
|
204
208
|
{
|
|
205
209
|
path: ["conversation", "context"],
|
|
@@ -209,7 +213,7 @@ export const commandMetadata = [
|
|
|
209
213
|
scopes: ["channels:history", "users:read"],
|
|
210
214
|
safety: "read",
|
|
211
215
|
output: "hydrated conversation context",
|
|
212
|
-
examples: ["
|
|
216
|
+
examples: ["agent-slack conversation context C123 --since 24h --include users,threads --format ndjson"]
|
|
213
217
|
},
|
|
214
218
|
{
|
|
215
219
|
path: ["thread", "get"],
|
|
@@ -219,7 +223,7 @@ export const commandMetadata = [
|
|
|
219
223
|
scopes: ["channels:history", "groups:history", "im:history", "mpim:history"],
|
|
220
224
|
safety: "read",
|
|
221
225
|
output: "thread messages",
|
|
222
|
-
examples: ["
|
|
226
|
+
examples: ["agent-slack thread get --channel C123 --ts 1710000000.000100 --include users --json"]
|
|
223
227
|
},
|
|
224
228
|
{
|
|
225
229
|
path: ["message", "get"],
|
|
@@ -229,7 +233,7 @@ export const commandMetadata = [
|
|
|
229
233
|
scopes: ["channels:history"],
|
|
230
234
|
safety: "read",
|
|
231
235
|
output: "message",
|
|
232
|
-
examples: ["
|
|
236
|
+
examples: ["agent-slack message get --channel C123 --ts 1710000000.000100 --json"]
|
|
233
237
|
},
|
|
234
238
|
{
|
|
235
239
|
path: ["message", "permalink"],
|
|
@@ -238,7 +242,7 @@ export const commandMetadata = [
|
|
|
238
242
|
methods: ["chat.getPermalink"],
|
|
239
243
|
safety: "read",
|
|
240
244
|
output: "permalink",
|
|
241
|
-
examples: ["
|
|
245
|
+
examples: ["agent-slack message permalink --channel C123 --ts 1710000000.000100 --json"]
|
|
242
246
|
},
|
|
243
247
|
{
|
|
244
248
|
path: ["search", "context"],
|
|
@@ -248,7 +252,7 @@ export const commandMetadata = [
|
|
|
248
252
|
scopes: ["search:read.public", "search:read.private"],
|
|
249
253
|
safety: "read",
|
|
250
254
|
output: "search context",
|
|
251
|
-
examples: ["
|
|
255
|
+
examples: ["agent-slack search context --query 'project atlas' --json"]
|
|
252
256
|
},
|
|
253
257
|
{
|
|
254
258
|
path: ["file", "list"],
|
|
@@ -257,7 +261,7 @@ export const commandMetadata = [
|
|
|
257
261
|
scopes: ["files:read"],
|
|
258
262
|
safety: "read",
|
|
259
263
|
output: "file list",
|
|
260
|
-
examples: ["
|
|
264
|
+
examples: ["agent-slack file list --channel C123 --json"]
|
|
261
265
|
},
|
|
262
266
|
{
|
|
263
267
|
path: ["file", "download"],
|
|
@@ -268,12 +272,13 @@ export const commandMetadata = [
|
|
|
268
272
|
scopes: ["files:read"],
|
|
269
273
|
safety: "read",
|
|
270
274
|
output: "download status",
|
|
271
|
-
examples: ["
|
|
275
|
+
examples: ["agent-slack file download F123 --out ./artifact.pdf --json"]
|
|
272
276
|
}
|
|
273
277
|
];
|
|
274
278
|
export const describeAllCommands = () => ({
|
|
275
|
-
name:
|
|
276
|
-
|
|
279
|
+
name: PRIMARY_COMMAND_NAME,
|
|
280
|
+
aliases: [SHORT_COMMAND_NAME],
|
|
281
|
+
version: CLI_VERSION,
|
|
277
282
|
commands: commandMetadata
|
|
278
283
|
});
|
|
279
284
|
export const findCommandMetadata = (path) => commandMetadata.find((command) => command.path.join(" ") === path.join(" ")) ?? null;
|
|
@@ -285,7 +290,7 @@ export const renderHumanHelp = (path) => {
|
|
|
285
290
|
const command = findCommandMetadata(path);
|
|
286
291
|
if (command !== null) {
|
|
287
292
|
return [
|
|
288
|
-
|
|
293
|
+
`${PRIMARY_COMMAND_NAME} ${command.path.join(" ")}`,
|
|
289
294
|
"",
|
|
290
295
|
command.summary,
|
|
291
296
|
"",
|
|
@@ -301,7 +306,7 @@ export const renderHumanHelp = (path) => {
|
|
|
301
306
|
}
|
|
302
307
|
const commands = path.length === 0 ? commandMetadata : commandMetadata.filter((item) => item.path[0] === path[0]);
|
|
303
308
|
return [
|
|
304
|
-
|
|
309
|
+
PRIMARY_COMMAND_NAME,
|
|
305
310
|
"",
|
|
306
311
|
"Agent-readable Slack CLI.",
|
|
307
312
|
"",
|
|
@@ -313,15 +318,15 @@ export const renderCompletion = (shell) => {
|
|
|
313
318
|
const words = [...new Set(commandMetadata.flatMap((command) => command.path))];
|
|
314
319
|
const commandWords = commandMetadata.map((command) => command.path.join(" "));
|
|
315
320
|
if (shell === "bash") {
|
|
316
|
-
return `complete -W '${words.join(" ")}'
|
|
321
|
+
return `complete -W '${words.join(" ")}' ${COMMAND_NAMES.join(" ")}\n`;
|
|
317
322
|
}
|
|
318
323
|
if (shell === "zsh") {
|
|
319
324
|
return [
|
|
320
|
-
|
|
321
|
-
"
|
|
325
|
+
`#compdef ${COMMAND_NAMES.join(" ")}`,
|
|
326
|
+
"_agent_slack_commands=(",
|
|
322
327
|
...commandWords.map((command) => ` '${command}:${summaryFor(command)}'`),
|
|
323
328
|
")",
|
|
324
|
-
|
|
329
|
+
`_describe '${PRIMARY_COMMAND_NAME} command' _agent_slack_commands`,
|
|
325
330
|
""
|
|
326
331
|
].join("\n");
|
|
327
332
|
}
|
package/dist/main.js
CHANGED
|
@@ -4,7 +4,10 @@ import { Effect } from "effect";
|
|
|
4
4
|
import { createLiveServices } from "./adapters/live.js";
|
|
5
5
|
import { executeCli } from "./application/execute.js";
|
|
6
6
|
const program = Effect.promise(async () => {
|
|
7
|
-
const result = await executeCli(process.argv.slice(2), createLiveServices()
|
|
7
|
+
const result = await executeCli(process.argv.slice(2), createLiveServices(), {
|
|
8
|
+
stdoutIsTty: process.stdout.isTTY === true,
|
|
9
|
+
env: process.env
|
|
10
|
+
});
|
|
8
11
|
if (result.stdout.length > 0) {
|
|
9
12
|
process.stdout.write(result.stdout);
|
|
10
13
|
}
|
package/dist/output/envelope.js
CHANGED
|
@@ -44,7 +44,7 @@ export const errorEnvelope = (error) => {
|
|
|
44
44
|
const suggestionFor = (tag) => {
|
|
45
45
|
switch (tag) {
|
|
46
46
|
case "NotAuthenticated":
|
|
47
|
-
return "Run
|
|
47
|
+
return "Run agent-slack auth login or provide a seeded profile for tests.";
|
|
48
48
|
case "MissingScope":
|
|
49
49
|
return "Reinstall or reauthorize the Slack app with the missing scope.";
|
|
50
50
|
case "SlackRateLimited":
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
const codes = {
|
|
2
|
+
bold: [1, 22],
|
|
3
|
+
cyan: [36, 39],
|
|
4
|
+
dim: [2, 22],
|
|
5
|
+
green: [32, 39],
|
|
6
|
+
yellow: [33, 39]
|
|
7
|
+
};
|
|
8
|
+
export const renderHumanEnvelope = (envelope, options) => {
|
|
9
|
+
const paint = (name, value) => options.color ? `\u001b[${codes[name][0]}m${value}\u001b[${codes[name][1]}m` : value;
|
|
10
|
+
if (isCommandCatalog(envelope.data)) {
|
|
11
|
+
return `${renderCommandCatalog(envelope.data, paint).join("\n")}\n`;
|
|
12
|
+
}
|
|
13
|
+
const metadata = renderMetadata(envelope, paint);
|
|
14
|
+
const warnings = envelope.warnings.map((warning) => `${paint("yellow", "Warning")}: ${warning}`);
|
|
15
|
+
const data = renderHumanData(envelope.data, paint);
|
|
16
|
+
const lines = [
|
|
17
|
+
`${paint("green", "OK")} ${paint("bold", envelope.method)}`,
|
|
18
|
+
...(metadata === "" ? [] : [metadata]),
|
|
19
|
+
...(warnings.length === 0 ? [] : warnings),
|
|
20
|
+
...(data.length === 0 ? [] : ["", ...data])
|
|
21
|
+
];
|
|
22
|
+
return `${lines.join("\n")}\n`;
|
|
23
|
+
};
|
|
24
|
+
const isCommandCatalog = (value) => {
|
|
25
|
+
if (!isRecord(value) || typeof value.name !== "string" || !Array.isArray(value.commands)) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
return value.commands.every((command) => isRecord(command) &&
|
|
29
|
+
Array.isArray(command.path) &&
|
|
30
|
+
command.path.every((part) => typeof part === "string") &&
|
|
31
|
+
typeof command.summary === "string");
|
|
32
|
+
};
|
|
33
|
+
const renderCommandCatalog = (catalog, paint) => {
|
|
34
|
+
const title = catalog.version === undefined ? `${titleCase(catalog.name)} Commands` : "Agent Slack";
|
|
35
|
+
const aliases = catalog.aliases?.join(", ");
|
|
36
|
+
const groups = groupCommands(catalog.commands);
|
|
37
|
+
return [
|
|
38
|
+
`${paint("bold", title)}${catalog.version === undefined ? "" : ` ${paint("dim", catalog.version)}`}`,
|
|
39
|
+
paint("dim", "Slack context CLI for agents"),
|
|
40
|
+
"",
|
|
41
|
+
`${paint("dim", "Use")} ${paint("cyan", `${catalog.name} <command> [--json]`)}`,
|
|
42
|
+
...(aliases === undefined ? [] : [`${paint("dim", "Alias")} ${aliases}`]),
|
|
43
|
+
`${paint("dim", "Output")} human in terminals, JSON when piped or with --json`,
|
|
44
|
+
"",
|
|
45
|
+
...groups.flatMap((group) => renderCommandGroup(group, paint)),
|
|
46
|
+
"",
|
|
47
|
+
paint("dim", `Run ${catalog.name} <command> --help for details.`)
|
|
48
|
+
];
|
|
49
|
+
};
|
|
50
|
+
const renderCommandGroup = (group, paint) => {
|
|
51
|
+
return [
|
|
52
|
+
paint("yellow", group.title),
|
|
53
|
+
...group.commands.map((command) => {
|
|
54
|
+
const safety = command.safety === "destructive"
|
|
55
|
+
? ` ${paint("yellow", "[destructive]")}`
|
|
56
|
+
: command.safety === "unknown"
|
|
57
|
+
? ` ${paint("yellow", "[review]")}`
|
|
58
|
+
: "";
|
|
59
|
+
return ` ${paint("cyan", commandSignature(command))} - ${command.summary}${safety}`;
|
|
60
|
+
}),
|
|
61
|
+
""
|
|
62
|
+
];
|
|
63
|
+
};
|
|
64
|
+
const groupCommands = (commands) => {
|
|
65
|
+
const groups = new Map();
|
|
66
|
+
for (const command of commands) {
|
|
67
|
+
const key = groupKey(command);
|
|
68
|
+
groups.set(key, [...(groups.get(key) ?? []), command]);
|
|
69
|
+
}
|
|
70
|
+
return commandGroupOrder
|
|
71
|
+
.map((group) => ({
|
|
72
|
+
...group,
|
|
73
|
+
commands: groups.get(group.key) ?? []
|
|
74
|
+
}))
|
|
75
|
+
.filter((group) => group.commands.length > 0);
|
|
76
|
+
};
|
|
77
|
+
const commandGroupOrder = [
|
|
78
|
+
{ key: "system", title: "System" },
|
|
79
|
+
{ key: "auth", title: "Auth" },
|
|
80
|
+
{ key: "workspace", title: "Workspace" },
|
|
81
|
+
{ key: "people", title: "People" },
|
|
82
|
+
{ key: "conversations", title: "Conversations" },
|
|
83
|
+
{ key: "search", title: "Search" },
|
|
84
|
+
{ key: "files", title: "Files" },
|
|
85
|
+
{ key: "surfaces", title: "Slack Objects" },
|
|
86
|
+
{ key: "api", title: "Web API" }
|
|
87
|
+
];
|
|
88
|
+
const groupKey = (command) => {
|
|
89
|
+
const root = command.path[0];
|
|
90
|
+
if (root === "describe" || root === "completion") {
|
|
91
|
+
return "system";
|
|
92
|
+
}
|
|
93
|
+
if (root === "team" || root === "enterprise") {
|
|
94
|
+
return "workspace";
|
|
95
|
+
}
|
|
96
|
+
if (root === "user" || root === "usergroups") {
|
|
97
|
+
return "people";
|
|
98
|
+
}
|
|
99
|
+
if (root === "conversation" || root === "thread" || root === "message") {
|
|
100
|
+
return "conversations";
|
|
101
|
+
}
|
|
102
|
+
if (root === "file") {
|
|
103
|
+
return "files";
|
|
104
|
+
}
|
|
105
|
+
if (root === "reaction" || root === "pin" || root === "bookmark" || root === "emoji" || root === "dnd") {
|
|
106
|
+
return "surfaces";
|
|
107
|
+
}
|
|
108
|
+
return root ?? "system";
|
|
109
|
+
};
|
|
110
|
+
const commandSignature = (command) => {
|
|
111
|
+
const args = command.args?.filter((arg) => arg !== "PAYLOAD_STDIN_MARKER").join(" ");
|
|
112
|
+
return [command.path.join(" "), args].filter((part) => part !== undefined && part !== "").join(" ");
|
|
113
|
+
};
|
|
114
|
+
const renderMetadata = (envelope, paint) => {
|
|
115
|
+
const parts = [
|
|
116
|
+
envelope.profile === null ? undefined : `profile ${envelope.profile}`,
|
|
117
|
+
envelope.team_id === null ? undefined : `team ${envelope.team_id}`,
|
|
118
|
+
envelope.enterprise_id === null ? undefined : `enterprise ${envelope.enterprise_id}`,
|
|
119
|
+
envelope.paging.has_more ? `next ${envelope.paging.next_cursor ?? "cursor"}` : undefined
|
|
120
|
+
].filter((part) => part !== undefined);
|
|
121
|
+
return parts.length === 0 ? "" : paint("dim", parts.join(" | "));
|
|
122
|
+
};
|
|
123
|
+
const renderHumanData = (value, paint) => {
|
|
124
|
+
if (Array.isArray(value)) {
|
|
125
|
+
return renderArray(value, paint);
|
|
126
|
+
}
|
|
127
|
+
if (isRecord(value)) {
|
|
128
|
+
const primaryArray = findPrimaryArray(value);
|
|
129
|
+
const excludedKeys = new Set(primaryArray === null ? [] : [primaryArray.key]);
|
|
130
|
+
const fieldLines = renderFields(value, excludedKeys, paint);
|
|
131
|
+
const hasComplexValues = hasVisibleComplexValues(value, excludedKeys);
|
|
132
|
+
if (primaryArray !== null) {
|
|
133
|
+
return [
|
|
134
|
+
...fieldLines,
|
|
135
|
+
fieldLines.length === 0 ? "" : "",
|
|
136
|
+
`${paint("cyan", labelFor(primaryArray.key))} (${primaryArray.items.length})`,
|
|
137
|
+
...renderArray(primaryArray.items, paint)
|
|
138
|
+
].filter((line) => line !== "");
|
|
139
|
+
}
|
|
140
|
+
if (fieldLines.length > 0 && !hasComplexValues) {
|
|
141
|
+
return fieldLines;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return ["data:", indent(JSON.stringify(value, null, 2))];
|
|
145
|
+
};
|
|
146
|
+
const findPrimaryArray = (record) => {
|
|
147
|
+
const preferred = ["messages", "members", "channels", "files", "users", "results", "items"];
|
|
148
|
+
for (const key of preferred) {
|
|
149
|
+
const value = record[key];
|
|
150
|
+
if (Array.isArray(value) && !value.every(isScalar)) {
|
|
151
|
+
return { key, items: value };
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
for (const [key, value] of Object.entries(record)) {
|
|
155
|
+
if (Array.isArray(value) && !value.every(isScalar)) {
|
|
156
|
+
return { key, items: value };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return null;
|
|
160
|
+
};
|
|
161
|
+
const renderFields = (record, excludedKeys, paint) => {
|
|
162
|
+
const entries = Object.entries(record)
|
|
163
|
+
.filter(([key, value]) => !excludedKeys.has(key) && shouldDisplayField(key, value) && isHumanFieldValue(value))
|
|
164
|
+
.map(([key, value]) => ({ key, label: labelFor(key), value }))
|
|
165
|
+
.sort((left, right) => fieldOrder(left.key) - fieldOrder(right.key));
|
|
166
|
+
if (entries.length === 0) {
|
|
167
|
+
return [];
|
|
168
|
+
}
|
|
169
|
+
const labelWidth = Math.max(...entries.map((entry) => entry.label.length));
|
|
170
|
+
return entries.map((entry) => `${paint("dim", pad(entry.label, labelWidth))} ${formatHumanValue(entry.value)}`);
|
|
171
|
+
};
|
|
172
|
+
const hasVisibleComplexValues = (record, excludedKeys) => Object.entries(record).some(([key, value]) => !excludedKeys.has(key) && shouldDisplayField(key, value) && !isHumanFieldValue(value));
|
|
173
|
+
const renderArray = (values, paint) => {
|
|
174
|
+
if (values.length === 0) {
|
|
175
|
+
return ["(empty)"];
|
|
176
|
+
}
|
|
177
|
+
if (!values.every(isRecord)) {
|
|
178
|
+
return [indent(JSON.stringify(values, null, 2))];
|
|
179
|
+
}
|
|
180
|
+
const rows = values.map((value) => value);
|
|
181
|
+
const columns = columnsFor(rows);
|
|
182
|
+
if (columns.length === 0) {
|
|
183
|
+
return [indent(JSON.stringify(values, null, 2))];
|
|
184
|
+
}
|
|
185
|
+
const renderedRows = rows.slice(0, 20).map((row) => columns.map((column) => truncate(formatScalar(row[column]), widthFor(column))));
|
|
186
|
+
const widths = columns.map((column, index) => Math.max(column.length, ...renderedRows.map((row) => row[index]?.length ?? 0)));
|
|
187
|
+
const header = columns.map((column, index) => pad(truncate(column, widthFor(column)), widths[index] ?? column.length)).join(" ");
|
|
188
|
+
const divider = widths.map((width) => "-".repeat(width)).join(" ");
|
|
189
|
+
const body = renderedRows.map((row) => row.map((cell, index) => pad(cell, widths[index] ?? cell.length)).join(" "));
|
|
190
|
+
const hiddenCount = values.length - renderedRows.length;
|
|
191
|
+
return [
|
|
192
|
+
paint("dim", header),
|
|
193
|
+
paint("dim", divider),
|
|
194
|
+
...body,
|
|
195
|
+
hiddenCount > 0 ? paint("dim", `... ${hiddenCount} more`) : ""
|
|
196
|
+
].filter((line) => line !== "");
|
|
197
|
+
};
|
|
198
|
+
const columnsFor = (rows) => {
|
|
199
|
+
const preferred = ["id", "channel", "ts", "user", "name", "real_name", "type", "subtype", "text"];
|
|
200
|
+
const discovered = new Set(rows.flatMap((row) => Object.keys(row).filter((key) => isScalar(row[key]))));
|
|
201
|
+
const ordered = preferred.filter((key) => discovered.has(key));
|
|
202
|
+
for (const key of discovered) {
|
|
203
|
+
if (!ordered.includes(key)) {
|
|
204
|
+
ordered.push(key);
|
|
205
|
+
}
|
|
206
|
+
if (ordered.length >= 5) {
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return ordered.slice(0, 5);
|
|
211
|
+
};
|
|
212
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
213
|
+
const isScalar = (value) => value === null || ["boolean", "number", "string"].includes(typeof value);
|
|
214
|
+
const isHumanFieldValue = (value) => isScalar(value) || (Array.isArray(value) && value.every(isScalar));
|
|
215
|
+
const shouldDisplayField = (key, value) => {
|
|
216
|
+
if (key === "ok" && value === true) {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
if (value === null || value === undefined) {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
if (/^has[A-Z].*Token$/.test(key) && value === false) {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
return true;
|
|
226
|
+
};
|
|
227
|
+
const labelOverrides = {
|
|
228
|
+
botId: "Bot",
|
|
229
|
+
botToken: "Bot token",
|
|
230
|
+
channelId: "Channel",
|
|
231
|
+
enterpriseId: "Enterprise",
|
|
232
|
+
hasAdminToken: "Admin token",
|
|
233
|
+
hasAppToken: "App token",
|
|
234
|
+
hasBotToken: "Bot token",
|
|
235
|
+
hasUserToken: "User token",
|
|
236
|
+
messageTs: "Message TS",
|
|
237
|
+
name: "Name",
|
|
238
|
+
profile: "Profile",
|
|
239
|
+
scopes: "Scopes",
|
|
240
|
+
teamId: "Team",
|
|
241
|
+
tokenType: "Token",
|
|
242
|
+
userId: "User"
|
|
243
|
+
};
|
|
244
|
+
const orderedFields = [
|
|
245
|
+
"name",
|
|
246
|
+
"teamId",
|
|
247
|
+
"enterpriseId",
|
|
248
|
+
"userId",
|
|
249
|
+
"botId",
|
|
250
|
+
"tokenType",
|
|
251
|
+
"hasBotToken",
|
|
252
|
+
"hasUserToken",
|
|
253
|
+
"hasAdminToken",
|
|
254
|
+
"hasAppToken",
|
|
255
|
+
"scopes"
|
|
256
|
+
];
|
|
257
|
+
const fieldOrder = (key) => {
|
|
258
|
+
const index = orderedFields.indexOf(key);
|
|
259
|
+
return index === -1 ? orderedFields.length : index;
|
|
260
|
+
};
|
|
261
|
+
const labelFor = (key) => labelOverrides[key] ?? titleCase(key.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/_/g, " "));
|
|
262
|
+
const titleCase = (value) => value.split(" ").map((word) => word.toLowerCase() === "id" ? "ID" : word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
263
|
+
const formatHumanValue = (value) => {
|
|
264
|
+
if (Array.isArray(value)) {
|
|
265
|
+
return value.length === 0 ? "(none)" : value.map(formatScalar).join(", ");
|
|
266
|
+
}
|
|
267
|
+
if (typeof value === "boolean") {
|
|
268
|
+
return value ? "yes" : "no";
|
|
269
|
+
}
|
|
270
|
+
return formatScalar(value);
|
|
271
|
+
};
|
|
272
|
+
const formatScalar = (value) => {
|
|
273
|
+
if (value === undefined) {
|
|
274
|
+
return "";
|
|
275
|
+
}
|
|
276
|
+
if (value === null) {
|
|
277
|
+
return "-";
|
|
278
|
+
}
|
|
279
|
+
if (typeof value === "string") {
|
|
280
|
+
return value;
|
|
281
|
+
}
|
|
282
|
+
return String(value);
|
|
283
|
+
};
|
|
284
|
+
const indent = (value) => value.split("\n").map((line) => ` ${line}`).join("\n");
|
|
285
|
+
const pad = (value, width) => value + " ".repeat(Math.max(0, width - value.length));
|
|
286
|
+
const widthFor = (column) => column === "text" ? 72 : 24;
|
|
287
|
+
const truncate = (value, width) => value.length > width ? `${value.slice(0, Math.max(0, width - 3))}...` : value;
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eliya-oss/agent-slack",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Slack context CLI for AI agents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "pnpm@11.9.0",
|
|
7
7
|
"bin": {
|
|
8
|
-
"
|
|
8
|
+
"agent-slack": "dist/main.js",
|
|
9
|
+
"aslk": "dist/main.js"
|
|
9
10
|
},
|
|
10
11
|
"files": [
|
|
11
12
|
"dist",
|
|
@@ -24,7 +25,8 @@
|
|
|
24
25
|
"dev": "tsx src/main.ts",
|
|
25
26
|
"prepack": "pnpm build",
|
|
26
27
|
"release:check": "pnpm check && npm pack --dry-run",
|
|
27
|
-
"
|
|
28
|
+
"agent-slack": "tsx src/main.ts",
|
|
29
|
+
"aslk": "tsx src/main.ts",
|
|
28
30
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
29
31
|
"test": "vitest run",
|
|
30
32
|
"test:coverage": "vitest run --coverage",
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: agent-slack
|
|
3
|
-
description: "Use when Codex needs Slack context through the `
|
|
3
|
+
description: "Use when Codex needs Slack context through the `agent-slack` CLI: authenticate, inspect profiles/scopes, read channels, messages, threads, files, users, search context, or call Slack Web API methods with agent-readable JSON or NDJSON output."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
#
|
|
6
|
+
# agent-slack CLI
|
|
7
7
|
|
|
8
|
-
Use `
|
|
8
|
+
Use `agent-slack` as the Slack context boundary. `aslk` is the short alias. Prefer read-only commands and preserve Slack's permission model: the CLI can only return data allowed by the active token, scopes, channel membership, workspace policy, and Slack plan.
|
|
9
9
|
|
|
10
10
|
## Workflow
|
|
11
11
|
|
|
12
|
-
1. Check availability: `
|
|
13
|
-
2. Check auth: `
|
|
12
|
+
1. Check availability: `agent-slack describe --json`.
|
|
13
|
+
2. Check auth: `agent-slack auth status --json`.
|
|
14
14
|
3. If auth is missing, ask the user to run OAuth or provide a seeded profile; do not invent credentials.
|
|
15
15
|
4. Use specific read commands before raw API calls.
|
|
16
16
|
5. Use `--json` for bounded results and `--format ndjson` for large context streams.
|
|
@@ -19,17 +19,17 @@ Use `slk` as the Slack context boundary. Prefer read-only commands and preserve
|
|
|
19
19
|
## Common Commands
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
agent-slack conversation history C123 --limit 50 --json
|
|
23
|
+
agent-slack thread get --channel C123 --ts 1710000000.000100 --include users,permalinks --json
|
|
24
|
+
agent-slack conversation context C123 --include users,threads,permalinks --format ndjson
|
|
25
|
+
agent-slack file download F123 --out ./artifact.bin --json
|
|
26
|
+
agent-slack user get U123 --json
|
|
27
|
+
agent-slack api call conversations.info --payload '{"channel":"C123"}' --json
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
## Safety
|
|
31
31
|
|
|
32
|
-
- Treat `
|
|
32
|
+
- Treat `agent-slack api call` as the escape hatch for missing wrappers.
|
|
33
33
|
- Do not pass write/admin-mutating Slack methods unless the user explicitly asks for mutation and accepts `--allow-write --yes`.
|
|
34
34
|
- Never print token values. `auth status` reports token presence and scopes without secrets.
|
|
35
|
-
- For local repo development, use `pnpm
|
|
35
|
+
- For local repo development, use `pnpm agent-slack -- ...` from the repository root instead of global `agent-slack`.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
interface:
|
|
2
2
|
display_name: "Slack CLI"
|
|
3
|
-
short_description: "Use
|
|
4
|
-
default_prompt: "Use $agent-slack to retrieve Slack channel or thread context with the
|
|
3
|
+
short_description: "Use agent-slack to retrieve Slack context"
|
|
4
|
+
default_prompt: "Use $agent-slack to retrieve Slack channel or thread context with the agent-slack CLI."
|