@orderful/droid 0.40.0 → 0.41.0
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/.claude-plugin/plugin.json +2 -0
- package/CHANGELOG.md +12 -0
- package/dist/bin/droid.js +88 -32
- package/dist/commands/integrations.d.ts.map +1 -1
- package/dist/commands/tui/components/IntegrationsDetails.d.ts +7 -1
- package/dist/commands/tui/components/IntegrationsDetails.d.ts.map +1 -1
- package/dist/commands/tui.d.ts.map +1 -1
- package/dist/integrations/atlassian/references/setup.md +59 -0
- package/dist/lib/types.d.ts +4 -0
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/tools/share/.claude-plugin/plugin.json +22 -0
- package/dist/tools/share/TOOL.yaml +17 -0
- package/dist/tools/share/commands/share.md +32 -0
- package/dist/tools/share/skills/share/SKILL.md +211 -0
- package/package.json +1 -1
- package/src/commands/integrations.ts +17 -5
- package/src/commands/tui/components/IntegrationsDetails.tsx +71 -4
- package/src/commands/tui.tsx +41 -20
- package/src/integrations/atlassian/references/setup.md +59 -0
- package/src/lib/types.ts +5 -0
- package/src/tools/share/.claude-plugin/plugin.json +22 -0
- package/src/tools/share/TOOL.yaml +17 -0
- package/src/tools/share/commands/share.md +32 -0
- package/src/tools/share/skills/share/SKILL.md +211 -0
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"./src/tools/droid/skills/droid-bootstrap/SKILL.md",
|
|
25
25
|
"./src/tools/plan/skills/plan/SKILL.md",
|
|
26
26
|
"./src/tools/project/skills/project/SKILL.md",
|
|
27
|
+
"./src/tools/share/skills/share/SKILL.md",
|
|
27
28
|
"./src/tools/status-update/skills/status-update/SKILL.md",
|
|
28
29
|
"./src/tools/tech-design/skills/tech-design/SKILL.md",
|
|
29
30
|
"./src/tools/wrapup/skills/wrapup/SKILL.md"
|
|
@@ -37,6 +38,7 @@
|
|
|
37
38
|
"./src/tools/droid/commands/setup.md",
|
|
38
39
|
"./src/tools/plan/commands/plan.md",
|
|
39
40
|
"./src/tools/project/commands/project.md",
|
|
41
|
+
"./src/tools/share/commands/share.md",
|
|
40
42
|
"./src/tools/status-update/commands/status-update.md",
|
|
41
43
|
"./src/tools/tech-design/commands/tech-design.md",
|
|
42
44
|
"./src/tools/wrapup/commands/wrapup.md"
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @orderful/droid
|
|
2
2
|
|
|
3
|
+
## 0.41.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#248](https://github.com/Orderful/droid/pull/248) [`1172a61`](https://github.com/Orderful/droid/commit/1172a61758b44483126918352a580d9722ffc5b0) Thanks [@frytyler](https://github.com/frytyler)! - Add /share tool for sharing content to Confluence (publish pages) and Slack (post summaries with free-form instructions), and add Atlassian to the TUI Integrations tab
|
|
8
|
+
|
|
9
|
+
## 0.40.1
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [#246](https://github.com/Orderful/droid/pull/246) [`4fd0832`](https://github.com/Orderful/droid/commit/4fd0832b740b2fe168c438f6b3aa4e269098598a) Thanks [@frytyler](https://github.com/frytyler)! - Fix dynamic require('fs') error in ESM bundle for integrations setup
|
|
14
|
+
|
|
3
15
|
## 0.40.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/dist/bin/droid.js
CHANGED
|
@@ -1,10 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
3
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
4
|
-
}) : x)(function(x) {
|
|
5
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
6
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
-
});
|
|
8
2
|
|
|
9
3
|
// src/bin/droid.ts
|
|
10
4
|
import { program } from "commander";
|
|
@@ -2827,10 +2821,7 @@ function SettingsDetails({
|
|
|
2827
2821
|
// src/commands/tui/components/IntegrationsDetails.tsx
|
|
2828
2822
|
import { Box as Box6, Text as Text6 } from "ink";
|
|
2829
2823
|
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
2830
|
-
function
|
|
2831
|
-
isFocused,
|
|
2832
|
-
selectedAction
|
|
2833
|
-
}) {
|
|
2824
|
+
function SlackDetails({ isFocused, selectedAction }) {
|
|
2834
2825
|
const hasToken = !!process.env.SLACK_USER_TOKEN;
|
|
2835
2826
|
const hasClientId = !!process.env.SLACK_CLIENT_ID;
|
|
2836
2827
|
const hasClientSecret = !!process.env.SLACK_CLIENT_SECRET;
|
|
@@ -2892,6 +2883,48 @@ function IntegrationsDetails({
|
|
|
2892
2883
|
!isFocused && /* @__PURE__ */ jsx6(Box6, { marginTop: 2, children: /* @__PURE__ */ jsx6(Text6, { color: colors.textDim, children: "press enter for options" }) })
|
|
2893
2884
|
] });
|
|
2894
2885
|
}
|
|
2886
|
+
function AtlassianDetails({ isFocused, selectedAction, connected }) {
|
|
2887
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", paddingLeft: 2, flexGrow: 1, children: [
|
|
2888
|
+
/* @__PURE__ */ jsx6(Text6, { color: colors.text, bold: true, children: "Atlassian" }),
|
|
2889
|
+
/* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", marginTop: 1, children: [
|
|
2890
|
+
/* @__PURE__ */ jsx6(Text6, { color: colors.textDim, bold: true, children: "MCP Server" }),
|
|
2891
|
+
/* @__PURE__ */ jsxs6(Text6, { children: [
|
|
2892
|
+
/* @__PURE__ */ jsx6(Text6, { color: colors.textDim, children: " Status " }),
|
|
2893
|
+
connected ? /* @__PURE__ */ jsx6(Text6, { color: colors.success, children: "Connected \u2713" }) : /* @__PURE__ */ jsx6(Text6, { color: "#fbbf24", children: "Not yet verified" })
|
|
2894
|
+
] })
|
|
2895
|
+
] }),
|
|
2896
|
+
/* @__PURE__ */ jsx6(Box6, { flexDirection: "column", marginTop: 1, children: /* @__PURE__ */ jsx6(Text6, { color: colors.textDim, children: connected ? "Confluence and Jira available via MCP" : "Use /share confluence to verify connection" }) }),
|
|
2897
|
+
isFocused && /* @__PURE__ */ jsx6(Box6, { marginTop: 2, flexDirection: "row", gap: 2, children: /* @__PURE__ */ jsxs6(
|
|
2898
|
+
Text6,
|
|
2899
|
+
{
|
|
2900
|
+
backgroundColor: selectedAction === 0 ? colors.primary : void 0,
|
|
2901
|
+
color: selectedAction === 0 ? "#ffffff" : colors.textDim,
|
|
2902
|
+
bold: selectedAction === 0,
|
|
2903
|
+
children: [
|
|
2904
|
+
" ",
|
|
2905
|
+
"Setup Guide",
|
|
2906
|
+
" "
|
|
2907
|
+
]
|
|
2908
|
+
}
|
|
2909
|
+
) }),
|
|
2910
|
+
isFocused && /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsxs6(Text6, { color: colors.textDim, children: [
|
|
2911
|
+
"enter confirm ",
|
|
2912
|
+
"\xB7",
|
|
2913
|
+
" esc back"
|
|
2914
|
+
] }) }),
|
|
2915
|
+
!isFocused && /* @__PURE__ */ jsx6(Box6, { marginTop: 2, children: /* @__PURE__ */ jsx6(Text6, { color: colors.textDim, children: "press enter for options" }) })
|
|
2916
|
+
] });
|
|
2917
|
+
}
|
|
2918
|
+
function IntegrationsDetails({
|
|
2919
|
+
isFocused,
|
|
2920
|
+
selectedAction,
|
|
2921
|
+
integration
|
|
2922
|
+
}) {
|
|
2923
|
+
if (integration.id === "atlassian") {
|
|
2924
|
+
return /* @__PURE__ */ jsx6(AtlassianDetails, { isFocused, selectedAction, connected: integration.connected });
|
|
2925
|
+
}
|
|
2926
|
+
return /* @__PURE__ */ jsx6(SlackDetails, { isFocused, selectedAction });
|
|
2927
|
+
}
|
|
2895
2928
|
|
|
2896
2929
|
// src/commands/tui/components/PlatformBadges.tsx
|
|
2897
2930
|
import { Box as Box7, Text as Text7 } from "ink";
|
|
@@ -4348,6 +4381,10 @@ function App() {
|
|
|
4348
4381
|
};
|
|
4349
4382
|
const tools = getBundledTools();
|
|
4350
4383
|
const skills = getBundledSkills();
|
|
4384
|
+
const integrations2 = [
|
|
4385
|
+
{ id: "slack", name: "Slack", connected: !!process.env.SLACK_USER_TOKEN },
|
|
4386
|
+
{ id: "atlassian", name: "Atlassian", connected: !!getConfigValue("integrations.atlassian.configured") }
|
|
4387
|
+
];
|
|
4351
4388
|
useInput9(
|
|
4352
4389
|
(input, key) => {
|
|
4353
4390
|
if (message) setMessage(null);
|
|
@@ -4381,7 +4418,7 @@ function App() {
|
|
|
4381
4418
|
setSelectedAction(0);
|
|
4382
4419
|
}
|
|
4383
4420
|
if (key.downArrow) {
|
|
4384
|
-
const maxIndex = activeTab === "tools" ? tools.length - 1 : activeTab === "integrations" ?
|
|
4421
|
+
const maxIndex = activeTab === "tools" ? tools.length - 1 : activeTab === "integrations" ? integrations2.length - 1 : 0;
|
|
4385
4422
|
setSelectedIndex((prev) => {
|
|
4386
4423
|
const newIndex = Math.min(maxIndex, prev + 1);
|
|
4387
4424
|
if (newIndex >= scrollOffset + MAX_VISIBLE_ITEMS) {
|
|
@@ -4406,12 +4443,18 @@ function App() {
|
|
|
4406
4443
|
setSelectedAction(0);
|
|
4407
4444
|
}
|
|
4408
4445
|
if (activeTab === "integrations") {
|
|
4409
|
-
const
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4446
|
+
const currentIntegration = integrations2[selectedIndex];
|
|
4447
|
+
let intActions = [];
|
|
4448
|
+
if (currentIntegration?.id === "slack") {
|
|
4449
|
+
const hasClientCreds = !!process.env.SLACK_CLIENT_ID && !!process.env.SLACK_CLIENT_SECRET;
|
|
4450
|
+
const canRunSetup = hasClientCreds && !process.env.SLACK_USER_TOKEN;
|
|
4451
|
+
intActions = [
|
|
4452
|
+
...canRunSetup ? [{ id: "setup" }] : [],
|
|
4453
|
+
{ id: "guide" }
|
|
4454
|
+
];
|
|
4455
|
+
} else if (currentIntegration?.id === "atlassian") {
|
|
4456
|
+
intActions = [{ id: "guide" }];
|
|
4457
|
+
}
|
|
4415
4458
|
const maxIntAction = intActions.length - 1;
|
|
4416
4459
|
if (key.leftArrow) {
|
|
4417
4460
|
setSelectedAction((prev) => Math.max(0, prev - 1));
|
|
@@ -4421,14 +4464,16 @@ function App() {
|
|
|
4421
4464
|
}
|
|
4422
4465
|
if (key.return) {
|
|
4423
4466
|
const actionId = intActions[selectedAction]?.id;
|
|
4424
|
-
if (actionId === "setup") {
|
|
4467
|
+
if (actionId === "setup" && currentIntegration?.id === "slack") {
|
|
4425
4468
|
exitCommand = ["droid", "integrations", "setup", "slack"];
|
|
4426
4469
|
exit();
|
|
4427
4470
|
} else if (actionId === "guide") {
|
|
4428
|
-
const
|
|
4471
|
+
const integrationId = currentIntegration?.id ?? "slack";
|
|
4472
|
+
const guideTitle = currentIntegration?.id === "atlassian" ? "Atlassian Integration Setup" : "Slack Integration Setup";
|
|
4473
|
+
const content = loadIntegrationReference(integrationId, "setup.md");
|
|
4429
4474
|
if (content) {
|
|
4430
4475
|
setPreviousView("detail");
|
|
4431
|
-
setReadmeContent({ title:
|
|
4476
|
+
setReadmeContent({ title: guideTitle, content });
|
|
4432
4477
|
setView("readme");
|
|
4433
4478
|
} else {
|
|
4434
4479
|
setMessage({ text: "Could not load setup guide", type: "error" });
|
|
@@ -4708,11 +4753,14 @@ function App() {
|
|
|
4708
4753
|
" tools total"
|
|
4709
4754
|
] }) })
|
|
4710
4755
|
] }),
|
|
4711
|
-
activeTab === "integrations" && /* @__PURE__ */ jsx17(Box16, { paddingX: 1, children: /* @__PURE__ */ jsxs16(Text17, { children: [
|
|
4712
|
-
selectedIndex ===
|
|
4713
|
-
/* @__PURE__ */
|
|
4714
|
-
|
|
4715
|
-
|
|
4756
|
+
activeTab === "integrations" && /* @__PURE__ */ jsx17(Box16, { flexDirection: "column", paddingX: 1, children: integrations2.map((integration, index) => /* @__PURE__ */ jsxs16(Text17, { children: [
|
|
4757
|
+
selectedIndex === index ? /* @__PURE__ */ jsx17(Text17, { color: colors.primary, children: ">" }) : /* @__PURE__ */ jsx17(Text17, { children: " " }),
|
|
4758
|
+
/* @__PURE__ */ jsxs16(Text17, { children: [
|
|
4759
|
+
" ",
|
|
4760
|
+
integration.name
|
|
4761
|
+
] }),
|
|
4762
|
+
integration.connected ? /* @__PURE__ */ jsx17(Text17, { color: colors.success, children: " \u2713" }) : /* @__PURE__ */ jsx17(Text17, { color: colors.textDim, children: " \u2717" })
|
|
4763
|
+
] }, integration.id)) }),
|
|
4716
4764
|
activeTab === "settings" && /* @__PURE__ */ jsx17(Box16, { paddingX: 1, children: /* @__PURE__ */ jsx17(Text17, { color: colors.textDim, children: "View and edit config" }) })
|
|
4717
4765
|
] }),
|
|
4718
4766
|
message && /* @__PURE__ */ jsx17(Box16, { paddingX: 1, marginTop: 1, children: /* @__PURE__ */ jsx17(
|
|
@@ -4734,11 +4782,12 @@ function App() {
|
|
|
4734
4782
|
selectedAction
|
|
4735
4783
|
}
|
|
4736
4784
|
),
|
|
4737
|
-
activeTab === "integrations" && /* @__PURE__ */ jsx17(
|
|
4785
|
+
activeTab === "integrations" && integrations2[selectedIndex] && /* @__PURE__ */ jsx17(
|
|
4738
4786
|
IntegrationsDetails,
|
|
4739
4787
|
{
|
|
4740
4788
|
isFocused: view === "detail",
|
|
4741
|
-
selectedAction
|
|
4789
|
+
selectedAction,
|
|
4790
|
+
integration: integrations2[selectedIndex]
|
|
4742
4791
|
}
|
|
4743
4792
|
),
|
|
4744
4793
|
activeTab === "settings" && /* @__PURE__ */ jsx17(
|
|
@@ -4968,7 +5017,7 @@ import inquirer5 from "inquirer";
|
|
|
4968
5017
|
import chalk11 from "chalk";
|
|
4969
5018
|
import { execSync as execSync6 } from "child_process";
|
|
4970
5019
|
import { createServer } from "https";
|
|
4971
|
-
import { readFileSync as readFileSync10 } from "fs";
|
|
5020
|
+
import { readFileSync as readFileSync10, mkdirSync as mkdirSync7, appendFileSync as appendFileSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
4972
5021
|
import { join as join13 } from "path";
|
|
4973
5022
|
|
|
4974
5023
|
// src/integrations/slack/index.ts
|
|
@@ -5101,7 +5150,6 @@ function getOrCreateCert() {
|
|
|
5101
5150
|
if (key && cert) return { key, cert };
|
|
5102
5151
|
} catch {
|
|
5103
5152
|
}
|
|
5104
|
-
const { mkdirSync: mkdirSync7 } = __require("fs");
|
|
5105
5153
|
mkdirSync7(CERT_DIR, { recursive: true });
|
|
5106
5154
|
execSync6(
|
|
5107
5155
|
`openssl req -x509 -newkey rsa:2048 -keyout "${CERT_KEY_PATH}" -out "${CERT_PATH}" -days 365 -nodes -subj "/CN=localhost" -addext "subjectAltName=DNS:localhost,IP:127.0.0.1" 2>/dev/null`,
|
|
@@ -5171,8 +5219,7 @@ function writeTokenToShellRc(key, value) {
|
|
|
5171
5219
|
const exportLine = buildExportLine(key, value, isFish);
|
|
5172
5220
|
const fullPath = expandHome(rcPath);
|
|
5173
5221
|
try {
|
|
5174
|
-
const
|
|
5175
|
-
const existing = readFs(fullPath, "utf-8");
|
|
5222
|
+
const existing = readFileSync10(fullPath, "utf-8");
|
|
5176
5223
|
if (existing.includes(`${key}=`) || existing.includes(`${key} "`)) {
|
|
5177
5224
|
const lines = existing.split("\n");
|
|
5178
5225
|
const pattern = isFish ? new RegExp(`^set\\s+-gx\\s+${key}\\s+`) : new RegExp(`^export\\s+${key}=`);
|
|
@@ -5185,7 +5232,6 @@ function writeTokenToShellRc(key, value) {
|
|
|
5185
5232
|
return line;
|
|
5186
5233
|
});
|
|
5187
5234
|
if (replaced) {
|
|
5188
|
-
const { writeFileSync: writeFileSync6 } = __require("fs");
|
|
5189
5235
|
writeFileSync6(fullPath, updated.join("\n"), "utf-8");
|
|
5190
5236
|
} else {
|
|
5191
5237
|
appendFileSync2(fullPath, `
|
|
@@ -5350,6 +5396,16 @@ async function integrationsStatusCommand() {
|
|
|
5350
5396
|
console.log(chalk11.yellow(" Status: not configured"));
|
|
5351
5397
|
}
|
|
5352
5398
|
console.log("");
|
|
5399
|
+
console.log(chalk11.bold(" Atlassian"));
|
|
5400
|
+
const atlassianConfigured = getConfigValue("integrations.atlassian.configured");
|
|
5401
|
+
if (atlassianConfigured) {
|
|
5402
|
+
console.log(chalk11.green(" MCP: Connected"));
|
|
5403
|
+
console.log(chalk11.green(" Status: configured"));
|
|
5404
|
+
} else {
|
|
5405
|
+
console.log(chalk11.yellow(" MCP: Not yet verified"));
|
|
5406
|
+
console.log(chalk11.gray(" Use /share confluence to verify, or see setup guide"));
|
|
5407
|
+
}
|
|
5408
|
+
console.log("");
|
|
5353
5409
|
}
|
|
5354
5410
|
async function slackPostCommand(options) {
|
|
5355
5411
|
let input;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"integrations.d.ts","sourceRoot":"","sources":["../../src/commands/integrations.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"integrations.d.ts","sourceRoot":"","sources":["../../src/commands/integrations.ts"],"names":[],"mappings":"AAoNA,wBAAsB,6BAA6B,IAAI,OAAO,CAAC,IAAI,CAAC,CAuInE;AAED,wBAAsB,yBAAyB,IAAI,OAAO,CAAC,IAAI,CAAC,CAoD/D;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAsCnF"}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
+
export interface Integration {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
connected: boolean;
|
|
5
|
+
}
|
|
1
6
|
export interface IntegrationsDetailsProps {
|
|
2
7
|
isFocused: boolean;
|
|
3
8
|
selectedAction: number;
|
|
9
|
+
integration: Integration;
|
|
4
10
|
}
|
|
5
|
-
export declare function IntegrationsDetails({ isFocused, selectedAction, }: IntegrationsDetailsProps): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export declare function IntegrationsDetails({ isFocused, selectedAction, integration, }: IntegrationsDetailsProps): import("react/jsx-runtime").JSX.Element;
|
|
6
12
|
//# sourceMappingURL=IntegrationsDetails.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IntegrationsDetails.d.ts","sourceRoot":"","sources":["../../../../src/commands/tui/components/IntegrationsDetails.tsx"],"names":[],"mappings":"AAGA,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"IntegrationsDetails.d.ts","sourceRoot":"","sources":["../../../../src/commands/tui/components/IntegrationsDetails.tsx"],"names":[],"mappings":"AAGA,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,WAAW,CAAC;CAC1B;AAyID,wBAAgB,mBAAmB,CAAC,EAClC,SAAS,EACT,cAAc,EACd,WAAW,GACZ,EAAE,wBAAwB,2CAO1B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tui.d.ts","sourceRoot":"","sources":["../../src/commands/tui.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"tui.d.ts","sourceRoot":"","sources":["../../src/commands/tui.tsx"],"names":[],"mappings":"AA4rBA,wBAAsB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAyBhD"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Atlassian Integration Setup
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The Atlassian integration connects droid to Confluence (and Jira) via the official Atlassian MCP server. This enables the `/share` tool to publish markdown files directly to Confluence pages.
|
|
6
|
+
|
|
7
|
+
## Setup
|
|
8
|
+
|
|
9
|
+
### 1. Add the Atlassian MCP Server
|
|
10
|
+
|
|
11
|
+
In Claude Code, run:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
/mcp
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Select **Atlassian** from the available MCP servers and follow the authentication prompts. This connects your Atlassian account (Confluence + Jira) to Claude Code.
|
|
18
|
+
|
|
19
|
+
### 2. Verify Connection
|
|
20
|
+
|
|
21
|
+
Run `/share confluence` in a Claude Code session. If the connection is working, you'll see a list of available Confluence spaces.
|
|
22
|
+
|
|
23
|
+
Alternatively, the TUI Integrations tab will show `Atlassian ✓` once the MCP has been used successfully.
|
|
24
|
+
|
|
25
|
+
## How It Works
|
|
26
|
+
|
|
27
|
+
Unlike Slack (which uses environment variables and OAuth), Atlassian uses Claude Code's built-in MCP server system:
|
|
28
|
+
|
|
29
|
+
| Aspect | Slack | Atlassian |
|
|
30
|
+
|--------|-------|-----------|
|
|
31
|
+
| Auth method | OAuth + env vars | MCP server (managed by Claude Code) |
|
|
32
|
+
| Setup | `droid integrations setup slack` | `/mcp` in Claude Code |
|
|
33
|
+
| API access | `@slack/web-api` SDK | MCP tool calls (`mcp__claude_ai_Atlassian__*`) |
|
|
34
|
+
| Config flag | `integrations.slack.configured` | `integrations.atlassian.configured` |
|
|
35
|
+
|
|
36
|
+
The `configured` flag is set automatically on first successful MCP call — no manual configuration needed.
|
|
37
|
+
|
|
38
|
+
## Troubleshooting
|
|
39
|
+
|
|
40
|
+
| Issue | Resolution |
|
|
41
|
+
|-------|------------|
|
|
42
|
+
| MCP not available | Run `/mcp` in Claude Code to add the Atlassian server |
|
|
43
|
+
| No Confluence spaces listed | Check your Atlassian account has Confluence access |
|
|
44
|
+
| Permission errors | Verify your account has edit permissions in the target space |
|
|
45
|
+
| "Not configured" in TUI | Use `/share confluence` once — the flag is set on first success |
|
|
46
|
+
|
|
47
|
+
## Usage
|
|
48
|
+
|
|
49
|
+
Once connected, use the `/share` command:
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
/share confluence # Interactive - prompts for file
|
|
53
|
+
/share confluence path/to/file.md # Share specific file
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The share skill handles:
|
|
57
|
+
- Choosing a Confluence space and parent page
|
|
58
|
+
- Creating new pages or updating existing ones
|
|
59
|
+
- Storing the page ID in the file's frontmatter for future updates
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -44,8 +44,12 @@ export interface IntegrationSlackConfig {
|
|
|
44
44
|
configured?: boolean;
|
|
45
45
|
crosspost_channel?: string;
|
|
46
46
|
}
|
|
47
|
+
export interface IntegrationAtlassianConfig {
|
|
48
|
+
configured?: boolean;
|
|
49
|
+
}
|
|
47
50
|
export interface IntegrationsConfig {
|
|
48
51
|
slack?: IntegrationSlackConfig;
|
|
52
|
+
atlassian?: IntegrationAtlassianConfig;
|
|
49
53
|
}
|
|
50
54
|
export interface DroidConfig {
|
|
51
55
|
platform: Platform;
|
package/dist/lib/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/lib/types.ts"],"names":[],"mappings":"AAAA,oBAAY,QAAQ;IAClB,UAAU,gBAAgB;IAC1B,QAAQ,aAAa;IACrB,MAAM,WAAW;IACjB,WAAW,iBAAiB;CAC7B;AAID,QAAA,MAAM,WAAW,iBAAW,CAAC;AAC7B,OAAO,EAAE,WAAW,IAAI,MAAM,EAAE,CAAC;AACjC,MAAM,MAAM,MAAM,GAAG,QAAQ,CAAC;AAE9B;;;GAGG;AACH,wBAAgB,QAAQ,IAAI,MAAM,CAEjC;AAGD,oBAAY,aAAa;IACvB,QAAQ,aAAa;IACrB,MAAM,WAAW;CAClB;AAGD,MAAM,MAAM,gBAAgB,GAAG,aAAa,GAAG,MAAM,CAAC;AAEtD,oBAAY,WAAW;IACrB,MAAM,WAAW;IACjB,IAAI,SAAS;IACb,KAAK,UAAU;CAChB;AAED,oBAAY,gBAAgB;IAC1B,MAAM,WAAW;IACjB,OAAO,YAAY;IACnB,MAAM,WAAW;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;CACvC;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,OAAO,CAAC;IACb,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAGD,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEjD,MAAM,WAAW,sBAAsB;IACrC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/lib/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "droid-share",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Share content to external platforms. Publish docs to Confluence or post summaries to Slack.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Orderful",
|
|
7
|
+
"url": "https://github.com/orderful"
|
|
8
|
+
},
|
|
9
|
+
"repository": "https://github.com/orderful/droid",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"keywords": [
|
|
12
|
+
"droid",
|
|
13
|
+
"ai",
|
|
14
|
+
"share"
|
|
15
|
+
],
|
|
16
|
+
"skills": [
|
|
17
|
+
"./skills/share/SKILL.md"
|
|
18
|
+
],
|
|
19
|
+
"commands": [
|
|
20
|
+
"./commands/share.md"
|
|
21
|
+
]
|
|
22
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
name: share
|
|
2
|
+
description: "Share content to external platforms. Publish docs to Confluence or post summaries to Slack."
|
|
3
|
+
version: 0.1.0
|
|
4
|
+
status: beta
|
|
5
|
+
|
|
6
|
+
includes:
|
|
7
|
+
skills:
|
|
8
|
+
- name: share
|
|
9
|
+
required: true
|
|
10
|
+
commands:
|
|
11
|
+
- name: share
|
|
12
|
+
is_alias: false
|
|
13
|
+
agents: []
|
|
14
|
+
|
|
15
|
+
dependencies: []
|
|
16
|
+
|
|
17
|
+
config_schema: {}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: share
|
|
3
|
+
description: "Share content to external platforms. Publish docs to Confluence or post summaries to Slack with free-form instructions."
|
|
4
|
+
argument-hint: "[confluence|slack] [{file|#channel}] [-- {instructions}]"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# /share
|
|
8
|
+
|
|
9
|
+
**User invoked:** `/share $ARGUMENTS`
|
|
10
|
+
|
|
11
|
+
**Your task:** Invoke the **share skill** with these arguments.
|
|
12
|
+
|
|
13
|
+
## Examples
|
|
14
|
+
|
|
15
|
+
- `/share` → Interactive mode, prompts for platform and file
|
|
16
|
+
- `/share confluence` → Publish to Confluence, prompts for file
|
|
17
|
+
- `/share confluence path/to/file.md` → Publish specific file to Confluence
|
|
18
|
+
- `/share slack #eng` → Share to Slack channel, prompts for content
|
|
19
|
+
- `/share slack #eng -- summarise the action items from this meeting`
|
|
20
|
+
- `/share slack #eng-design -- TLDR of this research doc with a link`
|
|
21
|
+
|
|
22
|
+
## Quick Reference
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
/share # Interactive
|
|
26
|
+
/share confluence # Publish doc to Confluence
|
|
27
|
+
/share confluence {file} # Publish specific file
|
|
28
|
+
/share slack #channel # Share to Slack (prompts for content)
|
|
29
|
+
/share slack #channel -- {instructions} # Share with specific instructions
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
See the **share skill** for complete documentation.
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: share
|
|
3
|
+
description: "Share content to external platforms. Publish docs to Confluence or post summaries to Slack. User prompts like 'share this to confluence', 'share on slack', 'post a summary to #eng'."
|
|
4
|
+
argument-hint: "[confluence|slack] [{file|#channel}] [-- {instructions}]"
|
|
5
|
+
allowed-tools:
|
|
6
|
+
[
|
|
7
|
+
Read,
|
|
8
|
+
Edit,
|
|
9
|
+
Glob,
|
|
10
|
+
Grep,
|
|
11
|
+
Bash(droid:*),
|
|
12
|
+
]
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Share Skill
|
|
16
|
+
|
|
17
|
+
Share content to external platforms. Two modes:
|
|
18
|
+
|
|
19
|
+
- **Confluence** — publish a full markdown file as a Confluence page (create or update)
|
|
20
|
+
- **Slack** — process content with free-form instructions and post the result to a channel
|
|
21
|
+
|
|
22
|
+
## Supported Platforms
|
|
23
|
+
|
|
24
|
+
| Platform | Backend | Mode |
|
|
25
|
+
|----------|---------|------|
|
|
26
|
+
| Confluence | Atlassian MCP (`mcp__claude_ai_Atlassian__*`) | Publish document as page |
|
|
27
|
+
| Slack | `droid integrations slack post` | Process + post message |
|
|
28
|
+
|
|
29
|
+
## Procedure
|
|
30
|
+
|
|
31
|
+
### 1. Resolve Platform
|
|
32
|
+
|
|
33
|
+
**If platform provided in arguments** (e.g. `confluence`, `slack`): use it directly.
|
|
34
|
+
|
|
35
|
+
**If not provided:** Ask the user: "Where do you want to share? (Confluence / Slack)"
|
|
36
|
+
|
|
37
|
+
Then branch to the appropriate flow below.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Confluence Flow
|
|
42
|
+
|
|
43
|
+
### C1. Resolve File
|
|
44
|
+
|
|
45
|
+
**If file path provided in arguments:** use it directly.
|
|
46
|
+
|
|
47
|
+
**If not provided:**
|
|
48
|
+
1. Check conversation context for an active file (e.g. brain doc being worked on)
|
|
49
|
+
2. If none, ask: "Which file would you like to publish?"
|
|
50
|
+
|
|
51
|
+
Read the file to confirm it exists.
|
|
52
|
+
|
|
53
|
+
### C2. Parse File
|
|
54
|
+
|
|
55
|
+
1. **Extract title:** Check YAML frontmatter for `title` field. If none, use the first `# heading`. If neither, use the filename without extension.
|
|
56
|
+
2. **Strip YAML frontmatter** from the content that will be sent (the `---` delimited block at the top). Keep the raw markdown body.
|
|
57
|
+
3. **Check for existing page ID:** Look for `confluence_page_id` in frontmatter — if present, this is an update rather than a create.
|
|
58
|
+
|
|
59
|
+
### C3. Verify Atlassian MCP Connection
|
|
60
|
+
|
|
61
|
+
Use `ToolSearch` to load `mcp__claude_ai_Atlassian__getAccessibleAtlassianResources`, then call it.
|
|
62
|
+
|
|
63
|
+
**If MCP is unavailable or errors:**
|
|
64
|
+
Tell the user: "Atlassian MCP is not connected. Run `/mcp` in Claude Code to add the Atlassian integration, then try again."
|
|
65
|
+
Stop here.
|
|
66
|
+
|
|
67
|
+
**If successful:**
|
|
68
|
+
- Extract the `id` (cloud ID) from the first accessible resource
|
|
69
|
+
- Mark integration as configured:
|
|
70
|
+
```bash
|
|
71
|
+
droid config --set "integrations.atlassian.configured=true"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### C4. Resolve Confluence Space
|
|
75
|
+
|
|
76
|
+
Use `ToolSearch` to load `mcp__claude_ai_Atlassian__getConfluenceSpaces`, then call it with the cloud ID.
|
|
77
|
+
|
|
78
|
+
Present the list of spaces to the user and ask them to pick one.
|
|
79
|
+
|
|
80
|
+
### C5. Resolve Parent Page (Optional)
|
|
81
|
+
|
|
82
|
+
Ask the user: "Place this page under a specific parent, or at the space root?"
|
|
83
|
+
|
|
84
|
+
If they want a parent:
|
|
85
|
+
- Use `mcp__claude_ai_Atlassian__getPagesInConfluenceSpace` to list top-level pages
|
|
86
|
+
- Let the user pick a parent page
|
|
87
|
+
- If they need to go deeper, use `mcp__claude_ai_Atlassian__getConfluencePageDescendants` to navigate
|
|
88
|
+
|
|
89
|
+
If space root: no parent ID needed.
|
|
90
|
+
|
|
91
|
+
### C6. Confirm Title
|
|
92
|
+
|
|
93
|
+
Pre-fill from the extracted title (step C2). Ask the user to confirm or edit.
|
|
94
|
+
|
|
95
|
+
### C7. Create or Update Page
|
|
96
|
+
|
|
97
|
+
#### Update (frontmatter has `confluence_page_id`)
|
|
98
|
+
|
|
99
|
+
1. **Check for comments:** Use `mcp__claude_ai_Atlassian__getConfluencePageInlineComments` and `mcp__claude_ai_Atlassian__getConfluencePageFooterComments` to check for comments on the existing page.
|
|
100
|
+
- If comments exist, warn: "This page has N comments that may reference content being replaced. Continue?"
|
|
101
|
+
- If user declines, stop.
|
|
102
|
+
|
|
103
|
+
2. **Update:** Use `mcp__claude_ai_Atlassian__updateConfluencePage` with:
|
|
104
|
+
- `id`: the `confluence_page_id` from frontmatter
|
|
105
|
+
- `title`: confirmed title
|
|
106
|
+
- `body`: markdown content (frontmatter stripped)
|
|
107
|
+
- `contentFormat`: `"markdown"`
|
|
108
|
+
|
|
109
|
+
#### Create (no existing page ID)
|
|
110
|
+
|
|
111
|
+
Use `mcp__claude_ai_Atlassian__createConfluencePage` with:
|
|
112
|
+
- `spaceId`: chosen space ID
|
|
113
|
+
- `title`: confirmed title
|
|
114
|
+
- `body`: markdown content (frontmatter stripped)
|
|
115
|
+
- `contentFormat`: `"markdown"`
|
|
116
|
+
- `parentId`: parent page ID (if chosen, otherwise omit)
|
|
117
|
+
|
|
118
|
+
### C8. Update Frontmatter
|
|
119
|
+
|
|
120
|
+
After successful create or update, use `Edit` to add/update frontmatter fields in the source file:
|
|
121
|
+
|
|
122
|
+
- `confluence_page_id`: the page ID from the response
|
|
123
|
+
- `confluence_url`: the page URL (construct from cloud URL + page `_links.webui` or similar)
|
|
124
|
+
- `confluence_last_shared`: ISO 8601 timestamp of when this was shared
|
|
125
|
+
|
|
126
|
+
If the file has no frontmatter, add a `---` delimited block at the top.
|
|
127
|
+
|
|
128
|
+
### C9. Return Result
|
|
129
|
+
|
|
130
|
+
Tell the user:
|
|
131
|
+
- "Published to Confluence: {url}"
|
|
132
|
+
- Or "Updated Confluence page: {url}"
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Slack Flow
|
|
137
|
+
|
|
138
|
+
### S1. Resolve Channel
|
|
139
|
+
|
|
140
|
+
**If `#channel` provided in arguments:** use it directly.
|
|
141
|
+
|
|
142
|
+
**If not provided:** Ask: "Which Slack channel?"
|
|
143
|
+
|
|
144
|
+
### S2. Resolve Source Content
|
|
145
|
+
|
|
146
|
+
**If file path provided in arguments:** read it.
|
|
147
|
+
|
|
148
|
+
**If not provided:**
|
|
149
|
+
1. Check conversation context for an active file (brain doc, codex doc, or any file being discussed)
|
|
150
|
+
2. If none, ask: "What content do you want to share?"
|
|
151
|
+
|
|
152
|
+
Read the file to get the full content.
|
|
153
|
+
|
|
154
|
+
### S3. Resolve Instructions
|
|
155
|
+
|
|
156
|
+
**If instructions provided after `--`:** use them.
|
|
157
|
+
|
|
158
|
+
**If not provided:** Ask: "How should I format this for Slack? (e.g. 'summarise the action items', 'TLDR with key decisions', 'list the open questions')"
|
|
159
|
+
|
|
160
|
+
### S4. Build Source Link
|
|
161
|
+
|
|
162
|
+
Check if the source content has a linkable reference:
|
|
163
|
+
|
|
164
|
+
| Source | Link format |
|
|
165
|
+
|--------|------------|
|
|
166
|
+
| File with `confluence_url` in frontmatter | Confluence page link |
|
|
167
|
+
| File in codex repo | Relative path reference |
|
|
168
|
+
| Brain doc with Obsidian path | Obsidian URI (for the user's own reference, not posted) |
|
|
169
|
+
| Other | File path (not posted — no useful link) |
|
|
170
|
+
|
|
171
|
+
If a meaningful URL exists (Confluence, GitHub), include it in the message.
|
|
172
|
+
|
|
173
|
+
### S5. Generate Message
|
|
174
|
+
|
|
175
|
+
Process the source content according to the user's instructions. The output should be a Slack-formatted message:
|
|
176
|
+
|
|
177
|
+
- Use Slack mrkdwn syntax (`*bold*`, `_italic_`, `• ` for bullets)
|
|
178
|
+
- Keep it concise — Slack messages should scan quickly
|
|
179
|
+
- If a source link is available, append it at the end: `:link: <{url}|View full document>`
|
|
180
|
+
- Do NOT dump the entire file — the point is to summarise/extract per the instructions
|
|
181
|
+
|
|
182
|
+
**Show the draft message to the user** and ask to confirm before posting.
|
|
183
|
+
|
|
184
|
+
### S6. Post to Slack
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
node -e 'process.stdout.write(JSON.stringify({channel:"{channel}",text:`{message}`,unfurl_links:false}))' | \
|
|
188
|
+
droid integrations slack post
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Check response for `"ok": true`.
|
|
192
|
+
|
|
193
|
+
### S7. Confirm
|
|
194
|
+
|
|
195
|
+
Tell the user: "Shared to #{channel}"
|
|
196
|
+
|
|
197
|
+
If there was a source link: "Included link to {source}"
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Error Handling
|
|
202
|
+
|
|
203
|
+
| Error | Action |
|
|
204
|
+
|-------|--------|
|
|
205
|
+
| Atlassian MCP not available | Suggest `/mcp` to connect Atlassian |
|
|
206
|
+
| No Confluence spaces returned | Check permissions — user may need Confluence access |
|
|
207
|
+
| Confluence create/update fails | Show error message from API response |
|
|
208
|
+
| No SLACK_USER_TOKEN | Suggest `droid integrations setup slack` |
|
|
209
|
+
| Slack post fails | Show error, offer to copy message to clipboard |
|
|
210
|
+
| File not found | Ask user to provide a valid path |
|
|
211
|
+
| No frontmatter to update | Create frontmatter block at top of file |
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@ import chalk from 'chalk';
|
|
|
3
3
|
import { execSync } from 'child_process';
|
|
4
4
|
import { type IncomingMessage, type ServerResponse } from 'http';
|
|
5
5
|
import { createServer } from 'https';
|
|
6
|
-
import { readFileSync } from 'fs';
|
|
6
|
+
import { readFileSync, mkdirSync, appendFileSync, writeFileSync } from 'fs';
|
|
7
7
|
import { join } from 'path';
|
|
8
8
|
import { hasSlackToken, exchangeOAuthCode, postMessage, editCanvas } from '../integrations/slack';
|
|
9
9
|
import { setConfigValue, getConfigValue } from '../lib/config';
|
|
@@ -85,7 +85,6 @@ function getOrCreateCert(): { key: string; cert: string } {
|
|
|
85
85
|
} catch { /* no cached cert, generate one */ }
|
|
86
86
|
|
|
87
87
|
// Generate new cert (valid for 365 days)
|
|
88
|
-
const { mkdirSync } = require('fs');
|
|
89
88
|
mkdirSync(CERT_DIR, { recursive: true });
|
|
90
89
|
execSync(
|
|
91
90
|
`openssl req -x509 -newkey rsa:2048 -keyout "${CERT_KEY_PATH}" -out "${CERT_PATH}" -days 365 -nodes -subj "/CN=localhost" -addext "subjectAltName=DNS:localhost,IP:127.0.0.1" 2>/dev/null`,
|
|
@@ -178,8 +177,7 @@ function writeTokenToShellRc(key: string, value: string): boolean {
|
|
|
178
177
|
const fullPath = expandHome(rcPath);
|
|
179
178
|
|
|
180
179
|
try {
|
|
181
|
-
const
|
|
182
|
-
const existing = readFs(fullPath, 'utf-8');
|
|
180
|
+
const existing = readFileSync(fullPath, 'utf-8');
|
|
183
181
|
|
|
184
182
|
// Replace existing export if present, otherwise append
|
|
185
183
|
if (existing.includes(`${key}=`) || existing.includes(`${key} "`)) {
|
|
@@ -198,7 +196,6 @@ function writeTokenToShellRc(key: string, value: string): boolean {
|
|
|
198
196
|
});
|
|
199
197
|
|
|
200
198
|
if (replaced) {
|
|
201
|
-
const { writeFileSync } = require('fs');
|
|
202
199
|
writeFileSync(fullPath, updated.join('\n'), 'utf-8');
|
|
203
200
|
} else {
|
|
204
201
|
appendFileSync(fullPath, `\n${exportLine}\n`, 'utf-8');
|
|
@@ -387,6 +384,21 @@ export async function integrationsStatusCommand(): Promise<void> {
|
|
|
387
384
|
}
|
|
388
385
|
|
|
389
386
|
console.log('');
|
|
387
|
+
|
|
388
|
+
// Atlassian
|
|
389
|
+
console.log(chalk.bold(' Atlassian'));
|
|
390
|
+
|
|
391
|
+
const atlassianConfigured = getConfigValue('integrations.atlassian.configured');
|
|
392
|
+
|
|
393
|
+
if (atlassianConfigured) {
|
|
394
|
+
console.log(chalk.green(' MCP: Connected'));
|
|
395
|
+
console.log(chalk.green(' Status: configured'));
|
|
396
|
+
} else {
|
|
397
|
+
console.log(chalk.yellow(' MCP: Not yet verified'));
|
|
398
|
+
console.log(chalk.gray(' Use /share confluence to verify, or see setup guide'));
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
console.log('');
|
|
390
402
|
}
|
|
391
403
|
|
|
392
404
|
/**
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import { Box, Text } from 'ink';
|
|
2
2
|
import { colors } from '../constants';
|
|
3
3
|
|
|
4
|
+
export interface Integration {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
connected: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
4
10
|
export interface IntegrationsDetailsProps {
|
|
5
11
|
isFocused: boolean;
|
|
6
12
|
selectedAction: number;
|
|
13
|
+
integration: Integration;
|
|
7
14
|
}
|
|
8
15
|
|
|
9
|
-
|
|
10
|
-
isFocused,
|
|
11
|
-
selectedAction,
|
|
12
|
-
}: IntegrationsDetailsProps) {
|
|
16
|
+
function SlackDetails({ isFocused, selectedAction }: { isFocused: boolean; selectedAction: number }) {
|
|
13
17
|
const hasToken = !!process.env.SLACK_USER_TOKEN;
|
|
14
18
|
const hasClientId = !!process.env.SLACK_CLIENT_ID;
|
|
15
19
|
const hasClientSecret = !!process.env.SLACK_CLIENT_SECRET;
|
|
@@ -93,3 +97,66 @@ export function IntegrationsDetails({
|
|
|
93
97
|
</Box>
|
|
94
98
|
);
|
|
95
99
|
}
|
|
100
|
+
|
|
101
|
+
function AtlassianDetails({ isFocused, selectedAction, connected }: { isFocused: boolean; selectedAction: number; connected: boolean }) {
|
|
102
|
+
return (
|
|
103
|
+
<Box flexDirection="column" paddingLeft={2} flexGrow={1}>
|
|
104
|
+
<Text color={colors.text} bold>Atlassian</Text>
|
|
105
|
+
|
|
106
|
+
<Box flexDirection="column" marginTop={1}>
|
|
107
|
+
<Text color={colors.textDim} bold>MCP Server</Text>
|
|
108
|
+
<Text>
|
|
109
|
+
<Text color={colors.textDim}> Status </Text>
|
|
110
|
+
{connected
|
|
111
|
+
? <Text color={colors.success}>Connected ✓</Text>
|
|
112
|
+
: <Text color="#fbbf24">Not yet verified</Text>}
|
|
113
|
+
</Text>
|
|
114
|
+
</Box>
|
|
115
|
+
|
|
116
|
+
<Box flexDirection="column" marginTop={1}>
|
|
117
|
+
<Text color={colors.textDim}>
|
|
118
|
+
{connected
|
|
119
|
+
? 'Confluence and Jira available via MCP'
|
|
120
|
+
: 'Use /share confluence to verify connection'}
|
|
121
|
+
</Text>
|
|
122
|
+
</Box>
|
|
123
|
+
|
|
124
|
+
{isFocused && (
|
|
125
|
+
<Box marginTop={2} flexDirection="row" gap={2}>
|
|
126
|
+
<Text
|
|
127
|
+
backgroundColor={selectedAction === 0 ? colors.primary : undefined}
|
|
128
|
+
color={selectedAction === 0 ? '#ffffff' : colors.textDim}
|
|
129
|
+
bold={selectedAction === 0}
|
|
130
|
+
>
|
|
131
|
+
{' '}Setup Guide{' '}
|
|
132
|
+
</Text>
|
|
133
|
+
</Box>
|
|
134
|
+
)}
|
|
135
|
+
|
|
136
|
+
{isFocused && (
|
|
137
|
+
<Box marginTop={1}>
|
|
138
|
+
<Text color={colors.textDim}>enter confirm {'·'} esc back</Text>
|
|
139
|
+
</Box>
|
|
140
|
+
)}
|
|
141
|
+
|
|
142
|
+
{!isFocused && (
|
|
143
|
+
<Box marginTop={2}>
|
|
144
|
+
<Text color={colors.textDim}>press enter for options</Text>
|
|
145
|
+
</Box>
|
|
146
|
+
)}
|
|
147
|
+
</Box>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function IntegrationsDetails({
|
|
152
|
+
isFocused,
|
|
153
|
+
selectedAction,
|
|
154
|
+
integration,
|
|
155
|
+
}: IntegrationsDetailsProps) {
|
|
156
|
+
if (integration.id === 'atlassian') {
|
|
157
|
+
return <AtlassianDetails isFocused={isFocused} selectedAction={selectedAction} connected={integration.connected} />;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Default: Slack
|
|
161
|
+
return <SlackDetails isFocused={isFocused} selectedAction={selectedAction} />;
|
|
162
|
+
}
|
package/src/commands/tui.tsx
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
isToolInstalled,
|
|
16
16
|
getToolUpdateStatus,
|
|
17
17
|
} from '../lib/tools';
|
|
18
|
-
import { configExists, loadConfig, getAutoUpdateConfig } from '../lib/config';
|
|
18
|
+
import { configExists, loadConfig, getAutoUpdateConfig, getConfigValue } from '../lib/config';
|
|
19
19
|
import { type ConfigOption, type ToolManifest } from '../lib/types';
|
|
20
20
|
import { detectAllPlatforms } from '../lib/platforms';
|
|
21
21
|
import { getVersion } from '../lib/version';
|
|
@@ -177,6 +177,12 @@ function App() {
|
|
|
177
177
|
// Keep skills for configure view (tools configure via their primary skill)
|
|
178
178
|
const skills = getBundledSkills();
|
|
179
179
|
|
|
180
|
+
// Dynamic integrations list
|
|
181
|
+
const integrations = [
|
|
182
|
+
{ id: 'slack', name: 'Slack', connected: !!process.env.SLACK_USER_TOKEN },
|
|
183
|
+
{ id: 'atlassian', name: 'Atlassian', connected: !!getConfigValue('integrations.atlassian.configured') },
|
|
184
|
+
];
|
|
185
|
+
|
|
180
186
|
useInput(
|
|
181
187
|
(input, key) => {
|
|
182
188
|
if (message) setMessage(null);
|
|
@@ -213,7 +219,7 @@ function App() {
|
|
|
213
219
|
setSelectedAction(0);
|
|
214
220
|
}
|
|
215
221
|
if (key.downArrow) {
|
|
216
|
-
const maxIndex = activeTab === 'tools' ? tools.length - 1 : activeTab === 'integrations' ?
|
|
222
|
+
const maxIndex = activeTab === 'tools' ? tools.length - 1 : activeTab === 'integrations' ? integrations.length - 1 : 0;
|
|
217
223
|
setSelectedIndex((prev) => {
|
|
218
224
|
const newIndex = Math.min(maxIndex, prev + 1);
|
|
219
225
|
// Scroll down if needed
|
|
@@ -239,12 +245,20 @@ function App() {
|
|
|
239
245
|
setSelectedAction(0);
|
|
240
246
|
}
|
|
241
247
|
if (activeTab === 'integrations') {
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
+
const currentIntegration = integrations[selectedIndex];
|
|
249
|
+
let intActions: { id: string }[] = [];
|
|
250
|
+
|
|
251
|
+
if (currentIntegration?.id === 'slack') {
|
|
252
|
+
const hasClientCreds = !!process.env.SLACK_CLIENT_ID && !!process.env.SLACK_CLIENT_SECRET;
|
|
253
|
+
const canRunSetup = hasClientCreds && !process.env.SLACK_USER_TOKEN;
|
|
254
|
+
intActions = [
|
|
255
|
+
...(canRunSetup ? [{ id: 'setup' }] : []),
|
|
256
|
+
{ id: 'guide' },
|
|
257
|
+
];
|
|
258
|
+
} else if (currentIntegration?.id === 'atlassian') {
|
|
259
|
+
intActions = [{ id: 'guide' }];
|
|
260
|
+
}
|
|
261
|
+
|
|
248
262
|
const maxIntAction = intActions.length - 1;
|
|
249
263
|
if (key.leftArrow) {
|
|
250
264
|
setSelectedAction((prev) => Math.max(0, prev - 1));
|
|
@@ -254,14 +268,18 @@ function App() {
|
|
|
254
268
|
}
|
|
255
269
|
if (key.return) {
|
|
256
270
|
const actionId = intActions[selectedAction]?.id;
|
|
257
|
-
if (actionId === 'setup') {
|
|
271
|
+
if (actionId === 'setup' && currentIntegration?.id === 'slack') {
|
|
258
272
|
exitCommand = ['droid', 'integrations', 'setup', 'slack'];
|
|
259
273
|
exit();
|
|
260
274
|
} else if (actionId === 'guide') {
|
|
261
|
-
const
|
|
275
|
+
const integrationId = currentIntegration?.id ?? 'slack';
|
|
276
|
+
const guideTitle = currentIntegration?.id === 'atlassian'
|
|
277
|
+
? 'Atlassian Integration Setup'
|
|
278
|
+
: 'Slack Integration Setup';
|
|
279
|
+
const content = loadIntegrationReference(integrationId, 'setup.md');
|
|
262
280
|
if (content) {
|
|
263
281
|
setPreviousView('detail');
|
|
264
|
-
setReadmeContent({ title:
|
|
282
|
+
setReadmeContent({ title: guideTitle, content });
|
|
265
283
|
setView('readme');
|
|
266
284
|
} else {
|
|
267
285
|
setMessage({ text: 'Could not load setup guide', type: 'error' });
|
|
@@ -612,14 +630,16 @@ function App() {
|
|
|
612
630
|
)}
|
|
613
631
|
|
|
614
632
|
{activeTab === 'integrations' && (
|
|
615
|
-
<Box paddingX={1}>
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
633
|
+
<Box flexDirection="column" paddingX={1}>
|
|
634
|
+
{integrations.map((integration, index) => (
|
|
635
|
+
<Text key={integration.id}>
|
|
636
|
+
{selectedIndex === index ? <Text color={colors.primary}>{'>'}</Text> : <Text> </Text>}
|
|
637
|
+
<Text> {integration.name}</Text>
|
|
638
|
+
{integration.connected
|
|
639
|
+
? <Text color={colors.success}> ✓</Text>
|
|
640
|
+
: <Text color={colors.textDim}> ✗</Text>}
|
|
641
|
+
</Text>
|
|
642
|
+
))}
|
|
623
643
|
</Box>
|
|
624
644
|
)}
|
|
625
645
|
|
|
@@ -658,10 +678,11 @@ function App() {
|
|
|
658
678
|
/>
|
|
659
679
|
)}
|
|
660
680
|
|
|
661
|
-
{activeTab === 'integrations' && (
|
|
681
|
+
{activeTab === 'integrations' && integrations[selectedIndex] && (
|
|
662
682
|
<IntegrationsDetails
|
|
663
683
|
isFocused={view === 'detail'}
|
|
664
684
|
selectedAction={selectedAction}
|
|
685
|
+
integration={integrations[selectedIndex]}
|
|
665
686
|
/>
|
|
666
687
|
)}
|
|
667
688
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Atlassian Integration Setup
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The Atlassian integration connects droid to Confluence (and Jira) via the official Atlassian MCP server. This enables the `/share` tool to publish markdown files directly to Confluence pages.
|
|
6
|
+
|
|
7
|
+
## Setup
|
|
8
|
+
|
|
9
|
+
### 1. Add the Atlassian MCP Server
|
|
10
|
+
|
|
11
|
+
In Claude Code, run:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
/mcp
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Select **Atlassian** from the available MCP servers and follow the authentication prompts. This connects your Atlassian account (Confluence + Jira) to Claude Code.
|
|
18
|
+
|
|
19
|
+
### 2. Verify Connection
|
|
20
|
+
|
|
21
|
+
Run `/share confluence` in a Claude Code session. If the connection is working, you'll see a list of available Confluence spaces.
|
|
22
|
+
|
|
23
|
+
Alternatively, the TUI Integrations tab will show `Atlassian ✓` once the MCP has been used successfully.
|
|
24
|
+
|
|
25
|
+
## How It Works
|
|
26
|
+
|
|
27
|
+
Unlike Slack (which uses environment variables and OAuth), Atlassian uses Claude Code's built-in MCP server system:
|
|
28
|
+
|
|
29
|
+
| Aspect | Slack | Atlassian |
|
|
30
|
+
|--------|-------|-----------|
|
|
31
|
+
| Auth method | OAuth + env vars | MCP server (managed by Claude Code) |
|
|
32
|
+
| Setup | `droid integrations setup slack` | `/mcp` in Claude Code |
|
|
33
|
+
| API access | `@slack/web-api` SDK | MCP tool calls (`mcp__claude_ai_Atlassian__*`) |
|
|
34
|
+
| Config flag | `integrations.slack.configured` | `integrations.atlassian.configured` |
|
|
35
|
+
|
|
36
|
+
The `configured` flag is set automatically on first successful MCP call — no manual configuration needed.
|
|
37
|
+
|
|
38
|
+
## Troubleshooting
|
|
39
|
+
|
|
40
|
+
| Issue | Resolution |
|
|
41
|
+
|-------|------------|
|
|
42
|
+
| MCP not available | Run `/mcp` in Claude Code to add the Atlassian server |
|
|
43
|
+
| No Confluence spaces listed | Check your Atlassian account has Confluence access |
|
|
44
|
+
| Permission errors | Verify your account has edit permissions in the target space |
|
|
45
|
+
| "Not configured" in TUI | Use `/share confluence` once — the flag is set on first success |
|
|
46
|
+
|
|
47
|
+
## Usage
|
|
48
|
+
|
|
49
|
+
Once connected, use the `/share` command:
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
/share confluence # Interactive - prompts for file
|
|
53
|
+
/share confluence path/to/file.md # Share specific file
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The share skill handles:
|
|
57
|
+
- Choosing a Confluence space and parent page
|
|
58
|
+
- Creating new pages or updating existing ones
|
|
59
|
+
- Storing the page ID in the file's frontmatter for future updates
|
package/src/lib/types.ts
CHANGED
|
@@ -63,8 +63,13 @@ export interface IntegrationSlackConfig {
|
|
|
63
63
|
crosspost_channel?: string;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
export interface IntegrationAtlassianConfig {
|
|
67
|
+
configured?: boolean;
|
|
68
|
+
}
|
|
69
|
+
|
|
66
70
|
export interface IntegrationsConfig {
|
|
67
71
|
slack?: IntegrationSlackConfig;
|
|
72
|
+
atlassian?: IntegrationAtlassianConfig;
|
|
68
73
|
}
|
|
69
74
|
|
|
70
75
|
export interface DroidConfig {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "droid-share",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Share content to external platforms. Publish docs to Confluence or post summaries to Slack.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Orderful",
|
|
7
|
+
"url": "https://github.com/orderful"
|
|
8
|
+
},
|
|
9
|
+
"repository": "https://github.com/orderful/droid",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"keywords": [
|
|
12
|
+
"droid",
|
|
13
|
+
"ai",
|
|
14
|
+
"share"
|
|
15
|
+
],
|
|
16
|
+
"skills": [
|
|
17
|
+
"./skills/share/SKILL.md"
|
|
18
|
+
],
|
|
19
|
+
"commands": [
|
|
20
|
+
"./commands/share.md"
|
|
21
|
+
]
|
|
22
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
name: share
|
|
2
|
+
description: "Share content to external platforms. Publish docs to Confluence or post summaries to Slack."
|
|
3
|
+
version: 0.1.0
|
|
4
|
+
status: beta
|
|
5
|
+
|
|
6
|
+
includes:
|
|
7
|
+
skills:
|
|
8
|
+
- name: share
|
|
9
|
+
required: true
|
|
10
|
+
commands:
|
|
11
|
+
- name: share
|
|
12
|
+
is_alias: false
|
|
13
|
+
agents: []
|
|
14
|
+
|
|
15
|
+
dependencies: []
|
|
16
|
+
|
|
17
|
+
config_schema: {}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: share
|
|
3
|
+
description: "Share content to external platforms. Publish docs to Confluence or post summaries to Slack with free-form instructions."
|
|
4
|
+
argument-hint: "[confluence|slack] [{file|#channel}] [-- {instructions}]"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# /share
|
|
8
|
+
|
|
9
|
+
**User invoked:** `/share $ARGUMENTS`
|
|
10
|
+
|
|
11
|
+
**Your task:** Invoke the **share skill** with these arguments.
|
|
12
|
+
|
|
13
|
+
## Examples
|
|
14
|
+
|
|
15
|
+
- `/share` → Interactive mode, prompts for platform and file
|
|
16
|
+
- `/share confluence` → Publish to Confluence, prompts for file
|
|
17
|
+
- `/share confluence path/to/file.md` → Publish specific file to Confluence
|
|
18
|
+
- `/share slack #eng` → Share to Slack channel, prompts for content
|
|
19
|
+
- `/share slack #eng -- summarise the action items from this meeting`
|
|
20
|
+
- `/share slack #eng-design -- TLDR of this research doc with a link`
|
|
21
|
+
|
|
22
|
+
## Quick Reference
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
/share # Interactive
|
|
26
|
+
/share confluence # Publish doc to Confluence
|
|
27
|
+
/share confluence {file} # Publish specific file
|
|
28
|
+
/share slack #channel # Share to Slack (prompts for content)
|
|
29
|
+
/share slack #channel -- {instructions} # Share with specific instructions
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
See the **share skill** for complete documentation.
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: share
|
|
3
|
+
description: "Share content to external platforms. Publish docs to Confluence or post summaries to Slack. User prompts like 'share this to confluence', 'share on slack', 'post a summary to #eng'."
|
|
4
|
+
argument-hint: "[confluence|slack] [{file|#channel}] [-- {instructions}]"
|
|
5
|
+
allowed-tools:
|
|
6
|
+
[
|
|
7
|
+
Read,
|
|
8
|
+
Edit,
|
|
9
|
+
Glob,
|
|
10
|
+
Grep,
|
|
11
|
+
Bash(droid:*),
|
|
12
|
+
]
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Share Skill
|
|
16
|
+
|
|
17
|
+
Share content to external platforms. Two modes:
|
|
18
|
+
|
|
19
|
+
- **Confluence** — publish a full markdown file as a Confluence page (create or update)
|
|
20
|
+
- **Slack** — process content with free-form instructions and post the result to a channel
|
|
21
|
+
|
|
22
|
+
## Supported Platforms
|
|
23
|
+
|
|
24
|
+
| Platform | Backend | Mode |
|
|
25
|
+
|----------|---------|------|
|
|
26
|
+
| Confluence | Atlassian MCP (`mcp__claude_ai_Atlassian__*`) | Publish document as page |
|
|
27
|
+
| Slack | `droid integrations slack post` | Process + post message |
|
|
28
|
+
|
|
29
|
+
## Procedure
|
|
30
|
+
|
|
31
|
+
### 1. Resolve Platform
|
|
32
|
+
|
|
33
|
+
**If platform provided in arguments** (e.g. `confluence`, `slack`): use it directly.
|
|
34
|
+
|
|
35
|
+
**If not provided:** Ask the user: "Where do you want to share? (Confluence / Slack)"
|
|
36
|
+
|
|
37
|
+
Then branch to the appropriate flow below.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Confluence Flow
|
|
42
|
+
|
|
43
|
+
### C1. Resolve File
|
|
44
|
+
|
|
45
|
+
**If file path provided in arguments:** use it directly.
|
|
46
|
+
|
|
47
|
+
**If not provided:**
|
|
48
|
+
1. Check conversation context for an active file (e.g. brain doc being worked on)
|
|
49
|
+
2. If none, ask: "Which file would you like to publish?"
|
|
50
|
+
|
|
51
|
+
Read the file to confirm it exists.
|
|
52
|
+
|
|
53
|
+
### C2. Parse File
|
|
54
|
+
|
|
55
|
+
1. **Extract title:** Check YAML frontmatter for `title` field. If none, use the first `# heading`. If neither, use the filename without extension.
|
|
56
|
+
2. **Strip YAML frontmatter** from the content that will be sent (the `---` delimited block at the top). Keep the raw markdown body.
|
|
57
|
+
3. **Check for existing page ID:** Look for `confluence_page_id` in frontmatter — if present, this is an update rather than a create.
|
|
58
|
+
|
|
59
|
+
### C3. Verify Atlassian MCP Connection
|
|
60
|
+
|
|
61
|
+
Use `ToolSearch` to load `mcp__claude_ai_Atlassian__getAccessibleAtlassianResources`, then call it.
|
|
62
|
+
|
|
63
|
+
**If MCP is unavailable or errors:**
|
|
64
|
+
Tell the user: "Atlassian MCP is not connected. Run `/mcp` in Claude Code to add the Atlassian integration, then try again."
|
|
65
|
+
Stop here.
|
|
66
|
+
|
|
67
|
+
**If successful:**
|
|
68
|
+
- Extract the `id` (cloud ID) from the first accessible resource
|
|
69
|
+
- Mark integration as configured:
|
|
70
|
+
```bash
|
|
71
|
+
droid config --set "integrations.atlassian.configured=true"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### C4. Resolve Confluence Space
|
|
75
|
+
|
|
76
|
+
Use `ToolSearch` to load `mcp__claude_ai_Atlassian__getConfluenceSpaces`, then call it with the cloud ID.
|
|
77
|
+
|
|
78
|
+
Present the list of spaces to the user and ask them to pick one.
|
|
79
|
+
|
|
80
|
+
### C5. Resolve Parent Page (Optional)
|
|
81
|
+
|
|
82
|
+
Ask the user: "Place this page under a specific parent, or at the space root?"
|
|
83
|
+
|
|
84
|
+
If they want a parent:
|
|
85
|
+
- Use `mcp__claude_ai_Atlassian__getPagesInConfluenceSpace` to list top-level pages
|
|
86
|
+
- Let the user pick a parent page
|
|
87
|
+
- If they need to go deeper, use `mcp__claude_ai_Atlassian__getConfluencePageDescendants` to navigate
|
|
88
|
+
|
|
89
|
+
If space root: no parent ID needed.
|
|
90
|
+
|
|
91
|
+
### C6. Confirm Title
|
|
92
|
+
|
|
93
|
+
Pre-fill from the extracted title (step C2). Ask the user to confirm or edit.
|
|
94
|
+
|
|
95
|
+
### C7. Create or Update Page
|
|
96
|
+
|
|
97
|
+
#### Update (frontmatter has `confluence_page_id`)
|
|
98
|
+
|
|
99
|
+
1. **Check for comments:** Use `mcp__claude_ai_Atlassian__getConfluencePageInlineComments` and `mcp__claude_ai_Atlassian__getConfluencePageFooterComments` to check for comments on the existing page.
|
|
100
|
+
- If comments exist, warn: "This page has N comments that may reference content being replaced. Continue?"
|
|
101
|
+
- If user declines, stop.
|
|
102
|
+
|
|
103
|
+
2. **Update:** Use `mcp__claude_ai_Atlassian__updateConfluencePage` with:
|
|
104
|
+
- `id`: the `confluence_page_id` from frontmatter
|
|
105
|
+
- `title`: confirmed title
|
|
106
|
+
- `body`: markdown content (frontmatter stripped)
|
|
107
|
+
- `contentFormat`: `"markdown"`
|
|
108
|
+
|
|
109
|
+
#### Create (no existing page ID)
|
|
110
|
+
|
|
111
|
+
Use `mcp__claude_ai_Atlassian__createConfluencePage` with:
|
|
112
|
+
- `spaceId`: chosen space ID
|
|
113
|
+
- `title`: confirmed title
|
|
114
|
+
- `body`: markdown content (frontmatter stripped)
|
|
115
|
+
- `contentFormat`: `"markdown"`
|
|
116
|
+
- `parentId`: parent page ID (if chosen, otherwise omit)
|
|
117
|
+
|
|
118
|
+
### C8. Update Frontmatter
|
|
119
|
+
|
|
120
|
+
After successful create or update, use `Edit` to add/update frontmatter fields in the source file:
|
|
121
|
+
|
|
122
|
+
- `confluence_page_id`: the page ID from the response
|
|
123
|
+
- `confluence_url`: the page URL (construct from cloud URL + page `_links.webui` or similar)
|
|
124
|
+
- `confluence_last_shared`: ISO 8601 timestamp of when this was shared
|
|
125
|
+
|
|
126
|
+
If the file has no frontmatter, add a `---` delimited block at the top.
|
|
127
|
+
|
|
128
|
+
### C9. Return Result
|
|
129
|
+
|
|
130
|
+
Tell the user:
|
|
131
|
+
- "Published to Confluence: {url}"
|
|
132
|
+
- Or "Updated Confluence page: {url}"
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Slack Flow
|
|
137
|
+
|
|
138
|
+
### S1. Resolve Channel
|
|
139
|
+
|
|
140
|
+
**If `#channel` provided in arguments:** use it directly.
|
|
141
|
+
|
|
142
|
+
**If not provided:** Ask: "Which Slack channel?"
|
|
143
|
+
|
|
144
|
+
### S2. Resolve Source Content
|
|
145
|
+
|
|
146
|
+
**If file path provided in arguments:** read it.
|
|
147
|
+
|
|
148
|
+
**If not provided:**
|
|
149
|
+
1. Check conversation context for an active file (brain doc, codex doc, or any file being discussed)
|
|
150
|
+
2. If none, ask: "What content do you want to share?"
|
|
151
|
+
|
|
152
|
+
Read the file to get the full content.
|
|
153
|
+
|
|
154
|
+
### S3. Resolve Instructions
|
|
155
|
+
|
|
156
|
+
**If instructions provided after `--`:** use them.
|
|
157
|
+
|
|
158
|
+
**If not provided:** Ask: "How should I format this for Slack? (e.g. 'summarise the action items', 'TLDR with key decisions', 'list the open questions')"
|
|
159
|
+
|
|
160
|
+
### S4. Build Source Link
|
|
161
|
+
|
|
162
|
+
Check if the source content has a linkable reference:
|
|
163
|
+
|
|
164
|
+
| Source | Link format |
|
|
165
|
+
|--------|------------|
|
|
166
|
+
| File with `confluence_url` in frontmatter | Confluence page link |
|
|
167
|
+
| File in codex repo | Relative path reference |
|
|
168
|
+
| Brain doc with Obsidian path | Obsidian URI (for the user's own reference, not posted) |
|
|
169
|
+
| Other | File path (not posted — no useful link) |
|
|
170
|
+
|
|
171
|
+
If a meaningful URL exists (Confluence, GitHub), include it in the message.
|
|
172
|
+
|
|
173
|
+
### S5. Generate Message
|
|
174
|
+
|
|
175
|
+
Process the source content according to the user's instructions. The output should be a Slack-formatted message:
|
|
176
|
+
|
|
177
|
+
- Use Slack mrkdwn syntax (`*bold*`, `_italic_`, `• ` for bullets)
|
|
178
|
+
- Keep it concise — Slack messages should scan quickly
|
|
179
|
+
- If a source link is available, append it at the end: `:link: <{url}|View full document>`
|
|
180
|
+
- Do NOT dump the entire file — the point is to summarise/extract per the instructions
|
|
181
|
+
|
|
182
|
+
**Show the draft message to the user** and ask to confirm before posting.
|
|
183
|
+
|
|
184
|
+
### S6. Post to Slack
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
node -e 'process.stdout.write(JSON.stringify({channel:"{channel}",text:`{message}`,unfurl_links:false}))' | \
|
|
188
|
+
droid integrations slack post
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Check response for `"ok": true`.
|
|
192
|
+
|
|
193
|
+
### S7. Confirm
|
|
194
|
+
|
|
195
|
+
Tell the user: "Shared to #{channel}"
|
|
196
|
+
|
|
197
|
+
If there was a source link: "Included link to {source}"
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Error Handling
|
|
202
|
+
|
|
203
|
+
| Error | Action |
|
|
204
|
+
|-------|--------|
|
|
205
|
+
| Atlassian MCP not available | Suggest `/mcp` to connect Atlassian |
|
|
206
|
+
| No Confluence spaces returned | Check permissions — user may need Confluence access |
|
|
207
|
+
| Confluence create/update fails | Show error message from API response |
|
|
208
|
+
| No SLACK_USER_TOKEN | Suggest `droid integrations setup slack` |
|
|
209
|
+
| Slack post fails | Show error, offer to copy message to clipboard |
|
|
210
|
+
| File not found | Ask user to provide a valid path |
|
|
211
|
+
| No frontmatter to update | Create frontmatter block at top of file |
|