@orderful/droid 0.40.1 → 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.
@@ -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,11 @@
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
+
3
9
  ## 0.40.1
4
10
 
5
11
  ### Patch Changes
package/dist/bin/droid.js CHANGED
@@ -2821,10 +2821,7 @@ function SettingsDetails({
2821
2821
  // src/commands/tui/components/IntegrationsDetails.tsx
2822
2822
  import { Box as Box6, Text as Text6 } from "ink";
2823
2823
  import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
2824
- function IntegrationsDetails({
2825
- isFocused,
2826
- selectedAction
2827
- }) {
2824
+ function SlackDetails({ isFocused, selectedAction }) {
2828
2825
  const hasToken = !!process.env.SLACK_USER_TOKEN;
2829
2826
  const hasClientId = !!process.env.SLACK_CLIENT_ID;
2830
2827
  const hasClientSecret = !!process.env.SLACK_CLIENT_SECRET;
@@ -2886,6 +2883,48 @@ function IntegrationsDetails({
2886
2883
  !isFocused && /* @__PURE__ */ jsx6(Box6, { marginTop: 2, children: /* @__PURE__ */ jsx6(Text6, { color: colors.textDim, children: "press enter for options" }) })
2887
2884
  ] });
2888
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
+ }
2889
2928
 
2890
2929
  // src/commands/tui/components/PlatformBadges.tsx
2891
2930
  import { Box as Box7, Text as Text7 } from "ink";
@@ -4342,6 +4381,10 @@ function App() {
4342
4381
  };
4343
4382
  const tools = getBundledTools();
4344
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
+ ];
4345
4388
  useInput9(
4346
4389
  (input, key) => {
4347
4390
  if (message) setMessage(null);
@@ -4375,7 +4418,7 @@ function App() {
4375
4418
  setSelectedAction(0);
4376
4419
  }
4377
4420
  if (key.downArrow) {
4378
- const maxIndex = activeTab === "tools" ? tools.length - 1 : activeTab === "integrations" ? 0 : 0;
4421
+ const maxIndex = activeTab === "tools" ? tools.length - 1 : activeTab === "integrations" ? integrations2.length - 1 : 0;
4379
4422
  setSelectedIndex((prev) => {
4380
4423
  const newIndex = Math.min(maxIndex, prev + 1);
4381
4424
  if (newIndex >= scrollOffset + MAX_VISIBLE_ITEMS) {
@@ -4400,12 +4443,18 @@ function App() {
4400
4443
  setSelectedAction(0);
4401
4444
  }
4402
4445
  if (activeTab === "integrations") {
4403
- const hasClientCreds = !!process.env.SLACK_CLIENT_ID && !!process.env.SLACK_CLIENT_SECRET;
4404
- const canRunSetup = hasClientCreds && !process.env.SLACK_USER_TOKEN;
4405
- const intActions = [
4406
- ...canRunSetup ? [{ id: "setup" }] : [],
4407
- { id: "guide" }
4408
- ];
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
+ }
4409
4458
  const maxIntAction = intActions.length - 1;
4410
4459
  if (key.leftArrow) {
4411
4460
  setSelectedAction((prev) => Math.max(0, prev - 1));
@@ -4415,14 +4464,16 @@ function App() {
4415
4464
  }
4416
4465
  if (key.return) {
4417
4466
  const actionId = intActions[selectedAction]?.id;
4418
- if (actionId === "setup") {
4467
+ if (actionId === "setup" && currentIntegration?.id === "slack") {
4419
4468
  exitCommand = ["droid", "integrations", "setup", "slack"];
4420
4469
  exit();
4421
4470
  } else if (actionId === "guide") {
4422
- const content = loadIntegrationReference("slack", "setup.md");
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");
4423
4474
  if (content) {
4424
4475
  setPreviousView("detail");
4425
- setReadmeContent({ title: "Slack Integration Setup", content });
4476
+ setReadmeContent({ title: guideTitle, content });
4426
4477
  setView("readme");
4427
4478
  } else {
4428
4479
  setMessage({ text: "Could not load setup guide", type: "error" });
@@ -4702,11 +4753,14 @@ function App() {
4702
4753
  " tools total"
4703
4754
  ] }) })
4704
4755
  ] }),
4705
- activeTab === "integrations" && /* @__PURE__ */ jsx17(Box16, { paddingX: 1, children: /* @__PURE__ */ jsxs16(Text17, { children: [
4706
- selectedIndex === 0 ? /* @__PURE__ */ jsx17(Text17, { color: colors.primary, children: ">" }) : /* @__PURE__ */ jsx17(Text17, { children: " " }),
4707
- /* @__PURE__ */ jsx17(Text17, { children: " Slack" }),
4708
- process.env.SLACK_USER_TOKEN ? /* @__PURE__ */ jsx17(Text17, { color: colors.success, children: " \u2713" }) : /* @__PURE__ */ jsx17(Text17, { color: colors.textDim, children: " \u2717" })
4709
- ] }) }),
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)) }),
4710
4764
  activeTab === "settings" && /* @__PURE__ */ jsx17(Box16, { paddingX: 1, children: /* @__PURE__ */ jsx17(Text17, { color: colors.textDim, children: "View and edit config" }) })
4711
4765
  ] }),
4712
4766
  message && /* @__PURE__ */ jsx17(Box16, { paddingX: 1, marginTop: 1, children: /* @__PURE__ */ jsx17(
@@ -4728,11 +4782,12 @@ function App() {
4728
4782
  selectedAction
4729
4783
  }
4730
4784
  ),
4731
- activeTab === "integrations" && /* @__PURE__ */ jsx17(
4785
+ activeTab === "integrations" && integrations2[selectedIndex] && /* @__PURE__ */ jsx17(
4732
4786
  IntegrationsDetails,
4733
4787
  {
4734
4788
  isFocused: view === "detail",
4735
- selectedAction
4789
+ selectedAction,
4790
+ integration: integrations2[selectedIndex]
4736
4791
  }
4737
4792
  ),
4738
4793
  activeTab === "settings" && /* @__PURE__ */ jsx17(
@@ -5341,6 +5396,16 @@ async function integrationsStatusCommand() {
5341
5396
  console.log(chalk11.yellow(" Status: not configured"));
5342
5397
  }
5343
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("");
5344
5409
  }
5345
5410
  async function slackPostCommand(options) {
5346
5411
  let input;
@@ -1 +1 @@
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,CAqC/D;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAsCnF"}
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;CACxB;AAED,wBAAgB,mBAAmB,CAAC,EAClC,SAAS,EACT,cAAc,GACf,EAAE,wBAAwB,2CAmF1B"}
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":"AAuqBA,wBAAsB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAyBhD"}
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
@@ -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;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/lib/types.ts"],"names":[],"mappings":"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"}
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orderful/droid",
3
- "version": "0.40.1",
3
+ "version": "0.41.0",
4
4
  "description": "AI workflow toolkit for sharing skills, commands, and agents across the team",
5
5
  "type": "module",
6
6
  "bin": {
@@ -384,6 +384,21 @@ export async function integrationsStatusCommand(): Promise<void> {
384
384
  }
385
385
 
386
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('');
387
402
  }
388
403
 
389
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
- export function IntegrationsDetails({
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
+ }
@@ -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' ? 0 : 0;
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 hasClientCreds = !!process.env.SLACK_CLIENT_ID && !!process.env.SLACK_CLIENT_SECRET;
243
- const canRunSetup = hasClientCreds && !process.env.SLACK_USER_TOKEN;
244
- const intActions = [
245
- ...(canRunSetup ? [{ id: 'setup' }] : []),
246
- { id: 'guide' },
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 content = loadIntegrationReference('slack', 'setup.md');
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: 'Slack Integration Setup', content });
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
- <Text>
617
- {selectedIndex === 0 ? <Text color={colors.primary}>{'>'}</Text> : <Text> </Text>}
618
- <Text> Slack</Text>
619
- {process.env.SLACK_USER_TOKEN
620
- ? <Text color={colors.success}> ✓</Text>
621
- : <Text color={colors.textDim}> ✗</Text>}
622
- </Text>
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 |