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.
Files changed (3) hide show
  1. package/SKILL.md +14 -5
  2. package/package.json +1 -1
  3. 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 move it to review in the same call and `--note` to leave
49
- the review note with it: after the handoff you no longer hold the card,
50
- so a follow-up `status` call would be refused. You cannot release a card
51
- to nobody or hand on done/archived cards.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "takibibase",
3
- "version": "1.11.0",
3
+ "version": "1.13.0",
4
4
  "description": "Thin CLI for the Takibi API: ask, search, tasks, docs, notes. Single file, zero dependencies.",
5
5
  "type": "module",
6
6
  "bin": {
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.11.0';
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-only artifact
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 moves the card to
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 artifact add: --note <text> --size <bytes> --hash <hex>
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.url}${a.note ? ` — ${a.note}` : ''} [${a.id}]`);
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 [verb, taskId, url, ...flagTokens] = rest;
770
- if (verb !== 'add' || !taskId || !url) throw usageError('Usage: takibi tasks artifact add <taskId> <url> [--note <t> --size <n> --hash <h>]');
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)}. Usage: takibi tasks artifact add <taskId> <url> [--note <t> --size <n> --hash <h>]`);
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) {