flowcollab 0.2.9 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/create.mjs CHANGED
@@ -53,7 +53,7 @@ async function main() {
53
53
  if (!num || num < 1) die('--from-issue must be a positive integer (e.g. --from-issue=42)');
54
54
  process.stdout.write(`Fetching GitHub Issue #${num}...\n`);
55
55
  const issue = await fetchGithubIssue(num);
56
- const labels = (issue.labels || []).map(l => l.name.toLowerCase());
56
+ const labels = (issue.labels || []).map(l => l?.name?.toLowerCase()).filter(Boolean);
57
57
  const type = labels.includes('bug') ? 'bug'
58
58
  : labels.includes('documentation') ? 'docs'
59
59
  : 'feature';
package/bin/edit.mjs CHANGED
@@ -14,7 +14,7 @@ const FIELD_MAP = {
14
14
  title: v => ({ title: v }),
15
15
  priority: v => ({ priority: v }),
16
16
  area: v => ({ area: v }),
17
- due: v => ({ due_at: /^\d{4}-\d{2}-\d{2}$/.test(v) ? v + 'T00:00:00Z' : v }),
17
+ due: v => ({ due_at: /^\d{4}-\d{2}-\d{2}$/.test(v) ? v + 'T12:00:00Z' : v }),
18
18
  milestone: v => ({ milestone: v }),
19
19
  'blocked-by': v => ({ blocked_by: v === 'null' ? [] : [v] }),
20
20
  };
package/bin/log.mjs CHANGED
@@ -36,7 +36,7 @@ function fmtEvent(e) {
36
36
 
37
37
  async function main() {
38
38
  const taskRaw = arg('task');
39
- const limit = Math.min(200, Math.max(1, parseInt(arg('limit') || '20')));
39
+ const limit = Math.min(200, Math.max(1, parseInt(arg('limit') || '20') || 20));
40
40
 
41
41
  const res = await flowFetch('/api/flow/tasks?limit=500');
42
42
  const tasks = res.tasks ?? [];
package/bin/login.mjs CHANGED
@@ -40,13 +40,18 @@ async function main() {
40
40
  // 3. Poll every 2s until authorized or expired
41
41
  const deadline = Date.now() + expires_in * 1000;
42
42
  process.stdout.write('Waiting for authorization');
43
+ let netErrCount = 0;
43
44
  while (Date.now() < deadline) {
44
45
  await new Promise(ok => setTimeout(ok, 2000));
45
46
  process.stdout.write('.');
46
47
  const poll = await fetch(
47
48
  `${base}/api/flow/auth/poll?device_token=${encodeURIComponent(device_token)}`,
48
49
  ).catch(() => null);
49
- if (!poll) continue;
50
+ if (!poll) {
51
+ if (++netErrCount >= 3) { process.stdout.write('\n'); throw new Error(`Cannot reach ${base} — check your connection.`); }
52
+ continue;
53
+ }
54
+ netErrCount = 0;
50
55
  if (poll.status === 410) {
51
56
  process.stdout.write('\n');
52
57
  const body = await poll.json().catch(() => ({}));
package/bin/pull.mjs CHANGED
@@ -76,7 +76,8 @@ function extractBody(ev) {
76
76
  }
77
77
 
78
78
  function fmtTimeline(ev, taskTitle) {
79
- const ts = ev.ts ? new Date(ev.ts).toISOString().replace('T', ' ').slice(0, 16) : '?';
79
+ const _d = ev.ts ? new Date(ev.ts) : null;
80
+ const ts = _d && !isNaN(_d) ? _d.toISOString().replace('T', ' ').slice(0, 16) : '?';
80
81
  const who = ev.slot_label || ev.actor_id || '?';
81
82
  const actingAs = ev.acting_as_id && ev.acting_as_id !== ev.actor_id ? `(as ${ev.acting_as_id})` : '';
82
83
  const body = extractBody(ev);
@@ -143,8 +144,9 @@ async function main() {
143
144
  const taskRef = task
144
145
  ? `working on #${task.issue_num ?? a.task_id.slice(0, 6)} "${sanitizeText(task.title, 60)}"`
145
146
  : 'idle';
146
- const ago = Math.round((Date.now() - new Date(a.last_seen_at).getTime()) / 60000);
147
- out.push(` ${a.actor_id} ${taskRef} (seen ${ago}m ago)`);
147
+ const seenMs = a.last_seen_at ? Date.now() - new Date(a.last_seen_at).getTime() : NaN;
148
+ const agoStr = !isNaN(seenMs) ? `${Math.round(seenMs / 60000)}m ago` : 'recently';
149
+ out.push(` ${a.actor_id} ${taskRef} (seen ${agoStr})`);
148
150
  }
149
151
  out.push('');
150
152
  }
@@ -162,7 +164,8 @@ async function main() {
162
164
  const t = taskById.get(ev.task_id);
163
165
  const title = t ? t.title : '(unknown task)';
164
166
  const taskRef = t ? `#${t.issue_num ?? t.id.slice(0, 6)}` : `#${ev.task_id.slice(0, 6)}`;
165
- const ts = ev.ts ? new Date(ev.ts).toISOString().replace('T', ' ').slice(0, 16) : '?';
167
+ const _md = ev.ts ? new Date(ev.ts) : null;
168
+ const ts = _md && !isNaN(_md) ? _md.toISOString().replace('T', ' ').slice(0, 16) : '?';
166
169
  out.push(` ${taskRef} ${title} (${ts}, from ${ev.actor_id || '?'})`);
167
170
  out.push(` └─ <comment_from_untrusted_user>${extractBody(ev)}</comment_from_untrusted_user>`);
168
171
  });
@@ -218,7 +221,8 @@ async function main() {
218
221
  const t = taskById.get(ev.task_id);
219
222
  const ref = t ? `#${t.issue_num ?? t.id.slice(0, 6)}` : `#${ev.task_id.slice(0, 6)}`;
220
223
  const title = t ? sanitizeText(t.title, 60) : '(unknown task)';
221
- const ts = ev.ts ? new Date(ev.ts).toISOString().replace('T', ' ').slice(0, 16) : '?';
224
+ const _hd = ev.ts ? new Date(ev.ts) : null;
225
+ const ts = _hd && !isNaN(_hd) ? _hd.toISOString().replace('T', ' ').slice(0, 16) : '?';
222
226
  out.push(` ${ref} ${title} (from ${ev.actor_id || '?'}, ${ts})`);
223
227
  const hd = t?.handoff_data;
224
228
  if (hd?.context || ev.payload?.context) {
@@ -410,7 +414,7 @@ async function main() {
410
414
  let gc = {};
411
415
  try { gc = JSON.parse(readFileSync(configPath, 'utf8')); } catch {}
412
416
  gc.projectId = defaultProject.id;
413
- writeFileSync(configPath, JSON.stringify(gc, null, 2));
417
+ writeFileSync(configPath, JSON.stringify(gc, null, 2), { encoding: 'utf8', mode: 0o600 });
414
418
  process.stdout.write(` project_id → ~/.flow/config.json (project: ${defaultProject.slug})\n`);
415
419
  }
416
420
  } catch { /* non-fatal */ }
package/bin/scan.mjs CHANGED
@@ -204,20 +204,22 @@ async function main() {
204
204
 
205
205
  if (all || doIssues) {
206
206
  process.stdout.write(' [issues] fetching GitHub Issues vs Flow tasks...\n');
207
- let s;
208
- try { s = await scanIssues(repo); } catch (e) { process.stdout.write(` [issues] Error: ${e.message}\n`); s = []; }
207
+ let s, issueErr = false;
208
+ try { s = await scanIssues(repo); } catch (e) { process.stdout.write(` [issues] Error: ${e.message}\n`); issueErr = true; s = []; }
209
209
  if (doIssues) { printSection('Untracked GitHub Issues', s || []); return; }
210
210
  if (s === null) process.stdout.write('\n[issues] Skipped — FLOW_GITHUB_REPO not set.\n');
211
+ else if (issueErr) process.stdout.write('\n[issues] Scan failed — check FLOW_GITHUB_REPO and token.\n');
211
212
  else if (all && s.length) printSection('Untracked GitHub Issues', s);
212
213
  else if (all) process.stdout.write('\n[issues] All open Issues are tracked in Flow.\n');
213
214
  }
214
215
 
215
216
  if (all || doPRs) {
216
217
  process.stdout.write(' [prs] fetching open PRs vs Flow tasks...\n');
217
- let s;
218
- try { s = await scanPRs(repo); } catch (e) { process.stdout.write(` [prs] Error: ${e.message}\n`); s = []; }
218
+ let s, prErr = false;
219
+ try { s = await scanPRs(repo); } catch (e) { process.stdout.write(` [prs] Error: ${e.message}\n`); prErr = true; s = []; }
219
220
  if (doPRs) { printSection('Unlinked open PRs', s || []); return; }
220
221
  if (s === null) process.stdout.write('\n[prs] Skipped — FLOW_GITHUB_REPO not set.\n');
222
+ else if (prErr) process.stdout.write('\n[prs] Scan failed — check FLOW_GITHUB_REPO and token.\n');
221
223
  else if (all && s.length) printSection('Unlinked open PRs', s);
222
224
  else if (all) process.stdout.write('\n[prs] All open PRs are linked to Flow tasks.\n');
223
225
  }
package/bin/standup.mjs CHANGED
@@ -30,7 +30,7 @@ const TITLE_W = Math.max(20, TERM_COLS - 29);
30
30
  const SHORT_TITLE_W = Math.max(20, TERM_COLS - 37); // agents have longer prefix
31
31
 
32
32
  async function main() {
33
- const sinceHours = Math.max(1, parseInt(arg('since') || '24'));
33
+ const sinceHours = Math.max(1, parseInt(arg('since') || '24') || 24);
34
34
  const since = new Date(Date.now() - sinceHours * 3600000);
35
35
  const showVelocity = process.argv.includes('--velocity');
36
36
 
@@ -52,7 +52,7 @@ async function main() {
52
52
  // Done since N hours — de-dup by task (most recent close per task)
53
53
  const doneEvents = timeline
54
54
  .filter(ev => ev.kind === 'event' && ev.payload?.act === 'closed' && new Date(ev.ts) >= since)
55
- .sort((a, b) => b.ts.localeCompare(a.ts));
55
+ .sort((a, b) => (b.ts || '').localeCompare(a.ts || ''));
56
56
  const seenDone = new Set();
57
57
  const doneItems = [];
58
58
  for (const ev of doneEvents) {
package/bin/sync.mjs CHANGED
@@ -34,7 +34,7 @@ function relAgo(ts) {
34
34
  function pad(s, n) { return String(s).padEnd(n).slice(0, n); }
35
35
 
36
36
  async function main() {
37
- const sinceMinutes = Math.max(1, parseInt(arg('since') || '60'));
37
+ const sinceMinutes = Math.max(1, parseInt(arg('since') || '60') || 60);
38
38
  const r = await flowFetch(`/api/flow/sync?since_minutes=${sinceMinutes}`);
39
39
 
40
40
  const out = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flowcollab",
3
- "version": "0.2.9",
3
+ "version": "0.3.1",
4
4
  "description": "Multi-Claude coordination layer — shared task board + CLI for teams running Claude Code",
5
5
  "type": "module",
6
6
  "files": [