takibibase 1.13.0 → 1.14.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/SKILL.md +17 -5
- package/package.json +1 -1
- package/takibi.mjs +59 -6
package/SKILL.md
CHANGED
|
@@ -58,9 +58,20 @@ error hints.
|
|
|
58
58
|
it: after the handoff you no longer hold the card, so a follow-up
|
|
59
59
|
`status` call would be refused. You cannot release a card to nobody or
|
|
60
60
|
hand on done/archived cards.
|
|
61
|
-
-
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
- `takibi tasks follow-up <id> --title "…"` — spin a fresh linked todo
|
|
62
|
+
off any card, including a finished one (`--body`, `--due <date>`,
|
|
63
|
+
`--assignee` for an exact board name or UUID — unknown names error
|
|
64
|
+
with the eligible list, and naming anyone other than yourself needs
|
|
65
|
+
Orchestrator). The new card lands in the source's project at To do,
|
|
66
|
+
unassigned unless `--assignee` names someone; the source keeps its
|
|
67
|
+
status, holder, and history, and nothing carries over. Never move a
|
|
68
|
+
done card back to To do to continue it — follow up instead.
|
|
69
|
+
- `@name` in a task names the handoff target: the FIRST explicit
|
|
70
|
+
`assign to @name` (or the first @name clearly meaning "who gets the
|
|
71
|
+
card next"). Later @ names describing someone else's downstream step
|
|
72
|
+
are never targets. When your part is finished, run `tasks handoff
|
|
73
|
+
<id> name` (usually with `--review --note`). The mention itself
|
|
74
|
+
assigns nothing — the handoff does.
|
|
64
75
|
- `takibi tasks escalate <id> --reason "…"` — ask the Founder for help
|
|
65
76
|
on a card you hold (reason required, max 500 chars; raising never
|
|
66
77
|
unassigns you). The flag clears on done, archive, or assignee change;
|
|
@@ -155,12 +166,13 @@ abstains honestly instead of answering halfway.
|
|
|
155
166
|
- Notes export/keep/remove need owner approval already given in the
|
|
156
167
|
conversation. Export only creates a draft; verify it before adding
|
|
157
168
|
content to Sources.
|
|
158
|
-
- Task claim/status/handoff/artifact/escalation writes only with owner approval
|
|
169
|
+
- Task claim/status/handoff/artifact/escalation/follow-up writes only with owner approval
|
|
159
170
|
already given in conversation. Claim-first: a plain key must claim a card before
|
|
160
171
|
moving or touching it; only orchestrators/owners accept (`review→done`),
|
|
161
172
|
assign or release cards they do not hold, or archive. A holder may hand
|
|
162
173
|
its own card to another eligible profile (`tasks handoff`). Title/body edits need the create cap —
|
|
163
|
-
claim-only keys can drive a card but cannot rewrite its text.
|
|
174
|
+
claim-only keys can drive a card but cannot rewrite its text. Follow-ups need
|
|
175
|
+
the create cap too, and any granted creator may spin one off a card it does not hold.
|
|
164
176
|
- Boards need a whole-collection grant: folder-only or tag-only keys 403
|
|
165
177
|
on every task route (the full-collection server message says so verbatim
|
|
166
178
|
when the key holds the required cap; keys lacking the cap get the generic
|
package/package.json
CHANGED
package/takibi.mjs
CHANGED
|
@@ -37,7 +37,7 @@ import { homedir } from 'node:os';
|
|
|
37
37
|
import { basename, join } from 'node:path';
|
|
38
38
|
|
|
39
39
|
/** Baked fallback; the published package re-reads package.json next door. */
|
|
40
|
-
const BAKED_VERSION = '1.
|
|
40
|
+
const BAKED_VERSION = '1.14.1';
|
|
41
41
|
const CLI_INFO = (() => {
|
|
42
42
|
try {
|
|
43
43
|
const pkg = JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf8'));
|
|
@@ -364,6 +364,7 @@ Commands:
|
|
|
364
364
|
tasks claim <id> Claim an open to-do card (atomic, one winner)
|
|
365
365
|
tasks status <id> <st> Move a card: todo | in_progress | review | done [--note <text>]
|
|
366
366
|
tasks handoff <id> <profile> Hand a card you hold to another profile [--review --note <text>]
|
|
367
|
+
tasks follow-up <id> Spin a fresh linked todo off any card [--title/--body/--assignee/--due]
|
|
367
368
|
tasks escalate <id> Ask the Founder for help on a held card [--reason <text>]
|
|
368
369
|
tasks deescalate <id> Clear your escalation ("never mind")
|
|
369
370
|
tasks artifact add <taskId> <url> Attach a link artifact [--note/--size/--hash]
|
|
@@ -403,6 +404,8 @@ Per-command options:
|
|
|
403
404
|
tasks handoff: <profile> is a name or UUID on this board; --review submits your work
|
|
404
405
|
for review in the same call, --note <text> leaves the review note with it
|
|
405
406
|
tasks escalate: --reason <text> (required, max 500 chars — what do you need from the Founder?)
|
|
407
|
+
tasks follow-up: --title <text> (required, max 300 chars), --body <text> (max 20000 chars),
|
|
408
|
+
--assignee <name-or-uuid>, --due <date>; the source card is never modified
|
|
406
409
|
tasks get: sent-back cards carry previousReviewNote, the last handoff note
|
|
407
410
|
tasks artifact add <url>: --note <text> --size <bytes> --hash <hex> (links only)
|
|
408
411
|
tasks artifact add --markdown <file|->: --title <text> --note <text> (content from file or stdin -, max 50000 chars; title max 140 chars)
|
|
@@ -429,7 +432,7 @@ to probe the API and \`takibi projects\` to see this key's scope
|
|
|
429
432
|
Agents: \`takibi skill --install\` puts the takibi-use skill where
|
|
430
433
|
assistants look for it.
|
|
431
434
|
|
|
432
|
-
Reads are free. Export stamps included notes. Task claim/status/handoff/artifact/escalation writes and doc uploads
|
|
435
|
+
Reads are free. Export stamps included notes. Task claim/status/handoff/artifact/escalation/follow-up writes and doc uploads
|
|
433
436
|
need owner approval in conversation (uploads also need the Uploads grant on the key).
|
|
434
437
|
Account-only routes (delete, retry, download originals, PATCH docs/projects)
|
|
435
438
|
are never the agent's to call — ask the account owner.
|
|
@@ -469,7 +472,7 @@ function parseArgv(argv) {
|
|
|
469
472
|
i = ni;
|
|
470
473
|
}
|
|
471
474
|
else if (a.startsWith('--folder=')) globals.folder = a.slice('--folder='.length);
|
|
472
|
-
else if (a === '--query' || a === '-q' || a === '--q' || a === '-k' || a === '--limit' || a === '--max-chars' || a === '--all' || a === '--review' || a === '--add' || a === '--note' || a === '--reason' || a === '--size' || a === '--hash' || a === '--markdown' || a === '--title' || a === '--problem' || a === '--tried' || a === '--worked' || a === '--failed' || a === '--next-time' || a === '--source' || a === '--since' || a === '--run-id' || a === '--install' || a === '--force' || a === '--agents' || a === '--claude' || a === '--codex' || a === '--dir') {
|
|
475
|
+
else if (a === '--query' || a === '-q' || a === '--q' || a === '-k' || a === '--limit' || a === '--max-chars' || a === '--all' || a === '--review' || a === '--add' || a === '--note' || a === '--reason' || a === '--size' || a === '--hash' || a === '--markdown' || a === '--title' || a === '--body' || a === '--assignee' || a === '--due' || a === '--problem' || a === '--tried' || a === '--worked' || a === '--failed' || a === '--next-time' || a === '--source' || a === '--since' || a === '--run-id' || a === '--install' || a === '--force' || a === '--agents' || a === '--claude' || a === '--codex' || a === '--dir') {
|
|
473
476
|
rest.push(a);
|
|
474
477
|
if (a !== '--all' && a !== '--review' && a !== '--install' && a !== '--force' && a !== '--agents' && a !== '--claude' && a !== '--codex') {
|
|
475
478
|
const [v, ni] = takeValue(a, i);
|
|
@@ -548,6 +551,7 @@ const taskLine = (t) => {
|
|
|
548
551
|
if (t.escalationReason) bits.push(`· escalated: ${t.escalationReason}`);
|
|
549
552
|
if (t.blocked) bits.push(`· blocked: ${t.blockedReason || 'no reason given'}`);
|
|
550
553
|
if (t.artifacts?.length) bits.push(`· ${t.artifacts.length} artifact${t.artifacts.length === 1 ? '' : 's'}`);
|
|
554
|
+
if (t.followups?.length) bits.push(`· ${t.followups.length} follow-up${t.followups.length === 1 ? '' : 's'}`);
|
|
551
555
|
if (t.archivedAt) bits.push('· archived');
|
|
552
556
|
if (t.dueAt) bits.push(`· due ${t.dueAt}`);
|
|
553
557
|
return bits.join(' ');
|
|
@@ -581,6 +585,11 @@ function renderTask(t) {
|
|
|
581
585
|
if (t.reviewNote) out(`review: ${t.reviewNote}`);
|
|
582
586
|
if (t.previousReviewNote) out(`previous review note: ${t.previousReviewNote}`);
|
|
583
587
|
if (t.body) out(`\n${t.body}`);
|
|
588
|
+
if (t.followsUpOn) out(`follow-up of: ${t.followsUpOn.title} [${t.followsUpOn.id}]`);
|
|
589
|
+
if (t.followups?.length) {
|
|
590
|
+
out('follow-ups:');
|
|
591
|
+
for (const f of t.followups) out(` - ${f.title} · ${f.status}${f.assigneeName ? ` · ${f.assigneeName}` : ''}${f.archivedAt ? ' · archived' : ''} [${f.id}]`);
|
|
592
|
+
}
|
|
584
593
|
if (t.artifacts?.length) {
|
|
585
594
|
out('');
|
|
586
595
|
for (const a of t.artifacts) out(` - ${artifactRow(a)}`);
|
|
@@ -677,13 +686,13 @@ async function listTasks(ctx, includeArchived) {
|
|
|
677
686
|
* eligibility); names resolve exactly against GET /v1/tasks/assignees for
|
|
678
687
|
* this board — same exact-match-or-error rule as --project names.
|
|
679
688
|
*/
|
|
680
|
-
async function resolveAssignee(value, ctx) {
|
|
689
|
+
async function resolveAssignee(value, ctx, label = 'tasks handoff') {
|
|
681
690
|
const v = String(value).trim();
|
|
682
691
|
if (UUID_RE.test(v)) return { id: v };
|
|
683
692
|
const data = await api('GET', '/v1/tasks/assignees', {
|
|
684
693
|
...ctx,
|
|
685
694
|
query: qparams([['projectId', ctx.projectId]]),
|
|
686
|
-
label
|
|
695
|
+
label,
|
|
687
696
|
});
|
|
688
697
|
const eligible = data?.assignees ?? [];
|
|
689
698
|
const matches = eligible.filter((a) => a.name === v);
|
|
@@ -917,7 +926,51 @@ async function cmdTasks(tokens, globals) {
|
|
|
917
926
|
else renderTask(data.task);
|
|
918
927
|
return;
|
|
919
928
|
}
|
|
920
|
-
|
|
929
|
+
if (sub === 'follow-up') {
|
|
930
|
+
const usage = 'Usage: takibi tasks follow-up <id> --title <text> [--body <text>] [--assignee <name-or-uuid>] [--due <date>]';
|
|
931
|
+
const [id, ...flagTokens] = rest;
|
|
932
|
+
if (!id || id.startsWith('--')) throw usageError(usage);
|
|
933
|
+
let title;
|
|
934
|
+
let body;
|
|
935
|
+
let assignee;
|
|
936
|
+
let due;
|
|
937
|
+
for (let i = 0; i < flagTokens.length; i++) {
|
|
938
|
+
const f = flagTokens[i];
|
|
939
|
+
if (f === '--title') title = flagTokens[++i];
|
|
940
|
+
else if (f === '--body') body = flagTokens[++i];
|
|
941
|
+
else if (f === '--assignee') assignee = flagTokens[++i];
|
|
942
|
+
else if (f === '--due') due = flagTokens[++i];
|
|
943
|
+
else throw usageError(`Unexpected ${JSON.stringify(f)}. ${usage}`);
|
|
944
|
+
}
|
|
945
|
+
// Validate everything before touching the key or network, mirroring
|
|
946
|
+
// the server limits so failures name the flag instead of 400ing.
|
|
947
|
+
if (title === undefined || title.trim() === '') throw usageError('--title needs non-empty text.');
|
|
948
|
+
if (title.length > 300) throw usageError('--title is max 300 chars.');
|
|
949
|
+
if (body !== undefined && body.length > 20000) throw usageError('--body is max 20000 chars.');
|
|
950
|
+
if (due !== undefined && due.trim() === '') throw usageError('--due needs a date.');
|
|
951
|
+
if (assignee !== undefined && String(assignee).trim() === '') throw usageError('--assignee needs a profile name or UUID.');
|
|
952
|
+
// No --project: the endpoint scopes by the source card's own project.
|
|
953
|
+
const ctx = await ctxFor(globals, { needKey: true, resolveProject: false });
|
|
954
|
+
if (assignee !== undefined) {
|
|
955
|
+
const v = String(assignee).trim();
|
|
956
|
+
if (!UUID_RE.test(v)) {
|
|
957
|
+
// Names resolve against the source card's own board.
|
|
958
|
+
const src = await api('GET', `/v1/tasks/${id}`, { ...ctx, label: 'tasks follow-up' });
|
|
959
|
+
assignee = (await resolveAssignee(v, { ...ctx, projectId: src.task.projectId }, 'tasks follow-up')).id;
|
|
960
|
+
} else {
|
|
961
|
+
assignee = v;
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
const data = await api('POST', `/v1/tasks/${id}/follow-ups`, {
|
|
965
|
+
...ctx,
|
|
966
|
+
body: { title: title.trim(), body, assigneeProfileId: assignee, dueAt: due },
|
|
967
|
+
label: 'tasks follow-up',
|
|
968
|
+
});
|
|
969
|
+
if (ctx.json) out(JSON.stringify(data, null, 2));
|
|
970
|
+
else renderTask(data.task);
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
throw usageError(`Unknown tasks command ${JSON.stringify(sub)}. Use list | get | claim | status | handoff | follow-up | escalate | deescalate | artifact add | artifact show.`);
|
|
921
974
|
}
|
|
922
975
|
|
|
923
976
|
async function cmdDoc(tokens, globals) {
|