flowcollab 0.1.3 → 0.1.5
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/bin/review.mjs +5 -26
- package/package.json +1 -2
- package/bin/init.mjs +0 -187
package/bin/review.mjs
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/* flow:review — request a code review for a task's PR.
|
|
3
3
|
Moves the task to in_review (if in_progress) and posts a standardised
|
|
4
|
-
review-request comment
|
|
5
|
-
|
|
6
|
-
webhook directly (bypasses the @mention comment path).
|
|
4
|
+
review-request comment. @to:<reviewer> in the comment body triggers
|
|
5
|
+
server-side notification routing (personal webhook → FLOW_NOTIFY_WEBHOOK).
|
|
7
6
|
|
|
8
7
|
Usage:
|
|
9
8
|
flow-review <task-id> [--pr=<number>] [--reviewer=<actor-id>] [--context="notes"]
|
|
@@ -26,10 +25,7 @@ async function main() {
|
|
|
26
25
|
const taskId = await resolveTaskId(rawId);
|
|
27
26
|
|
|
28
27
|
// Fetch tasks + people in parallel.
|
|
29
|
-
const
|
|
30
|
-
flowFetch('/api/flow/tasks?limit=500'),
|
|
31
|
-
flowFetch('/api/flow/people'),
|
|
32
|
-
]);
|
|
28
|
+
const { tasks = [] } = await flowFetch('/api/flow/tasks?limit=500');
|
|
33
29
|
|
|
34
30
|
const task = tasks.find(t => t.id === taskId);
|
|
35
31
|
if (!task) die(`Task ${taskId} not found.`);
|
|
@@ -54,23 +50,6 @@ async function main() {
|
|
|
54
50
|
|
|
55
51
|
await flowFetch('/api/flow/comment', { method: 'POST', body: { task_id: taskId, body } });
|
|
56
52
|
|
|
57
|
-
// If reviewer has a personal webhook, ping it directly too (fire-and-forget).
|
|
58
|
-
if (reviewerArg) {
|
|
59
|
-
const peopleArr = Array.isArray(people) ? people : Object.values(people);
|
|
60
|
-
const reviewer = peopleArr.find(p => p.id === reviewerArg);
|
|
61
|
-
const hook = reviewer?.notify_webhook || process.env.FLOW_NOTIFY_WEBHOOK;
|
|
62
|
-
if (hook) {
|
|
63
|
-
const ref = `#${task.issue_num ?? taskId.slice(0, 6)}`;
|
|
64
|
-
fetch(hook, {
|
|
65
|
-
method: 'POST',
|
|
66
|
-
headers: { 'content-type': 'application/json' },
|
|
67
|
-
body: JSON.stringify({
|
|
68
|
-
text: `Review requested on ${ref} — PR #${prNum}${prUrl ? ' ' + prUrl : ''}${context ? '\n' + context : ''}`,
|
|
69
|
-
}),
|
|
70
|
-
}).catch(() => {});
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
53
|
// Move to in_review if currently in_progress.
|
|
75
54
|
if (task.status === 'in_progress') {
|
|
76
55
|
await flowFetch(`/api/flow/tasks/${taskId}`, { method: 'PATCH', body: { status: 'in_review' } });
|
|
@@ -78,8 +57,8 @@ async function main() {
|
|
|
78
57
|
|
|
79
58
|
const ref = `#${task.issue_num ?? taskId.slice(0, 6)}`;
|
|
80
59
|
const moved = task.status === 'in_progress' ? ' (moved → in_review)' : '';
|
|
81
|
-
const
|
|
82
|
-
process.stdout.write(`Review requested on ${ref} — PR #${prNum}${prUrl ? ' ' + prUrl : ''}${moved}${
|
|
60
|
+
const mentioned = reviewerArg ? ` (@to:${reviewerArg} notified)` : '';
|
|
61
|
+
process.stdout.write(`Review requested on ${ref} — PR #${prNum}${prUrl ? ' ' + prUrl : ''}${moved}${mentioned}\n`);
|
|
83
62
|
}
|
|
84
63
|
|
|
85
64
|
main().catch(e => die(e.message || e));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flowcollab",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Multi-Claude coordination layer — shared task board + CLI for teams running Claude Code",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -26,7 +26,6 @@
|
|
|
26
26
|
"flow-handoff": "bin/handoff.mjs",
|
|
27
27
|
"flow-standup": "bin/standup.mjs",
|
|
28
28
|
"flow-search": "bin/search.mjs",
|
|
29
|
-
"flow-init": "bin/init.mjs",
|
|
30
29
|
"flow-project": "bin/project.mjs",
|
|
31
30
|
"flow-close-sprint": "bin/close-sprint.mjs",
|
|
32
31
|
"flow-review": "bin/review.mjs",
|
package/bin/init.mjs
DELETED
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/* flow:init — set up CLI access for a web-provisioned Flow account.
|
|
3
|
-
|
|
4
|
-
You signed up at https://flow-production-84b7.up.railway.app and have a
|
|
5
|
-
token from the Flow setup page. This writes your .env and CLAUDE.md.
|
|
6
|
-
|
|
7
|
-
Usage: flow-init --web
|
|
8
|
-
flow-init --web
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
12
|
-
import path from 'node:path';
|
|
13
|
-
import { createInterface } from 'node:readline';
|
|
14
|
-
|
|
15
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
16
|
-
const ask = (q) => new Promise(r => rl.question(q, r));
|
|
17
|
-
|
|
18
|
-
function buildWebClaudeMd({ actorId, ownerActorId, boardUrl, scope, areas = [] }) {
|
|
19
|
-
const ownerRef = scope === 'owner' ? actorId : (ownerActorId || 'owner');
|
|
20
|
-
const areaLine = areas.length ? areas.join(', ') : 'backend, frontend, infra, docs';
|
|
21
|
-
return `# Flow — Claude Code workflow
|
|
22
|
-
|
|
23
|
-
This project uses Flow for multi-agent coordination.
|
|
24
|
-
Follow this workflow every session without exception.
|
|
25
|
-
|
|
26
|
-
## Your identity
|
|
27
|
-
- **Your actor ID:** \`${actorId}\`
|
|
28
|
-
- **Your scope:** ${scope}
|
|
29
|
-
- **Board:** ${boardUrl}
|
|
30
|
-
|
|
31
|
-
## Required session workflow
|
|
32
|
-
|
|
33
|
-
\`\`\`bash
|
|
34
|
-
flow-pull # ALWAYS start here — board snapshot + your mentions
|
|
35
|
-
flow-claim <task_id> # claim before writing any code
|
|
36
|
-
flow-comment <task_id> "..." # update at milestones, use @to:${ownerRef} to ping
|
|
37
|
-
flow-close <task_id> "summary" # close with a meaningful summary when done
|
|
38
|
-
flow-propose --parent=<id> --title="..." --md="..." # propose new work; never start without approval
|
|
39
|
-
\`\`\`
|
|
40
|
-
|
|
41
|
-
## Rules — never violate
|
|
42
|
-
- \`flow-pull\` at session start — no exceptions
|
|
43
|
-
- Claim a task before touching related code
|
|
44
|
-
- Comment at major milestones, not every commit
|
|
45
|
-
- Close with a summary the next agent can act on
|
|
46
|
-
- Never approve your own proposals — only **owners** can approve (in the browser at ${boardUrl})
|
|
47
|
-
- Use \`@to:${ownerRef}\` in comments to request human action or review
|
|
48
|
-
|
|
49
|
-
## All CLI commands
|
|
50
|
-
\`\`\`
|
|
51
|
-
flow-pull — board snapshot + mentions + handoffs
|
|
52
|
-
flow-pull --focus — top-3 open tasks sorted by priority
|
|
53
|
-
flow-pull --milestone=<name> — filter snapshot to a sprint
|
|
54
|
-
flow-claim <id> — self-assign a task
|
|
55
|
-
flow-comment <id> "..." — add a comment or milestone update
|
|
56
|
-
flow-close <id> "summary" — mark task done with summary
|
|
57
|
-
flow-create --title="..." — create a task directly (no approval gate)
|
|
58
|
-
flow-create --from-issue=<num> — import a GitHub Issue as a task
|
|
59
|
-
flow-create --from-project-item=<id> — import a GitHub Projects item
|
|
60
|
-
flow-create --template=<id> — create from template (standup, pr-review, retro, ...)
|
|
61
|
-
flow-propose --parent=<id> --title="..." --md="..." — propose work for human approval
|
|
62
|
-
flow-approve/reject <id> — resolve a proposal (owner only)
|
|
63
|
-
flow-assign <id> <actor> — reassign a task (owner only)
|
|
64
|
-
flow-decisions — list pending proposals
|
|
65
|
-
flow-search "query" — search tasks by title
|
|
66
|
-
flow-status — board summary (counts, active agents)
|
|
67
|
-
flow-standup — recent activity digest
|
|
68
|
-
flow-standup --velocity — recent activity + week-over-week velocity chart
|
|
69
|
-
flow-handoff --task=<id> --to=<actor> — hand off a task with context
|
|
70
|
-
flow-heartbeat — send presence ping (run in background)
|
|
71
|
-
flow-sync — delta sync since N minutes ago
|
|
72
|
-
flow-ping — health check
|
|
73
|
-
flow-whoami — verify token and actor identity
|
|
74
|
-
flow-project — list GitHub Projects v2 items
|
|
75
|
-
flow-close-sprint --milestone=<name> — close sprint (owner only)
|
|
76
|
-
flow-review <id> [--pr=<num>] [--reviewer=<actor>] — request code review; moves → in_review, pings reviewer
|
|
77
|
-
flow-edit <id> [--title=] [--priority=] [--area=] [--due=] [--milestone=] [--blocked-by=]
|
|
78
|
-
flow-unblock <id> — clear blocked_by
|
|
79
|
-
flow-log [--task=<id>] [--limit=20] — tail recent timeline events
|
|
80
|
-
\`\`\`
|
|
81
|
-
|
|
82
|
-
## Area labels
|
|
83
|
-
${areaLine}
|
|
84
|
-
`;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
async function main() {
|
|
88
|
-
if (!process.argv.includes('--web')) {
|
|
89
|
-
console.error(
|
|
90
|
-
'Usage: flow-init --web\n\n' +
|
|
91
|
-
'Flow is a hosted SaaS. Sign up at:\n' +
|
|
92
|
-
' https://flow-production-84b7.up.railway.app\n\n' +
|
|
93
|
-
'Once you have a token from the setup page, run:\n' +
|
|
94
|
-
' flow-init --web\n'
|
|
95
|
-
);
|
|
96
|
-
process.exit(1);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
console.log('\nFlow Board — web setup\n');
|
|
100
|
-
console.log('You signed up via the web and already have a token from the Flow setup page.');
|
|
101
|
-
console.log('This writes your .env and CLAUDE.md — no Supabase config needed.\n');
|
|
102
|
-
|
|
103
|
-
const apiBase = (await ask('Flow server URL (e.g. https://flow-production-84b7.up.railway.app): '))
|
|
104
|
-
.trim().replace(/\/+$/, '');
|
|
105
|
-
const token = (await ask('Your API token (from setup page): ')).trim();
|
|
106
|
-
const actorId = (await ask('Your actor ID (from setup page, e.g. "alice"): ')).trim() || 'owner';
|
|
107
|
-
const scopeRaw = (await ask('Your scope [owner/contributor, default: owner]: ')).trim().toLowerCase();
|
|
108
|
-
const scope = scopeRaw === 'contributor' ? 'contributor' : 'owner';
|
|
109
|
-
|
|
110
|
-
let ownerActorId = actorId;
|
|
111
|
-
if (scope === 'contributor') {
|
|
112
|
-
ownerActorId = (await ask("Owner's actor ID (for CLAUDE.md @mentions, e.g. \"alice\"): ")).trim() || 'owner';
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const cwd = process.cwd();
|
|
116
|
-
const localAreas = (() => {
|
|
117
|
-
try {
|
|
118
|
-
const raw = readFileSync(path.join(cwd, 'flow.config.json'), 'utf8');
|
|
119
|
-
return Object.keys(JSON.parse(raw).labels?.area || {});
|
|
120
|
-
} catch { return []; }
|
|
121
|
-
})();
|
|
122
|
-
|
|
123
|
-
const envTarget = path.join(cwd, '.env');
|
|
124
|
-
const claudeMdTarget = path.join(cwd, 'CLAUDE.md');
|
|
125
|
-
const envExists = existsSync(envTarget);
|
|
126
|
-
const claudeExists = existsSync(claudeMdTarget);
|
|
127
|
-
|
|
128
|
-
const writeEnvAns = (await ask(
|
|
129
|
-
`\nWrite .env to ${cwd}? ${envExists ? '[y/N]' : '[Y/n]'}: `
|
|
130
|
-
)).trim().toLowerCase();
|
|
131
|
-
const writeEnv = envExists
|
|
132
|
-
? (writeEnvAns === 'y' || writeEnvAns === 'yes')
|
|
133
|
-
: (writeEnvAns !== 'n' && writeEnvAns !== 'no');
|
|
134
|
-
|
|
135
|
-
const writeClaudeAns = (await ask(
|
|
136
|
-
`Write CLAUDE.md to ${cwd}? ${claudeExists ? '[y/N]' : '[Y/n]'}: `
|
|
137
|
-
)).trim().toLowerCase();
|
|
138
|
-
const writeClaude = claudeExists
|
|
139
|
-
? (writeClaudeAns === 'y' || writeClaudeAns === 'yes')
|
|
140
|
-
: (writeClaudeAns !== 'n' && writeClaudeAns !== 'no');
|
|
141
|
-
|
|
142
|
-
rl.close();
|
|
143
|
-
|
|
144
|
-
const tokenVar = scope === 'owner' ? 'FLOW_API_TOKEN_OWNER' : 'FLOW_API_TOKEN_CONTRIBUTOR';
|
|
145
|
-
const actorVar = scope === 'owner' ? 'FLOW_TOKEN_OWNER_ACTOR' : 'FLOW_TOKEN_CONTRIBUTOR_ACTOR';
|
|
146
|
-
const envLines = [
|
|
147
|
-
`${tokenVar}=${token}`,
|
|
148
|
-
`${actorVar}=${actorId}`,
|
|
149
|
-
`FLOW_API_BASE=${apiBase}`,
|
|
150
|
-
`FLOW_ACTING_VIA=claude`,
|
|
151
|
-
`FLOW_DEFAULT_ASSIGNEE=${actorId}`,
|
|
152
|
-
];
|
|
153
|
-
|
|
154
|
-
console.log('\n========== .env ==========\n');
|
|
155
|
-
envLines.forEach(l => console.log(l));
|
|
156
|
-
|
|
157
|
-
if (writeEnv) {
|
|
158
|
-
if (envExists) {
|
|
159
|
-
const existing = readFileSync(envTarget, 'utf8');
|
|
160
|
-
const conflicts = envLines.map(l => l.split('=')[0]).filter(k => new RegExp(`^${k}=`, 'm').test(existing));
|
|
161
|
-
if (conflicts.length) {
|
|
162
|
-
console.log(`\n⚠ These keys already exist in .env — not overwriting: ${conflicts.join(', ')}`);
|
|
163
|
-
console.log(' Paste the block above into .env manually.');
|
|
164
|
-
} else {
|
|
165
|
-
writeFileSync(envTarget, existing.trimEnd() + '\n\n' + envLines.join('\n') + '\n', 'utf8');
|
|
166
|
-
console.log(`\n✓ .env updated. Add .env to .gitignore — it contains your API token.`);
|
|
167
|
-
}
|
|
168
|
-
} else {
|
|
169
|
-
writeFileSync(envTarget, envLines.join('\n') + '\n', 'utf8');
|
|
170
|
-
console.log(`\n✓ .env written. Add .env to .gitignore — it contains your API token.`);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if (writeClaude) {
|
|
175
|
-
const content = buildWebClaudeMd({ actorId, ownerActorId, boardUrl: `${apiBase}/flow/`, scope, areas: localAreas });
|
|
176
|
-
writeFileSync(claudeMdTarget, content, 'utf8');
|
|
177
|
-
console.log(`✓ CLAUDE.md written. Commit this so every agent on the team gets the workflow.`);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
console.log('\n========== Next steps ==========\n');
|
|
181
|
-
console.log(`1. Verify your connection: flow-whoami`);
|
|
182
|
-
console.log(`2. See your board: flow-pull`);
|
|
183
|
-
console.log(`3. Open the board: ${apiBase}/flow/`);
|
|
184
|
-
console.log('');
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
main().catch(e => { console.error('init failed:', e.message || e); process.exit(1); });
|