@orderful/droid 0.38.0 → 0.39.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.
@@ -0,0 +1,203 @@
1
+ # Formatting Reference
2
+
3
+ Detailed formatting rules for status updates.
4
+
5
+ ## Status Emoji
6
+
7
+ | Status | Emoji | Display Text |
8
+ |--------|-------|--------------|
9
+ | `on_track` | `:large_green_circle:` | On track |
10
+ | `at_risk` | `:large_yellow_circle:` | At risk |
11
+ | `blocked` | `:red_circle:` | Blocked |
12
+
13
+ ## Phase Checklist
14
+
15
+ ### Phases in Order
16
+
17
+ 1. `discovery` - Research and requirements gathering
18
+ 2. `post_discovery_feedback` - Incorporating discovery feedback
19
+ 3. `design` - Prototype and design work
20
+ 4. `post_design_feedback` - Incorporating design feedback
21
+ 5. `prd` - Writing PRD
22
+ 6. `tech_design` - Writing Tech Design
23
+ 7. `development` - Active implementation
24
+ 8. `qa` - QA & Testing
25
+ 9. `release` - Released to production
26
+
27
+ ### Phase Emoji
28
+
29
+ | State | Emoji |
30
+ |-------|-------|
31
+ | completed | `:check:` |
32
+ | current | `:work-in-progress-sign:` |
33
+ | upcoming | `:waiting-to-start:` |
34
+
35
+ ### Collapsed Groups
36
+
37
+ When all phases in a group are completed or upcoming (not current), collapse them:
38
+
39
+ | Group | Phases | Collapsed Name |
40
+ |-------|--------|----------------|
41
+ | 1 | discovery, post_discovery_feedback | "Discovery & Post-Discovery Feedback" |
42
+ | 2 | design, post_design_feedback | "Prototype, Design & Post-Design Feedback" |
43
+ | 3 | prd, tech_design | "PRD & Tech Design" |
44
+ | 4 | development | "Development" |
45
+ | 5 | qa, release | "QA → Release" |
46
+
47
+ ### Display Rules
48
+
49
+ 1. **Completed phases** - Collapse into groups, show with `:check:`
50
+ 2. **Current phase** - Show on its own line with `:work-in-progress-sign:` and **bold**
51
+ 3. **Upcoming phases** - Collapse into chain with `→`, show with `:waiting-to-start:`
52
+
53
+ ### Examples
54
+
55
+ **Current = `development`:**
56
+ ```
57
+ *Where we're at*
58
+ :check: Discovery & Post-Discovery Feedback
59
+ :check: Prototype, Design & Post-Design Feedback
60
+ :check: PRD & Tech Design
61
+ :work-in-progress-sign: *Development*
62
+ :waiting-to-start: QA → Release
63
+ ```
64
+
65
+ **Current = `prd`:**
66
+ ```
67
+ *Where we're at*
68
+ :check: Discovery & Post-Discovery Feedback
69
+ :check: Prototype, Design & Post-Design Feedback
70
+ :work-in-progress-sign: *PRD*
71
+ :waiting-to-start: Tech Design → Development → QA → Release
72
+ ```
73
+
74
+ **Current = `post_discovery_feedback`:**
75
+ ```
76
+ *Where we're at*
77
+ :check: Discovery
78
+ :work-in-progress-sign: *Post-Discovery Feedback*
79
+ :waiting-to-start: Prototype, Design & Post-Design Feedback → PRD & Tech Design → Development → QA → Release
80
+ ```
81
+
82
+ **Current = `design`:**
83
+ ```
84
+ *Where we're at*
85
+ :check: Discovery & Post-Discovery Feedback
86
+ :work-in-progress-sign: *Prototype/Design*
87
+ :waiting-to-start: Post-Design Feedback → PRD & Tech Design → Development → QA → Release
88
+ ```
89
+
90
+ ## Links Footer
91
+
92
+ Only include links that are configured in project metadata:
93
+
94
+ ```
95
+ :link: <{prd_url}|PRD> · <{tech_design_url}|Tech Design> · <{figma_url}|Figma>
96
+ ```
97
+
98
+ Omit any link that isn't set. If no links configured, omit the entire footer.
99
+
100
+ ## Full Message Template
101
+
102
+ ```
103
+ *Status:* {status_emoji} *{status_text}*
104
+
105
+ *Where we're at*
106
+ {phase_checklist}
107
+
108
+ *In flight this week*
109
+ • {bullet 1}
110
+ • {bullet 2}
111
+
112
+ *Up next*
113
+ • {bullet 1}
114
+ • {bullet 2}
115
+
116
+ {links_footer}
117
+
118
+ Posted with help from :droid:{ & :codex: if used}
119
+ ```
120
+
121
+ ## Crosspost Template (Summary Version)
122
+
123
+ ```
124
+ *{project_title}*
125
+
126
+ {status_line}
127
+ {current_phase_only - e.g., ":work-in-progress-sign: *Development*"}
128
+
129
+ {in_flight_section}
130
+
131
+ :link: <{message_url}|View full update>
132
+ ```
133
+
134
+ ## Example Full Output
135
+
136
+ **Example 1: Development phase, on track**
137
+ ```
138
+ *Status:* :large_green_circle: *On track*
139
+
140
+ *Where we're at*
141
+ :check: Discovery & Post-Discovery Feedback
142
+ :check: Prototype, Design & Post-Design Feedback
143
+ :check: PRD & Tech Design
144
+ :work-in-progress-sign: *Development*
145
+ :waiting-to-start: QA → Release
146
+
147
+ *In flight this week*
148
+ • Implementing scenario template API
149
+ • Adding copy endpoint for templates
150
+
151
+ *Up next*
152
+ • Fan-out orchestration design
153
+ • Integration tests
154
+
155
+ :link: <url|PRD> · <url|Tech Design> · <url|Figma>
156
+
157
+ Posted with help from :droid: & :codex:
158
+ ```
159
+
160
+ **Example 2: PRD phase, at risk**
161
+ ```
162
+ *Status:* :large_yellow_circle: *At risk*
163
+
164
+ *Where we're at*
165
+ :check: Discovery & Post-Discovery Feedback
166
+ :check: Prototype, Design & Post-Design Feedback
167
+ :work-in-progress-sign: *PRD*
168
+ :waiting-to-start: Tech Design → Development → QA → Release
169
+
170
+ *In flight this week*
171
+ • Finalising requirements with stakeholders
172
+ • Waiting on legal review for data handling
173
+
174
+ *Up next*
175
+ • Tech design kickoff
176
+
177
+ :link: <url|PRD>
178
+
179
+ Posted with help from :droid: & :codex:
180
+ ```
181
+
182
+ **Example 3: No Slack, printed to terminal**
183
+ ```
184
+ Status: On track
185
+
186
+ Where we're at
187
+ ✓ Discovery & Post-Discovery Feedback
188
+ ✓ Prototype, Design & Post-Design Feedback
189
+ → Development
190
+ ○ QA → Release
191
+
192
+ In flight this week
193
+ • Building the feature
194
+ • Writing tests
195
+
196
+ Up next
197
+ • Code review
198
+ • QA handoff
199
+
200
+ ---
201
+ Tip: To post this to Slack, add slack.channel to your project's CONTEXT.md
202
+ and set SLACK_USER_TOKEN in your environment.
203
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orderful/droid",
3
- "version": "0.38.0",
3
+ "version": "0.39.0",
4
4
  "description": "AI workflow toolkit for sharing skills, commands, and agents across the team",
5
5
  "type": "module",
6
6
  "bin": {
package/src/bin/droid.ts CHANGED
@@ -16,6 +16,7 @@ import {
16
16
  reposRemoveCommand,
17
17
  reposGetCommand,
18
18
  } from '../commands/repos';
19
+ import { authSlackCommand, authStatusCommand } from '../commands/auth';
19
20
  import { getVersion } from '../lib/version';
20
21
  import { loadConfig, saveConfig, configExists } from '../lib/config';
21
22
  import { syncNewPlatforms } from '../lib/platforms';
@@ -109,6 +110,24 @@ program
109
110
  .allowUnknownOption()
110
111
  .action(execCommand);
111
112
 
113
+ // Auth command with subcommands
114
+ const auth = program
115
+ .command('auth')
116
+ .description('Set up authentication for external services');
117
+
118
+ auth
119
+ .command('slack')
120
+ .description('Set up Slack authentication for status updates')
121
+ .action(authSlackCommand);
122
+
123
+ auth
124
+ .command('status')
125
+ .description('Show authentication status')
126
+ .action(authStatusCommand);
127
+
128
+ // Default: show auth status if no subcommand
129
+ auth.action(authStatusCommand);
130
+
112
131
  // Sync new platforms before running any command
113
132
  // This ensures that newly installed platforms (e.g., Codex) get all existing tools
114
133
  if (configExists()) {
@@ -0,0 +1,150 @@
1
+ import inquirer from 'inquirer';
2
+ import chalk from 'chalk';
3
+ import { execSync } from 'child_process';
4
+ import { hasSlackToken } from '../lib/secrets';
5
+
6
+ // Slack OAuth configuration
7
+ const SLACK_SCOPES = 'chat:write,canvases:write';
8
+ const REDIRECT_URI = 'https://localhost:9876/callback';
9
+
10
+ /**
11
+ * Get the user's shell config file path
12
+ */
13
+ function getShellConfig(): string {
14
+ const shell = process.env.SHELL || '/bin/zsh';
15
+ if (shell.includes('zsh')) return '~/.zshrc';
16
+ if (shell.includes('bash')) return '~/.bashrc';
17
+ if (shell.includes('fish')) return '~/.config/fish/config.fish';
18
+ return '~/.profile';
19
+ }
20
+
21
+ export async function authSlackCommand(): Promise<void> {
22
+ console.log(chalk.bold('\n🔐 Slack Authentication Setup\n'));
23
+
24
+ // Check current status
25
+ if (hasSlackToken()) {
26
+ console.log(chalk.green('✓ SLACK_USER_TOKEN is already set in your environment\n'));
27
+ const { reauth } = await inquirer.prompt<{ reauth: boolean }>([
28
+ {
29
+ type: 'confirm',
30
+ name: 'reauth',
31
+ message: 'Re-authenticate anyway?',
32
+ default: false,
33
+ },
34
+ ]);
35
+
36
+ if (!reauth) {
37
+ return;
38
+ }
39
+ console.log('');
40
+ }
41
+
42
+ // Check for client credentials
43
+ const clientId = process.env.SLACK_CLIENT_ID;
44
+ const clientSecret = process.env.SLACK_CLIENT_SECRET;
45
+
46
+ if (!clientId || !clientSecret) {
47
+ const shellConfig = getShellConfig();
48
+ console.log(chalk.yellow('Missing Slack app credentials.\n'));
49
+ console.log(chalk.gray('Get Client ID and Client Secret from 1Password ("Droid Slack App")'));
50
+ console.log(chalk.gray(`and add to your ${shellConfig}:\n`));
51
+ console.log(chalk.white(' export SLACK_CLIENT_ID="your-client-id"'));
52
+ console.log(chalk.white(' export SLACK_CLIENT_SECRET="your-client-secret"\n'));
53
+ console.log(chalk.gray('Then reload and run this again:\n'));
54
+ console.log(chalk.white(` source ${shellConfig} && droid auth slack\n`));
55
+ return;
56
+ }
57
+
58
+ // Step 1: Direct user to authorize
59
+ const authorizeUrl = `https://slack.com/oauth/v2/authorize?client_id=${clientId}&user_scope=${SLACK_SCOPES}&redirect_uri=${encodeURIComponent(REDIRECT_URI)}`;
60
+
61
+ console.log(chalk.yellow('Step 1: Authorize in Slack\n'));
62
+ console.log(chalk.gray(' Go to the "Droid Setup" canvas in #rnd-updates and click "Install Droid Slack App"'));
63
+ console.log(chalk.gray(' Or open this URL:\n'));
64
+ console.log(chalk.cyan(` ${authorizeUrl}\n`));
65
+ console.log(chalk.gray(' After clicking Allow, you\'ll be redirected to a URL that won\'t load.'));
66
+ console.log(chalk.gray(' Copy the "code" parameter from the URL bar.\n'));
67
+
68
+ // Step 2: Get the code
69
+ const { code } = await inquirer.prompt<{ code: string }>([
70
+ {
71
+ type: 'input',
72
+ name: 'code',
73
+ message: 'Paste the code:',
74
+ validate: (input: string) => input.length > 0 || 'Code is required',
75
+ },
76
+ ]);
77
+
78
+ // Step 3: Exchange code for token
79
+ console.log(chalk.yellow('\nStep 2: Exchanging code for token...\n'));
80
+
81
+ try {
82
+ const response = execSync(
83
+ `curl -s -X POST https://slack.com/api/oauth.v2.access \
84
+ -d "client_id=${clientId}" \
85
+ -d "client_secret=${clientSecret}" \
86
+ -d "code=${code}" \
87
+ -d "redirect_uri=${REDIRECT_URI}"`,
88
+ { encoding: 'utf-8' }
89
+ );
90
+
91
+ const result = JSON.parse(response);
92
+
93
+ if (!result.ok) {
94
+ console.log(chalk.red(`✗ Slack API error: ${result.error}`));
95
+ if (result.error === 'invalid_code') {
96
+ console.log(chalk.gray(' The code may have expired. Try again with a fresh code.'));
97
+ }
98
+ return;
99
+ }
100
+
101
+ const token = result.authed_user?.access_token;
102
+ if (!token) {
103
+ console.log(chalk.red('✗ No user token in response'));
104
+ console.log(chalk.gray(' Response:'), JSON.stringify(result, null, 2));
105
+ return;
106
+ }
107
+
108
+ // Success!
109
+ const shellConfig = getShellConfig();
110
+ console.log(chalk.green('✓ Got token!\n'));
111
+ console.log(chalk.yellow('Step 3: Save the token\n'));
112
+ console.log(chalk.gray(` Add to your ${shellConfig}:\n`));
113
+ console.log(chalk.white(` export SLACK_USER_TOKEN="${token}"\n`));
114
+ console.log(chalk.gray(' Then reload your shell:\n'));
115
+ console.log(chalk.white(` source ${shellConfig}\n`));
116
+
117
+ } catch (error) {
118
+ console.log(chalk.red('✗ Failed to exchange code for token'));
119
+ if (error instanceof Error) {
120
+ console.log(chalk.gray(` ${error.message}`));
121
+ }
122
+ }
123
+ }
124
+
125
+ export async function authStatusCommand(): Promise<void> {
126
+ console.log(chalk.bold('\n🔐 Auth Status\n'));
127
+
128
+ const clientId = process.env.SLACK_CLIENT_ID;
129
+ const clientSecret = process.env.SLACK_CLIENT_SECRET;
130
+ const hasCredentials = clientId && clientSecret;
131
+
132
+ if (hasCredentials) {
133
+ console.log(chalk.green('✓ Slack app credentials configured'));
134
+ } else {
135
+ console.log(chalk.yellow('✗ Slack app credentials missing'));
136
+ console.log(chalk.gray(' Run: droid auth slack'));
137
+ }
138
+
139
+ if (hasSlackToken()) {
140
+ const token = process.env.SLACK_USER_TOKEN!;
141
+ const masked = token.slice(0, 10) + '...' + token.slice(-4);
142
+ console.log(chalk.green('✓ Slack user token configured'));
143
+ console.log(chalk.gray(` Token: ${masked}`));
144
+ } else {
145
+ console.log(chalk.yellow('✗ Slack user token missing'));
146
+ if (hasCredentials) {
147
+ console.log(chalk.gray(' Run: droid auth slack'));
148
+ }
149
+ }
150
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Secret/token helpers - reads from environment variables only
3
+ * Users manage their own tokens in shell config (e.g., ~/.zshrc)
4
+ */
5
+
6
+ export function getSlackToken(): string | undefined {
7
+ return process.env.SLACK_USER_TOKEN;
8
+ }
9
+
10
+ export function hasSlackToken(): boolean {
11
+ return !!process.env.SLACK_USER_TOKEN;
12
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "droid-status-update",
3
+ "version": "0.1.0",
4
+ "description": "Generate and post project status updates. Aggregates context from codex projects, Jira epics, and manual input. Posts to Slack or prints to terminal.",
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
+ "status-update"
15
+ ],
16
+ "skills": [
17
+ "./skills/status-update/SKILL.md"
18
+ ],
19
+ "commands": [
20
+ "./commands/status-update.md"
21
+ ]
22
+ }
@@ -0,0 +1,21 @@
1
+ name: status-update
2
+ description: "Generate and post project status updates. Aggregates context from codex projects, Jira epics, and manual input. Posts to Slack or prints to terminal."
3
+ version: 0.1.0
4
+ status: beta
5
+
6
+ includes:
7
+ skills:
8
+ - name: status-update
9
+ required: true
10
+ commands:
11
+ - name: status-update
12
+ is_alias: false
13
+ agents: []
14
+
15
+ dependencies: []
16
+
17
+ config_schema:
18
+ default_crosspost_channel:
19
+ type: string
20
+ description: Default channel for cross-posting updates (e.g., rnd-updates)
21
+ required: false
@@ -0,0 +1,27 @@
1
+ ---
2
+ name: status-update
3
+ description: "Generate and post project status updates. Pulls from codex, Jira, and manual input. Posts to Slack or prints to terminal."
4
+ argument-hint: "[{project}]"
5
+ ---
6
+
7
+ # /status-update
8
+
9
+ **User invoked:** `/status-update $ARGUMENTS`
10
+
11
+ **Your task:** Invoke the **status-update skill** with these arguments.
12
+
13
+ ## Examples
14
+
15
+ - `/status-update` → Prompt for project or use active project
16
+ - `/status-update partnership-automation` → Generate update for specific project
17
+ - `/status-update --no-slack` → Generate update but print to terminal instead of posting
18
+
19
+ ## Quick Reference
20
+
21
+ ```
22
+ /status-update # Interactive - prompts for everything
23
+ /status-update {project} # Use codex project for metadata
24
+ /status-update --no-slack # Output to terminal instead of Slack
25
+ ```
26
+
27
+ See the **status-update skill** for complete documentation.