takibibase 1.11.0 → 1.13.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.
- package/SKILL.md +14 -5
- package/package.json +1 -1
- package/takibi.mjs +100 -11
package/SKILL.md
CHANGED
|
@@ -41,14 +41,23 @@ error hints.
|
|
|
41
41
|
- `takibi tasks list | get <id> | claim <id> | status <id> <todo|in_progress|review|done> [--note …]`
|
|
42
42
|
and `takibi tasks artifact add <taskId> <url> [--note …]`. The status
|
|
43
43
|
`--note` is the review handoff note (max 1000 chars); leaving review
|
|
44
|
-
clears it.
|
|
44
|
+
clears it. A sent-back card keeps the last handoff as `previousReviewNote`
|
|
45
|
+
— read it for the last round's feedback without it being pasted into the body.
|
|
46
|
+
- `takibi tasks artifact add <taskId> --markdown <file|-> [--title …] [--note …]`
|
|
47
|
+
— attach an inline markdown note (content from a file or stdin `-`,
|
|
48
|
+
max 50000 chars; title max 140 chars);
|
|
49
|
+
`takibi tasks artifact show <taskId> <artifactId>` prints one note's raw
|
|
50
|
+
markdown. Prefer inline markdown artifacts for text deliverables —
|
|
51
|
+
briefs, drafts, debriefs; external Drive/URLs only for sheets, slides,
|
|
52
|
+
or binary files.
|
|
45
53
|
- `takibi tasks handoff <id> <profile> [--review] [--note "…"]` — hand a
|
|
46
54
|
card you hold to another profile on the same board (name or UUID; an
|
|
47
55
|
unknown name errors with the eligible names — never guess). Use
|
|
48
|
-
`--review` to
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
56
|
+
`--review` to submit your work for review in the same call (the inbox
|
|
57
|
+
credits you as the finisher) and `--note` to leave the review note with
|
|
58
|
+
it: after the handoff you no longer hold the card, so a follow-up
|
|
59
|
+
`status` call would be refused. You cannot release a card to nobody or
|
|
60
|
+
hand on done/archived cards.
|
|
52
61
|
- `@name` in a task names the handoff target: when your part is
|
|
53
62
|
finished, run `tasks handoff <id> name` (usually with `--review
|
|
54
63
|
--note`). The mention itself assigns nothing — the handoff does.
|
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.13.0';
|
|
41
41
|
const CLI_INFO = (() => {
|
|
42
42
|
try {
|
|
43
43
|
const pkg = JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf8'));
|
|
@@ -366,7 +366,9 @@ Commands:
|
|
|
366
366
|
tasks handoff <id> <profile> Hand a card you hold to another profile [--review --note <text>]
|
|
367
367
|
tasks escalate <id> Ask the Founder for help on a held card [--reason <text>]
|
|
368
368
|
tasks deescalate <id> Clear your escalation ("never mind")
|
|
369
|
-
tasks artifact add <taskId> <url> Attach a link
|
|
369
|
+
tasks artifact add <taskId> <url> Attach a link artifact [--note/--size/--hash]
|
|
370
|
+
tasks artifact add <taskId> --markdown <file|-> Attach an inline markdown note [--title/--note]
|
|
371
|
+
tasks artifact show <taskId> <artifactId> Print one markdown note's content
|
|
370
372
|
doc list Documents (metadata)
|
|
371
373
|
doc get <id> One document's metadata
|
|
372
374
|
doc text <id> Converted text of one document
|
|
@@ -398,10 +400,13 @@ Per-command options:
|
|
|
398
400
|
ask/search: -q, --query <text> (or a bare positional), -k <n> (ask 1-12, search 1-20)
|
|
399
401
|
tasks list: --all (include archived)
|
|
400
402
|
tasks status: --note <text> (review handoff note; cleared when the card leaves review)
|
|
401
|
-
tasks handoff: <profile> is a name or UUID on this board; --review
|
|
402
|
-
review in the same call, --note <text> leaves the review note with it
|
|
403
|
+
tasks handoff: <profile> is a name or UUID on this board; --review submits your work
|
|
404
|
+
for review in the same call, --note <text> leaves the review note with it
|
|
403
405
|
tasks escalate: --reason <text> (required, max 500 chars — what do you need from the Founder?)
|
|
404
|
-
tasks
|
|
406
|
+
tasks get: sent-back cards carry previousReviewNote, the last handoff note
|
|
407
|
+
tasks artifact add <url>: --note <text> --size <bytes> --hash <hex> (links only)
|
|
408
|
+
tasks artifact add --markdown <file|->: --title <text> --note <text> (content from file or stdin -, max 50000 chars; title max 140 chars)
|
|
409
|
+
tasks artifact show: prints the raw markdown (links carry no content)
|
|
405
410
|
doc list: --limit <n> (1-100) doc text: --max-chars <n>
|
|
406
411
|
doc upload: <file> (pdf, md, txt, docx, max 10MB)
|
|
407
412
|
notes append: --problem <text> (or a bare positional),
|
|
@@ -464,7 +469,7 @@ function parseArgv(argv) {
|
|
|
464
469
|
i = ni;
|
|
465
470
|
}
|
|
466
471
|
else if (a.startsWith('--folder=')) globals.folder = a.slice('--folder='.length);
|
|
467
|
-
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 === '--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') {
|
|
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') {
|
|
468
473
|
rest.push(a);
|
|
469
474
|
if (a !== '--all' && a !== '--review' && a !== '--install' && a !== '--force' && a !== '--agents' && a !== '--claude' && a !== '--codex') {
|
|
470
475
|
const [v, ni] = takeValue(a, i);
|
|
@@ -548,13 +553,37 @@ const taskLine = (t) => {
|
|
|
548
553
|
return bits.join(' ');
|
|
549
554
|
};
|
|
550
555
|
|
|
556
|
+
/**
|
|
557
|
+
* Display title for a task artifact: the explicit title when set, else
|
|
558
|
+
* the content's first ATX heading, else a fixed fallback. Local duplicate
|
|
559
|
+
* of artifactTitle in apps/web/src/tasks/artifacts.ts — this file ships
|
|
560
|
+
* standalone and cannot import TS.
|
|
561
|
+
*/
|
|
562
|
+
function artifactTitle(title, content) {
|
|
563
|
+
const explicit = title?.trim() ?? '';
|
|
564
|
+
if (explicit !== '') return explicit;
|
|
565
|
+
for (const line of (content ?? '').split('\n')) {
|
|
566
|
+
const text = /^#{1,6}\s+(.*)$/.exec(line.trim())?.[1] ?? '';
|
|
567
|
+
if (text !== '') return text;
|
|
568
|
+
}
|
|
569
|
+
return 'Untitled note';
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/** One artifact row: links print the URL exactly as before; markdown
|
|
573
|
+
* notes (null URL) print the display title — never the literal null. */
|
|
574
|
+
const artifactRow = (a) =>
|
|
575
|
+
a.kind === 'markdown' || a.url == null
|
|
576
|
+
? `${artifactTitle(a.title, a.content ?? null)}${a.note ? ` — ${a.note}` : ''} [${a.id}]`
|
|
577
|
+
: `${a.url}${a.note ? ` — ${a.note}` : ''} [${a.id}]`;
|
|
578
|
+
|
|
551
579
|
function renderTask(t) {
|
|
552
580
|
out(taskLine(t));
|
|
553
581
|
if (t.reviewNote) out(`review: ${t.reviewNote}`);
|
|
582
|
+
if (t.previousReviewNote) out(`previous review note: ${t.previousReviewNote}`);
|
|
554
583
|
if (t.body) out(`\n${t.body}`);
|
|
555
584
|
if (t.artifacts?.length) {
|
|
556
585
|
out('');
|
|
557
|
-
for (const a of t.artifacts) out(` - ${a
|
|
586
|
+
for (const a of t.artifacts) out(` - ${artifactRow(a)}`);
|
|
558
587
|
}
|
|
559
588
|
err(`created ${t.createdAt} · updated ${t.updatedAt}`);
|
|
560
589
|
}
|
|
@@ -766,13 +795,73 @@ async function cmdTasks(tokens, globals) {
|
|
|
766
795
|
return;
|
|
767
796
|
}
|
|
768
797
|
if (sub === 'artifact' || sub === 'artifacts') {
|
|
769
|
-
const
|
|
770
|
-
|
|
798
|
+
const LINK_USAGE = 'Usage: takibi tasks artifact add <taskId> <url> [--note <t> --size <n> --hash <h>]';
|
|
799
|
+
const MD_USAGE = 'Usage: takibi tasks artifact add <taskId> --markdown <file|-> [--title <t>] [--note <t>]';
|
|
800
|
+
const [verb, taskId, ...after] = rest;
|
|
801
|
+
if (verb === 'show') {
|
|
802
|
+
const [artifactId, extra] = after;
|
|
803
|
+
if (!taskId || !artifactId || extra) throw usageError('Usage: takibi tasks artifact show <taskId> <artifactId>');
|
|
804
|
+
const ctx = await ctxFor(globals, { needKey: true });
|
|
805
|
+
const data = await api('GET', `/v1/tasks/${taskId}/artifacts/${artifactId}`, {
|
|
806
|
+
...ctx,
|
|
807
|
+
query: qparams([['projectId', ctx.projectId]]),
|
|
808
|
+
label: 'tasks artifact show',
|
|
809
|
+
});
|
|
810
|
+
// --json prints the server envelope even for links (content null);
|
|
811
|
+
// human output refuses instead of printing an empty page.
|
|
812
|
+
if (ctx.json) {
|
|
813
|
+
out(JSON.stringify(data, null, 2));
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
if (data.artifact.content == null) {
|
|
817
|
+
throw new CliError('That artifact is a link — it carries no stored content to print.');
|
|
818
|
+
}
|
|
819
|
+
out(data.artifact.content);
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
const [url, ...flagTokens] = after;
|
|
823
|
+
if (verb !== 'add' || !taskId || !url) throw usageError(LINK_USAGE);
|
|
824
|
+
if (url === '--markdown') {
|
|
825
|
+
const [file, ...mdFlags] = flagTokens;
|
|
826
|
+
if (!file) throw usageError(MD_USAGE);
|
|
827
|
+
const md = {};
|
|
828
|
+
for (let i = 0; i < mdFlags.length; i++) {
|
|
829
|
+
const f = mdFlags[i];
|
|
830
|
+
if (f === '--title' || f === '--note') md[f.slice(2)] = mdFlags[++i];
|
|
831
|
+
else throw usageError(`Unexpected ${JSON.stringify(f)}. ${MD_USAGE}`);
|
|
832
|
+
}
|
|
833
|
+
if (md.title !== undefined && md.title === '') throw usageError(`--title needs non-empty text. ${MD_USAGE}`);
|
|
834
|
+
if (md.note !== undefined && md.note === '') throw usageError(`--note needs non-empty text. ${MD_USAGE}`);
|
|
835
|
+
let content;
|
|
836
|
+
if (file === '-') {
|
|
837
|
+
content = readFileSync(0, 'utf8');
|
|
838
|
+
} else {
|
|
839
|
+
try {
|
|
840
|
+
content = readFileSync(file, 'utf8');
|
|
841
|
+
} catch {
|
|
842
|
+
throw usageError(`No such file: ${file}.`);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
if (!content.trim()) throw usageError(`${file === '-' ? 'That stdin' : file} is empty — nothing to attach.`);
|
|
846
|
+
const ctx = await ctxFor(globals, { needKey: true });
|
|
847
|
+
const data = await api('POST', `/v1/tasks/${taskId}/artifacts`, {
|
|
848
|
+
...ctx,
|
|
849
|
+
query: qparams([['projectId', ctx.projectId]]),
|
|
850
|
+
body: { kind: 'markdown', title: md.title, content, note: md.note },
|
|
851
|
+
label: 'tasks artifact add',
|
|
852
|
+
});
|
|
853
|
+
if (ctx.json) out(JSON.stringify(data, null, 2));
|
|
854
|
+
else {
|
|
855
|
+
out(`attached ${data.artifact.id}`);
|
|
856
|
+
out(` ${artifactTitle(data.artifact.title, content)}${data.artifact.note ? ` — ${data.artifact.note}` : ''}`);
|
|
857
|
+
}
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
771
860
|
const flags = {};
|
|
772
861
|
for (let i = 0; i < flagTokens.length; i++) {
|
|
773
862
|
const f = flagTokens[i];
|
|
774
863
|
if (f === '--note' || f === '--size' || f === '--hash') flags[f.slice(2)] = flagTokens[++i];
|
|
775
|
-
else throw usageError(`Unexpected ${JSON.stringify(f)}.
|
|
864
|
+
else throw usageError(`Unexpected ${JSON.stringify(f)}. ${LINK_USAGE}`);
|
|
776
865
|
}
|
|
777
866
|
if (flags.note !== undefined && flags.note === '') throw usageError('--note needs non-empty text.');
|
|
778
867
|
if (flags.size !== undefined) {
|
|
@@ -828,7 +917,7 @@ async function cmdTasks(tokens, globals) {
|
|
|
828
917
|
else renderTask(data.task);
|
|
829
918
|
return;
|
|
830
919
|
}
|
|
831
|
-
throw usageError(`Unknown tasks command ${JSON.stringify(sub)}. Use list | get | claim | status | handoff | escalate | deescalate | artifact add.`);
|
|
920
|
+
throw usageError(`Unknown tasks command ${JSON.stringify(sub)}. Use list | get | claim | status | handoff | escalate | deescalate | artifact add | artifact show.`);
|
|
832
921
|
}
|
|
833
922
|
|
|
834
923
|
async function cmdDoc(tokens, globals) {
|