@joe-broadhead/opencode-slack 0.0.0 → 0.0.1
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/CHANGELOG.md +13 -0
- package/README.md +12 -2
- package/dist/index.js +31 -6
- package/dist/tui.js +7 -1
- package/package.json +197 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
4
4
|
|
|
5
|
+
## [0.0.1] - 2026-11-03
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- A `jelly` section in `package.json`: each tool with its permission, its default effect and whether it only reads, the skill ids. Jelly reads it without loading the plugin and applies the declared asks, because its agents have no base allow rule for this plugin's own default rules to follow. `docs/jelly.md` explains it.
|
|
10
|
+
- `title` and `signIns` in the `jelly` section: the name Jelly shows on the plugin's card, and what the plugin signs in to, so that one card can say whether each sign-in is connected and start, renew or end it. `docs/jelly.md` lists them.
|
|
11
|
+
- Hosted callbacks, so sign-in works when OpenCode runs on another machine ([#1](https://github.com/joe-broadhead/opencode-slack/issues/1)): `oauth.redirect_uri` also accepts an `https:` URL with a path and no credentials, query or fragment, together with the new `oauth.callback_port`, the port of OpenCode's local callback listener, which a reverse proxy forwards the address to. The server config then carries `callback_port`, and status shows the port in `callbackPort` and its `Callback` row. `oauth.callback_port` is refused with a loopback redirect, whose own port is the listener's. The docs explain how to add a hosted address to the Slack app's Redirect URLs, and how two sign-ins on one machine avoid colliding on port 8765.
|
|
12
|
+
- `presetBy` on the `jelly.signIns` Slack app step: with `oauth.client_id` set in the plugin options, the step is done, so Jelly asks nobody to paste a client ID.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- The `oauth.redirect_uri` errors say which rule an `http:` URL broke, and their next step names both a loopback and a hosted example. The `Callback` status row says to register the URL in the app's Redirect URLs.
|
|
17
|
+
|
|
5
18
|
## [0.0.0] - 2026-11-01
|
|
6
19
|
|
|
7
20
|
### Added
|
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ opencode plugin add @joe-broadhead/opencode-slack
|
|
|
21
21
|
|
|
22
22
|
If your organisation already set the client ID for you (see [Organisation rollout](#organisation-rollout)), there is no **Slack app** entry; go straight to step 2.
|
|
23
23
|
2. **Sign in to Slack.** Run `/connect` and choose **Slack**. In the TUI, press `o` or Enter to open the sign-in page in your browser. In a terminal, `opencode mcp auth slack` opens the browser automatically. `/mcps` → `slack` is an equivalent way in.
|
|
24
|
-
3. Slack shows the workspace, the account and what the app asks for. Approve it. Slack sends the browser back to `http://localhost:8765/callback
|
|
24
|
+
3. Slack shows the workspace, the account and what the app asks for. Approve it. Slack sends the browser back to the callback, `http://localhost:8765/callback` unless you set another, and OpenCode finishes the sign-in.
|
|
25
25
|
|
|
26
26
|
`/slack-status` now shows `connected` and the Slack tools it found. OpenCode stores the credential and refreshes it. To sign out, run `opencode mcp logout slack`.
|
|
27
27
|
|
|
@@ -106,7 +106,7 @@ Do not put this block in the top-level `permissions`. OpenCode adds top-level ru
|
|
|
106
106
|
|
|
107
107
|
## Configure
|
|
108
108
|
|
|
109
|
-
Everything works without options. Options set the client ID for everyone (instead of `/connect`), the callback URL, the scopes, a client secret for apps without PKCE, the server name and the host check. To pass them, use the object form of the `plugins` entry:
|
|
109
|
+
Everything works without options. Options set the client ID for everyone (instead of `/connect`), the callback URL and its local port, the scopes, a client secret for apps without PKCE, the server name and the host check. To pass them, use the object form of the `plugins` entry:
|
|
110
110
|
|
|
111
111
|
```jsonc
|
|
112
112
|
{
|
|
@@ -116,6 +116,14 @@ Everything works without options. Options set the client ID for everyone (instea
|
|
|
116
116
|
}
|
|
117
117
|
```
|
|
118
118
|
|
|
119
|
+
**OpenCode on another machine.** The default callback, `http://localhost:8765/callback`, works only when the browser runs on OpenCode's machine. Otherwise use a hosted callback: an `https:` address the browser can reach, which a reverse proxy forwards to OpenCode's local listener on `oauth.callback_port`:
|
|
120
|
+
|
|
121
|
+
```jsonc
|
|
122
|
+
"oauth": { "client_id": "YOUR_SLACK_CLIENT_ID", "redirect_uri": "https://host.example/oauth/return", "callback_port": 8766 }
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Add the address to the Slack app's **OAuth & Permissions → Redirect URLs** (or `oauth_config.redirect_urls` in the [manifest](https://github.com/joe-broadhead/opencode-slack/blob/HEAD/docs/slack-app-manifest.json)). Slack matches the host and port exactly. [The callback: loopback or hosted](https://github.com/joe-broadhead/opencode-slack/blob/HEAD/docs/configuration.md#the-callback-loopback-or-hosted) explains the proxy and how two sign-ins on one machine avoid colliding on port 8765.
|
|
126
|
+
|
|
119
127
|
[docs/configuration.md](https://github.com/joe-broadhead/opencode-slack/blob/HEAD/docs/configuration.md) lists every option, the scopes each tool needs, the permission rules and the status model.
|
|
120
128
|
|
|
121
129
|
## Organisation rollout
|
|
@@ -165,6 +173,8 @@ If status itself cannot be read, `/slack-status` says `STATUS_UNAVAILABLE`; run
|
|
|
165
173
|
- **Slack asks you to request approval:** your workspace requires admin approval for apps or scopes. Ask an admin to approve the app, then sign in again.
|
|
166
174
|
- **Slack refuses the app for MCP:** turn on **Agents → Slack Model Context Protocol (MCP) Server** in the app settings. The app must be internal to your organisation or published in the Slack Marketplace.
|
|
167
175
|
- **The browser cannot open `localhost:8765` after Slack approves:** OpenCode listens for the callback on `127.0.0.1` only. If your browser tries IPv6 for `localhost`, replace `localhost` with `127.0.0.1` in the address bar. To avoid it, register `http://127.0.0.1:8765/callback` in the app and set it as `oauth.redirect_uri`.
|
|
176
|
+
- **The browser cannot open `localhost:8765` because OpenCode runs on another machine:** the browser's `localhost` is not OpenCode's. Use a [hosted callback](https://github.com/joe-broadhead/opencode-slack/blob/HEAD/docs/configuration.md#the-callback-loopback-or-hosted).
|
|
177
|
+
- **Sign-in fails because the callback port is in use:** another sign-in on this machine is waiting on port 8765. Finish it first, or give this OpenCode its own callback, such as `http://localhost:8767/callback`, registered in the Slack app too.
|
|
168
178
|
|
|
169
179
|
**A Slack tool fails**
|
|
170
180
|
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,8 @@ import { Plugin } from "@opencode/plugin";
|
|
|
3
3
|
|
|
4
4
|
// src/defaults.ts
|
|
5
5
|
var DEFAULT_REDIRECT_URI = "http://localhost:8765/callback";
|
|
6
|
+
var HOSTED_REDIRECT_EXAMPLE = "https://host.example/oauth/return";
|
|
7
|
+
var CALLBACK_PORT_EXAMPLE = 8766;
|
|
6
8
|
var DEFAULT_SCOPES = ["channels:history", "channels:read", "search:read.public", "users:read"];
|
|
7
9
|
var DEFAULT_SERVER = "slack";
|
|
8
10
|
var APP_MANIFEST_URL = "https://github.com/joe-broadhead/opencode-slack/blob/HEAD/docs/slack-app-manifest.json";
|
|
@@ -96,7 +98,7 @@ function isTestedHost(version) {
|
|
|
96
98
|
|
|
97
99
|
// src/options.ts
|
|
98
100
|
var TOP_KEYS = ["oauth", "server", "allowUntestedHost"];
|
|
99
|
-
var OAUTH_KEYS = ["client_id", "redirect_uri", "scopes", "client_secret"];
|
|
101
|
+
var OAUTH_KEYS = ["client_id", "redirect_uri", "callback_port", "scopes", "client_secret"];
|
|
100
102
|
var SERVER_NAME = /^[A-Za-z0-9_-]+$/;
|
|
101
103
|
var NO_SPACES2 = /^\S+$/;
|
|
102
104
|
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -108,14 +110,17 @@ function redirectProblem(value) {
|
|
|
108
110
|
} catch {
|
|
109
111
|
return "Must be a valid URL.";
|
|
110
112
|
}
|
|
111
|
-
if (url.protocol !== "http:") return "Must use http:.";
|
|
112
|
-
if (url.hostname !== "localhost" && url.hostname !== "127.0.0.1")
|
|
113
|
-
|
|
113
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return "Must use http: or https:.";
|
|
114
|
+
if (url.protocol === "http:" && url.hostname !== "localhost" && url.hostname !== "127.0.0.1") {
|
|
115
|
+
return "An http: URL must use the host localhost or 127.0.0.1.";
|
|
116
|
+
}
|
|
117
|
+
if (url.protocol === "http:" && url.port === "") return "An http: URL must include an explicit port.";
|
|
114
118
|
if (url.username !== "" || url.password !== "") return "Must not include credentials.";
|
|
115
119
|
if (value.includes("?") || value.includes("#")) return "Must not include a query or fragment.";
|
|
116
120
|
if (url.pathname === "/") return "Must include a path, such as /callback.";
|
|
117
121
|
return void 0;
|
|
118
122
|
}
|
|
123
|
+
var isPort = (value) => typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= 65535;
|
|
119
124
|
function parseOptions(input) {
|
|
120
125
|
const raw = input ?? {};
|
|
121
126
|
if (!isRecord(raw)) return fail("options", "Plugin options must be an object.", "Set options to an object, or remove them.");
|
|
@@ -136,7 +141,18 @@ function parseOptions(input) {
|
|
|
136
141
|
const redirectUri = oauth.redirect_uri ?? DEFAULT_REDIRECT_URI;
|
|
137
142
|
const redirectError = typeof redirectUri === "string" ? redirectProblem(redirectUri) : "Must be a string.";
|
|
138
143
|
if (typeof redirectUri !== "string" || redirectError !== void 0) {
|
|
139
|
-
return fail("oauth.redirect_uri", redirectError ?? "Must be a string.", `Use
|
|
144
|
+
return fail("oauth.redirect_uri", redirectError ?? "Must be a string.", `Use a URL listed in the app's Redirect URLs, for example ${DEFAULT_REDIRECT_URI} or ${HOSTED_REDIRECT_EXAMPLE}.`);
|
|
145
|
+
}
|
|
146
|
+
const hosted = new URL(redirectUri).protocol === "https:";
|
|
147
|
+
const callbackPort = oauth.callback_port;
|
|
148
|
+
if (callbackPort !== void 0 && !isPort(callbackPort)) {
|
|
149
|
+
return fail("oauth.callback_port", "Must be a whole number from 1 to 65535.", `Set it to the local port that receives the forwarded callback, for example ${CALLBACK_PORT_EXAMPLE}.`);
|
|
150
|
+
}
|
|
151
|
+
if (callbackPort !== void 0 && !hosted) {
|
|
152
|
+
return fail("oauth.callback_port", "Applies only to an https: oauth.redirect_uri.", "Remove it: with a loopback oauth.redirect_uri, OpenCode listens on that URL's port.");
|
|
153
|
+
}
|
|
154
|
+
if (callbackPort === void 0 && hosted) {
|
|
155
|
+
return fail("oauth.callback_port", "Must be set with an https: oauth.redirect_uri.", `Set it to the local port your proxy forwards the redirect to, for example ${CALLBACK_PORT_EXAMPLE}.`);
|
|
140
156
|
}
|
|
141
157
|
const scopes = oauth.scopes ?? DEFAULT_SCOPES;
|
|
142
158
|
const isScope = (scope) => typeof scope === "string" && NO_SPACES2.test(scope);
|
|
@@ -164,6 +180,7 @@ function parseOptions(input) {
|
|
|
164
180
|
options: {
|
|
165
181
|
...clientId === void 0 ? {} : { clientId },
|
|
166
182
|
redirectUri,
|
|
183
|
+
...callbackPort === void 0 ? {} : { callbackPort },
|
|
167
184
|
scopes: [...scopes],
|
|
168
185
|
...clientSecret === void 0 ? {} : { clientSecret },
|
|
169
186
|
server,
|
|
@@ -248,6 +265,7 @@ var status = object({
|
|
|
248
265
|
server: object({ name: text, url: text }, ["name", "url"]),
|
|
249
266
|
client: object({ id: text, mode: { enum: ["public_pkce", "confidential"] }, source: { enum: ["options", "connect"] } }, ["id", "mode", "source"]),
|
|
250
267
|
callback: text,
|
|
268
|
+
callbackPort: { type: "integer" },
|
|
251
269
|
scopes: texts,
|
|
252
270
|
asksBefore: texts,
|
|
253
271
|
tools: object({
|
|
@@ -280,6 +298,7 @@ function serverConfig(options, client) {
|
|
|
280
298
|
oauth: {
|
|
281
299
|
client_id: client.id,
|
|
282
300
|
redirect_uri: options.redirectUri,
|
|
301
|
+
...options.callbackPort === void 0 ? {} : { callback_port: options.callbackPort },
|
|
283
302
|
scope: options.scopes.join(" "),
|
|
284
303
|
...client.secret === void 0 ? {} : { client_secret: client.secret }
|
|
285
304
|
}
|
|
@@ -402,6 +421,7 @@ function buildStatus(input) {
|
|
|
402
421
|
client: { id: client.id, mode: client.secret === void 0 ? "public_pkce" : "confidential", source: client.source }
|
|
403
422
|
},
|
|
404
423
|
callback: options.redirectUri,
|
|
424
|
+
...options.callbackPort === void 0 ? {} : { callbackPort: options.callbackPort },
|
|
405
425
|
scopes: options.scopes
|
|
406
426
|
},
|
|
407
427
|
asksBefore: ASKS_BEFORE,
|
|
@@ -443,6 +463,11 @@ function buildStatus(input) {
|
|
|
443
463
|
const status2 = input.servers.find((item) => item.name === server)?.status;
|
|
444
464
|
return { ...base, ...connection(server, status2, unguardedAgents, names.length) };
|
|
445
465
|
}
|
|
466
|
+
function callbackRow(callback, port) {
|
|
467
|
+
const register = "register exactly this URL in the app's Redirect URLs";
|
|
468
|
+
if (port === void 0) return `${callback} (${register})`;
|
|
469
|
+
return `${callback} (${register}, and forward it to http://127.0.0.1:${port}${new URL(callback).pathname}, where OpenCode listens)`;
|
|
470
|
+
}
|
|
446
471
|
var CLIENT_SOURCE = { options: "set in plugin options", connect: "saved in /connect" };
|
|
447
472
|
function stateLabel(status2) {
|
|
448
473
|
switch (status2.state) {
|
|
@@ -477,7 +502,7 @@ function statusRows(status2) {
|
|
|
477
502
|
const mode = status2.client.mode === "public_pkce" ? "public, PKCE" : "confidential, secret set";
|
|
478
503
|
rows.push({ label: "Client", value: `${status2.client.id} (${mode}; ${CLIENT_SOURCE[status2.client.source]})` });
|
|
479
504
|
}
|
|
480
|
-
if (status2.callback !== void 0) rows.push({ label: "Callback", value:
|
|
505
|
+
if (status2.callback !== void 0) rows.push({ label: "Callback", value: callbackRow(status2.callback, status2.callbackPort) });
|
|
481
506
|
if (status2.scopes !== void 0) rows.push({ label: "Scopes", value: status2.scopes.join(" ") });
|
|
482
507
|
rows.push({
|
|
483
508
|
label: "Tools",
|
package/dist/tui.js
CHANGED
|
@@ -25,6 +25,7 @@ var status = object({
|
|
|
25
25
|
server: object({ name: text, url: text }, ["name", "url"]),
|
|
26
26
|
client: object({ id: text, mode: { enum: ["public_pkce", "confidential"] }, source: { enum: ["options", "connect"] } }, ["id", "mode", "source"]),
|
|
27
27
|
callback: text,
|
|
28
|
+
callbackPort: { type: "integer" },
|
|
28
29
|
scopes: texts,
|
|
29
30
|
asksBefore: texts,
|
|
30
31
|
tools: object({
|
|
@@ -97,6 +98,11 @@ function defaultRules(server) {
|
|
|
97
98
|
// src/status.ts
|
|
98
99
|
var quote = (rule) => `{ "action": "${rule.action}", "resource": "${rule.resource}", "effect": "${rule.effect}" }`;
|
|
99
100
|
var remedy = (server) => `[${defaultRules(server).map(quote).join(", ")}]`;
|
|
101
|
+
function callbackRow(callback, port) {
|
|
102
|
+
const register = "register exactly this URL in the app's Redirect URLs";
|
|
103
|
+
if (port === void 0) return `${callback} (${register})`;
|
|
104
|
+
return `${callback} (${register}, and forward it to http://127.0.0.1:${port}${new URL(callback).pathname}, where OpenCode listens)`;
|
|
105
|
+
}
|
|
100
106
|
var CLIENT_SOURCE = { options: "set in plugin options", connect: "saved in /connect" };
|
|
101
107
|
function stateLabel(status2) {
|
|
102
108
|
switch (status2.state) {
|
|
@@ -131,7 +137,7 @@ function statusRows(status2) {
|
|
|
131
137
|
const mode = status2.client.mode === "public_pkce" ? "public, PKCE" : "confidential, secret set";
|
|
132
138
|
rows.push({ label: "Client", value: `${status2.client.id} (${mode}; ${CLIENT_SOURCE[status2.client.source]})` });
|
|
133
139
|
}
|
|
134
|
-
if (status2.callback !== void 0) rows.push({ label: "Callback", value:
|
|
140
|
+
if (status2.callback !== void 0) rows.push({ label: "Callback", value: callbackRow(status2.callback, status2.callbackPort) });
|
|
135
141
|
if (status2.scopes !== void 0) rows.push({ label: "Scopes", value: status2.scopes.join(" ") });
|
|
136
142
|
rows.push({
|
|
137
143
|
label: "Tools",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@joe-broadhead/opencode-slack",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.1",
|
|
4
4
|
"description": "Unofficial OpenCode plugin for Slack",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"opencode",
|
|
@@ -55,5 +55,201 @@
|
|
|
55
55
|
"esbuild": "0.28.2",
|
|
56
56
|
"solid-js": "1.9.17",
|
|
57
57
|
"typescript": "5.9.3"
|
|
58
|
+
},
|
|
59
|
+
"jelly": {
|
|
60
|
+
"title": "Slack",
|
|
61
|
+
"tools": [
|
|
62
|
+
{
|
|
63
|
+
"name": "slack.slack_search_public",
|
|
64
|
+
"permission": "slack_slack_search_public",
|
|
65
|
+
"effect": "allow",
|
|
66
|
+
"readOnly": true
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"name": "slack.slack_search_channels",
|
|
70
|
+
"permission": "slack_slack_search_channels",
|
|
71
|
+
"effect": "allow",
|
|
72
|
+
"readOnly": true
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"name": "slack.slack_search_users",
|
|
76
|
+
"permission": "slack_slack_search_users",
|
|
77
|
+
"effect": "allow",
|
|
78
|
+
"readOnly": true
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"name": "slack.slack_read_channel",
|
|
82
|
+
"permission": "slack_slack_read_channel",
|
|
83
|
+
"effect": "allow",
|
|
84
|
+
"readOnly": true
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"name": "slack.slack_read_thread",
|
|
88
|
+
"permission": "slack_slack_read_thread",
|
|
89
|
+
"effect": "allow",
|
|
90
|
+
"readOnly": true
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"name": "slack.slack_read_user_profile",
|
|
94
|
+
"permission": "slack_slack_read_user_profile",
|
|
95
|
+
"effect": "allow",
|
|
96
|
+
"readOnly": true
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"name": "slack.slack_read_canvas",
|
|
100
|
+
"permission": "slack_slack_read_canvas",
|
|
101
|
+
"effect": "allow",
|
|
102
|
+
"readOnly": true
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"name": "slack.slack_list_channel_members",
|
|
106
|
+
"permission": "slack_slack_list_channel_members",
|
|
107
|
+
"effect": "allow",
|
|
108
|
+
"readOnly": true
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"name": "slack.slack_get_reactions",
|
|
112
|
+
"permission": "slack_slack_get_reactions",
|
|
113
|
+
"effect": "allow",
|
|
114
|
+
"readOnly": true
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"name": "slack.slack_list_user_channels",
|
|
118
|
+
"permission": "slack_slack_list_user_channels",
|
|
119
|
+
"effect": "allow",
|
|
120
|
+
"readOnly": true
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"name": "slack.slack_read_list",
|
|
124
|
+
"permission": "slack_slack_read_list",
|
|
125
|
+
"effect": "allow",
|
|
126
|
+
"readOnly": true
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"name": "slack.slack_search_emojis",
|
|
130
|
+
"permission": "slack_slack_search_emojis",
|
|
131
|
+
"effect": "allow",
|
|
132
|
+
"readOnly": true
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"name": "slack.slack_search_public_and_private",
|
|
136
|
+
"permission": "slack_slack_search_public_and_private",
|
|
137
|
+
"effect": "ask",
|
|
138
|
+
"readOnly": true
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"name": "slack.slack_read_file",
|
|
142
|
+
"permission": "slack_slack_read_file",
|
|
143
|
+
"effect": "ask",
|
|
144
|
+
"readOnly": true
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"name": "slack.slack_send_message",
|
|
148
|
+
"permission": "slack_slack_send_message",
|
|
149
|
+
"effect": "ask",
|
|
150
|
+
"readOnly": false
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
"name": "slack.slack_send_message_draft",
|
|
154
|
+
"permission": "slack_slack_send_message_draft",
|
|
155
|
+
"effect": "ask",
|
|
156
|
+
"readOnly": false
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
"name": "slack.slack_schedule_message",
|
|
160
|
+
"permission": "slack_slack_schedule_message",
|
|
161
|
+
"effect": "ask",
|
|
162
|
+
"readOnly": false
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"name": "slack.slack_create_canvas",
|
|
166
|
+
"permission": "slack_slack_create_canvas",
|
|
167
|
+
"effect": "ask",
|
|
168
|
+
"readOnly": false
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"name": "slack.slack_update_canvas",
|
|
172
|
+
"permission": "slack_slack_update_canvas",
|
|
173
|
+
"effect": "ask",
|
|
174
|
+
"readOnly": false
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
"name": "slack.slack_add_reaction",
|
|
178
|
+
"permission": "slack_slack_add_reaction",
|
|
179
|
+
"effect": "ask",
|
|
180
|
+
"readOnly": false
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"name": "slack.slack_get_file_upload_url",
|
|
184
|
+
"permission": "slack_slack_get_file_upload_url",
|
|
185
|
+
"effect": "ask",
|
|
186
|
+
"readOnly": false
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
"name": "slack.slack_complete_file_upload",
|
|
190
|
+
"permission": "slack_slack_complete_file_upload",
|
|
191
|
+
"effect": "ask",
|
|
192
|
+
"readOnly": false
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
"name": "slack.slack_create_conversation",
|
|
196
|
+
"permission": "slack_slack_create_conversation",
|
|
197
|
+
"effect": "ask",
|
|
198
|
+
"readOnly": false
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
"name": "slack.slack_create_list",
|
|
202
|
+
"permission": "slack_slack_create_list",
|
|
203
|
+
"effect": "ask",
|
|
204
|
+
"readOnly": false
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
"name": "slack.slack_update_list",
|
|
208
|
+
"permission": "slack_slack_update_list",
|
|
209
|
+
"effect": "ask",
|
|
210
|
+
"readOnly": false
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
"name": "slack.slack_add_list_record",
|
|
214
|
+
"permission": "slack_slack_add_list_record",
|
|
215
|
+
"effect": "ask",
|
|
216
|
+
"readOnly": false
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
"name": "slack.slack_update_list_record",
|
|
220
|
+
"permission": "slack_slack_update_list_record",
|
|
221
|
+
"effect": "ask",
|
|
222
|
+
"readOnly": false
|
|
223
|
+
}
|
|
224
|
+
],
|
|
225
|
+
"skills": [
|
|
226
|
+
"slack-search"
|
|
227
|
+
],
|
|
228
|
+
"rpc": [],
|
|
229
|
+
"views": [],
|
|
230
|
+
"signIns": [
|
|
231
|
+
{
|
|
232
|
+
"id": "app",
|
|
233
|
+
"label": "Slack app",
|
|
234
|
+
"integration": "slack-app",
|
|
235
|
+
"presetBy": "oauth.client_id",
|
|
236
|
+
"help": {
|
|
237
|
+
"url": "https://github.com/joe-broadhead/opencode-slack/blob/HEAD/docs/slack-app-manifest.json",
|
|
238
|
+
"label": "Create the Slack app from its manifest, then paste its client ID"
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
"id": "slack",
|
|
243
|
+
"label": "Slack",
|
|
244
|
+
"server": "slack",
|
|
245
|
+
"after": "app"
|
|
246
|
+
}
|
|
247
|
+
],
|
|
248
|
+
"contributes": {
|
|
249
|
+
"homeFeeds": [],
|
|
250
|
+
"homeWidgets": [],
|
|
251
|
+
"workKinds": [],
|
|
252
|
+
"settings": []
|
|
253
|
+
}
|
|
58
254
|
}
|
|
59
255
|
}
|