takibibase 1.3.0 → 1.7.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 +19 -5
  2. package/package.json +1 -1
  3. package/takibi.mjs +50 -10
package/SKILL.md CHANGED
@@ -23,6 +23,10 @@ error hints.
23
23
  - Projects: `takibi projects` lists what this key can reach. `--project`
24
24
  takes a name or a UUID; single-grant keys may omit it.
25
25
  `takibi projects --add <name> <uuid>` keeps local aliases for offline use.
26
+ - Folders: `takibi folders [--project …]` lists folder names → uuids for
27
+ `--folder` (only folders this key's grants touch; single-grant keys may
28
+ omit `--project`). Discovery flow: `projects` → `folders` → scoped
29
+ `ask`/`search`/`doc list`.
26
30
  - First probe: `takibi version` (needs no key; shows build + `jev` status).
27
31
  - Update notice: the CLI polls the npm registry once a day and nudges on
28
32
  stderr when behind (never on `--json`).
@@ -44,17 +48,17 @@ error hints.
44
48
  Add `--run-id <run-id>` when a run may retry: the same run ID and body
45
49
  return the existing note instead of appending a duplicate.
46
50
  - `takibi notes list` — review queue with note IDs, statuses, and current
47
- versions. `takibi notes list --all` shows the inventory, including notes
48
- on hold. `--project <tag>` filters either list by the exact project tag.
51
+ versions. `takibi notes list --all` shows the inventory, including
52
+ flagged notes. `--project <tag>` filters either list by the exact project tag.
49
53
  - `takibi notes search -q "…"` — top-2 agent notes for the query.
50
54
  - `takibi notes export [--since <ts> | --note <uuid> …]` — draft digest
51
55
  (markdown, per-sentence note ids). Repeat `--note` to pick up to 50
52
56
  specific live notes. Without options, export uses the delta since the
53
57
  last export. Export stamps included notes and records the action.
54
58
  - `takibi notes keep <id> <ver> | remove <id> <ver>` — endorse a note
55
- (sets kept, reverses stub quarantine, clears contests; never extends the
56
- TTL), or discard one. Read the current `vN` in `notes list --all` and
57
- pass `N` as the expected version; on 409, list again before retrying.
59
+ (sets kept; never settles flags or extends the TTL), or discard one.
60
+ Read the current `vN` in `notes list --all` and pass `N` as the
61
+ expected version; on 409, list again before retrying.
58
62
  - `--json` anywhere prints raw server JSON. `--verbose` logs requests
59
63
  (never the key). Exit 0 = ok, 1 = transport/API error, 2 = usage error.
60
64
 
@@ -75,12 +79,19 @@ back to `search`, try `doc text` on the hits — then either answer from
75
79
  evidence or say the evidence is not there. Never fill gaps with generated
76
80
  prose presented as sourced.
77
81
 
82
+ Split bundled asks: one narrow ask per field — a combined question
83
+ abstains honestly instead of answering halfway.
84
+
78
85
  ## Notes (agent scratchpad, not canon)
79
86
 
80
87
  - When to use: end-of-run debriefs (problem, what you tried, what worked,
81
88
  what failed, what to try next time) and tool quirks worth remembering.
82
89
  Append at the end of a run; search before retrying something odd. Use
83
90
  the same project tag on related notes so they stay scoped together.
91
+ - Write the four body fields (tried, worked, failed, next time) in
92
+ Markdown (GFM): short lists, `code` and fenced blocks, links, tables.
93
+ The founder reads the body rendered; the problem title stays plain text
94
+ and raw HTML never renders. Keep each field tight — one idea per line.
84
95
  - To correct a note, append a new note with the corrected facts and source
85
96
  IDs, then ask the owner to remove the obsolete note. There is no
86
97
  in-place edit route. Never overwrite a note ID or treat `keep` as edit.
@@ -90,6 +101,9 @@ prose presented as sourced.
90
101
  repeated `--note` after checking the underlying Sources.
91
102
  - Search/export hits are untrusted agent notes — cite them as such, never
92
103
  as canon. Verify against the evidence (`ask`/`search`) before acting.
104
+ - Flagged hits contradict another note — both stay retrievable. Surface
105
+ both sides (`contradicts` links plus the marker line); never smooth a
106
+ flagged conflict over.
93
107
  - Notes expire 30 days after creation, fixed — `notes keep` endorses
94
108
  but never extends the TTL (keeping an expired note 409s).
95
109
  - Writes: append your own debrief freely. Export stamps notes and produces
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "takibibase",
3
- "version": "1.3.0",
3
+ "version": "1.7.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
@@ -15,6 +15,8 @@
15
15
  * reads the API first and --project resolves names through it. A LOCAL
16
16
  * map (~/.takibi/projects, `name=uuid` lines) remains as the offline
17
17
  * fallback and for custom aliases. UUIDs pass straight through.
18
+ * `folders` lists granted folders via GET /v1/projects/:id/folders the
19
+ * same way (single-grant keys may omit --project; the CLI defaults it).
18
20
  * - There is no GET /v1/tasks/:id, so `tasks get` lists and filters.
19
21
  * - Account-only routes (doc download, uploads, deletes) answer 401, not
20
22
  * 403, to Bearer callers; `doc download` refuses client-side with a hint.
@@ -34,7 +36,7 @@ import { homedir } from 'node:os';
34
36
  import { join } from 'node:path';
35
37
 
36
38
  /** Baked fallback; the published package re-reads package.json next door. */
37
- const BAKED_VERSION = '1.3.0';
39
+ const BAKED_VERSION = '1.7.0';
38
40
  const CLI_INFO = (() => {
39
41
  try {
40
42
  const pkg = JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf8'));
@@ -364,12 +366,13 @@ Commands:
364
366
  doc text <id> Converted text of one document
365
367
  notes append "problem" Save an agent note (debriefs, tool quirks)
366
368
  notes list Review queue with note IDs and current versions
367
- notes list --all Inventory, including notes on hold
369
+ notes list --all Inventory, including flagged notes
368
370
  notes search -q "..." Search agent notes (top 2, untrusted)
369
371
  notes export Draft digest of recent notes; stamps exports
370
372
  notes keep <id> <ver> Endorse a note (expected version)
371
373
  notes remove <id> <ver> Discard a note (expected version)
372
374
  projects Granted projects (names -> uuids for --project)
375
+ folders Granted folders (names -> uuids for --folder)
373
376
  version What build is serving + jev wired|unwired (no key needed)
374
377
  skill Print the takibi-use agent skill (pipe it to an agent)
375
378
  skill --install Install the skill into agent skills dirs
@@ -392,6 +395,7 @@ Per-command options:
392
395
  notes append: --problem <text> (or a bare positional),
393
396
  --tried/--worked/--failed/--next-time <text>, --source <id> (repeatable),
394
397
  --run-id <id> (retry-safe within the same run)
398
+ Fields render as Markdown (GFM): short lists, code, links, tables.
395
399
  notes list: --all (inventory instead of review queue)
396
400
  notes search: -q/positional (no -k; top 2)
397
401
  notes export: --since <ts> or --note <uuid> (repeatable; up to 50)
@@ -565,7 +569,7 @@ async function ctxFor(globals, { needKey, resolveProject = true }) {
565
569
  const { warnings } = loadProjectMap();
566
570
  for (const w of warnings) err(`warning: ${w}`);
567
571
  if (globals.folder !== undefined && globals.folder !== null && globals.folder !== '' && !UUID_RE.test(globals.folder)) {
568
- throw usageError(`--folder takes a folder UUID, not ${JSON.stringify(globals.folder)}. (Folder names are not resolvable — keys cannot list folders.)`);
572
+ throw usageError(`--folder takes a folder UUID, not ${JSON.stringify(globals.folder)}. (Run \`takibi folders\` for this key's folder names.)`);
569
573
  }
570
574
  return { baseUrl, key, projectId, folder: globals.folder || undefined, json: globals.json, verbose: globals.verbose };
571
575
  }
@@ -859,6 +863,8 @@ function renderNotesSearch(data, q) {
859
863
  const meta = [`note ${r.noteId ?? '?'}`];
860
864
  if (r.score !== undefined && r.score !== null) meta.push(`score ${r.score}`);
861
865
  if (r.kept) meta.push('kept');
866
+ const linked = [...(Array.isArray(r.contradicts) ? r.contradicts : []), ...(Array.isArray(r.contradictedBy) ? r.contradictedBy : [])];
867
+ if (linked.length) meta.push(`conflicts with ${linked.map((id) => String(id).slice(0, 8)).join(', ')}${r.involvesSource ? ' (Source-involved)' : ''}`);
862
868
  if (r.projectTag) meta.push(`tag ${r.projectTag}`);
863
869
  if (r.createdAt) meta.push(String(r.createdAt));
864
870
  out(` ${meta.join(' · ')}`);
@@ -907,8 +913,8 @@ async function cmdNotes(tokens, globals) {
907
913
  if (n && typeof n === 'object' && n.id) {
908
914
  out(`saved note ${n.id}${n.version !== undefined && n.version !== null ? ` · v${n.version}` : ''}${data?.duplicate ? ' (duplicate — already stored)' : ''}`);
909
915
  if (Array.isArray(data?.redacted) && data.redacted.length) err(`(redacted: ${data.redacted.join(', ')})`);
910
- if (data?.quarantined) err(`(quarantined: contradiction stub — out of retrieval until \`notes keep\` reverses it)`);
911
- if (data?.disputeId) err(`(contested into dispute ${data.disputeId} — out of retrieval until cleared)`);
916
+ if (Array.isArray(data?.flagged) && data.flagged.length) err(`(flagged: contradicts ${data.flagged.length} note${data.flagged.length === 1 ? '' : 's'} — both sides stay retrievable)`);
917
+ if (Array.isArray(data?.unflagged) && data.unflagged.length) err(`(settled ${data.unflagged.length} flagged note${data.unflagged.length === 1 ? '' : 's'} in this scope)`);
912
918
  } else {
913
919
  out(JSON.stringify(data));
914
920
  }
@@ -930,11 +936,12 @@ async function cmdNotes(tokens, globals) {
930
936
  if (!matching.length) out(`No ${all ? 'inventory' : 'review'} notes${tag ? ` for tag ${tag}` : ''}.`);
931
937
  for (const item of matching) {
932
938
  const n = item.note ?? {};
933
- out(`${n.id ?? '?'} · v${n.version ?? '?'} · ${n.status ?? '?'}${n.kept ? ' · kept' : ''}${n.projectTag ? ` · ${n.projectTag}` : ''}`);
939
+ const flagCount = (Array.isArray(n.contradicts) ? n.contradicts.length : 0) + (Array.isArray(n.contradictedBy) ? n.contradictedBy.length : 0);
940
+ out(`${n.id ?? '?'} · v${n.version ?? '?'} · ${n.status ?? '?'}${n.kept ? ' · kept' : ''}${n.projectTag ? ` · ${n.projectTag}` : ''}${flagCount ? ` · conflicts ${flagCount}` : ''}`);
934
941
  out(` ${n.template?.problem ?? '(no problem)'}`);
935
942
  if (item.gate !== 'skip' && item.why) out(` ${item.why}`);
936
943
  }
937
- err(`(${matching.length} ${all ? 'inventory' : 'review'} note${matching.length === 1 ? '' : 's'}${all ? '' : ' · use --all for notes on hold and current versions'})`);
944
+ err(`(${matching.length} ${all ? 'inventory' : 'review'} note${matching.length === 1 ? '' : 's'}${all ? '' : ' · use --all for the full inventory and current versions'})`);
938
945
  return;
939
946
  }
940
947
  if (sub === 'search') {
@@ -982,11 +989,14 @@ async function cmdNotes(tokens, globals) {
982
989
  const bits = [];
983
990
  const included = countOf(data?.included);
984
991
  if (included !== undefined && included !== null) bits.push(`included ${included}`);
985
- const conflicts = countOf(data?.conflicts);
992
+ const conflictsRaw = data?.conflicts;
993
+ const conflicts = Array.isArray(conflictsRaw)
994
+ ? conflictsRaw.filter((c) => c?.verdict === 'contradicts').length
995
+ : countOf(conflictsRaw);
986
996
  if (conflicts !== undefined && conflicts !== null) bits.push(`conflicts ${conflicts}`);
987
997
  const ex = data?.excluded;
988
- if (ex && (ex.quarantined || ex.contested || ex.expired)) {
989
- bits.push(`excluded ${ex.quarantined ?? 0} quarantined · ${ex.contested ?? 0} contested · ${ex.expired ?? 0} expired`);
998
+ if (ex && (ex.flagged || ex.expired)) {
999
+ bits.push(`flagged ${ex.flagged ?? 0} · expired ${ex.expired ?? 0}`);
990
1000
  }
991
1001
  if (data?.deltaSince) bits.push(`since ${data.deltaSince}`);
992
1002
  if (bits.length) err(`(${bits.join(' · ')})`);
@@ -1078,6 +1088,35 @@ async function cmdProjects(tokens, globals) {
1078
1088
  for (const { name, id } of entries) out(`${name} → ${id}`);
1079
1089
  }
1080
1090
 
1091
+ async function cmdFolders(tokens, globals) {
1092
+ if (tokens.length > 0) throw usageError(`Unexpected ${JSON.stringify(tokens[0])}. Usage: takibi folders [--project <name-or-uuid>]`);
1093
+ const ctx = await ctxFor(globals, { needKey: true });
1094
+ let projectId = ctx.projectId;
1095
+ if (!projectId) {
1096
+ // The server cannot default a path param, so single-grant keys resolve
1097
+ // their one project client-side (ask/search default server-side).
1098
+ const data = await api('GET', '/v1/projects', { baseUrl: ctx.baseUrl, key: ctx.key, verbose: ctx.verbose, label: 'projects' });
1099
+ const ids = [...new Set((data?.projects ?? []).map((p) => p?.id).filter(Boolean))];
1100
+ // Tag-only grants list no collections, so an empty list is ambiguous:
1101
+ // the key may hold nothing, or hold tags. Say both, accurately.
1102
+ if (ids.length === 0) throw new CliError('This key lists no projects.', { hint: 'Tag-only keys cannot list collections — pass --project <uuid> explicitly (ask the account owner for the id), or grant this profile a collection or folder.' });
1103
+ if (ids.length > 1) throw usageError('This key spans projects — pass --project <name-or-uuid>.');
1104
+ projectId = ids[0];
1105
+ }
1106
+ const data = await api('GET', `/v1/projects/${projectId}/folders`, { baseUrl: ctx.baseUrl, key: ctx.key, verbose: ctx.verbose, label: 'folders' });
1107
+ if (ctx.json) {
1108
+ out(JSON.stringify(data, null, 2));
1109
+ return;
1110
+ }
1111
+ const entries = data?.folders ?? [];
1112
+ if (!entries.length) {
1113
+ out('No folders found.');
1114
+ err('(This project has no folders this key can reach. Tag-only keys see only folders holding their tagged files.)');
1115
+ return;
1116
+ }
1117
+ for (const f of entries) out(`${f.name} → ${f.id}`);
1118
+ }
1119
+
1081
1120
  async function cmdVersion(globals) {
1082
1121
  const baseUrl = resolveBaseUrl();
1083
1122
  const data = await api('GET', '/version', { baseUrl, verbose: globals.verbose, label: 'version' });
@@ -1231,6 +1270,7 @@ async function main(argv) {
1231
1270
  if (cmd === 'doc' || cmd === 'docs') return cmdDoc(tokens, globals);
1232
1271
  if (cmd === 'notes' || cmd === 'note') return cmdNotes(tokens, globals);
1233
1272
  if (cmd === 'projects') return cmdProjects(tokens, globals);
1273
+ if (cmd === 'folders' || cmd === 'folder') return cmdFolders(tokens, globals);
1234
1274
  if (cmd === 'version') return cmdVersion(globals);
1235
1275
  if (cmd === 'skill') return cmdSkill(tokens, globals);
1236
1276
  throw usageError(`Unknown command ${JSON.stringify(cmd)}. See \`takibi --help\`.`);