takibibase 1.0.2 → 1.0.3
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 +4 -0
- package/package.json +2 -2
- package/takibi.mjs +82 -5
package/SKILL.md
CHANGED
|
@@ -23,6 +23,10 @@ error hints.
|
|
|
23
23
|
takes a name or a UUID; single-grant keys may omit it.
|
|
24
24
|
`takibi projects --add <name> <uuid>` keeps local aliases for offline use.
|
|
25
25
|
- First probe: `takibi version` (needs no key; shows build + `jev` status).
|
|
26
|
+
- Update notice: the CLI polls the npm registry once a day and nudges on
|
|
27
|
+
stderr when behind (never on `--json`). Silence it with
|
|
28
|
+
`TAKIBI_NO_UPDATE_CHECK=1`; point it at a mirror with
|
|
29
|
+
`TAKIBI_REGISTRY_URL`.
|
|
26
30
|
|
|
27
31
|
## Commands
|
|
28
32
|
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "takibibase",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Thin CLI for the Takibi API: ask, search, tasks, docs. Single file, zero dependencies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"takibi": "
|
|
7
|
+
"takibi": "takibi.mjs"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"takibi.mjs",
|
package/takibi.mjs
CHANGED
|
@@ -22,21 +22,24 @@
|
|
|
22
22
|
* Exit codes: 0 ok (honest abstains included), 1 transport/API error,
|
|
23
23
|
* 2 usage error.
|
|
24
24
|
*/
|
|
25
|
-
import { appendFileSync, existsSync, mkdirSync, readFileSync, writeSync } from 'node:fs';
|
|
25
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync, writeSync } from 'node:fs';
|
|
26
26
|
import { homedir } from 'node:os';
|
|
27
27
|
import { join } from 'node:path';
|
|
28
28
|
|
|
29
29
|
/** Baked fallback; the published package re-reads package.json next door. */
|
|
30
|
-
const BAKED_VERSION = '1.0.
|
|
31
|
-
const
|
|
30
|
+
const BAKED_VERSION = '1.0.3';
|
|
31
|
+
const CLI_INFO = (() => {
|
|
32
32
|
try {
|
|
33
33
|
const pkg = JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf8'));
|
|
34
|
-
if (pkg.name === 'takibibase' && typeof pkg.version === 'string' && pkg.version.length > 0)
|
|
34
|
+
if (pkg.name === 'takibibase' && typeof pkg.version === 'string' && pkg.version.length > 0) {
|
|
35
|
+
return { version: pkg.version, singleFile: false };
|
|
36
|
+
}
|
|
35
37
|
} catch {
|
|
36
38
|
// Single-file download: no package.json travels with takibi.mjs.
|
|
37
39
|
}
|
|
38
|
-
return BAKED_VERSION;
|
|
40
|
+
return { version: BAKED_VERSION, singleFile: true };
|
|
39
41
|
})();
|
|
42
|
+
const CLI_VERSION = CLI_INFO.version;
|
|
40
43
|
const DEFAULT_BASE_URL = 'http://localhost:3849';
|
|
41
44
|
const REQUEST_TIMEOUT_MS = 90_000;
|
|
42
45
|
const USER_AGENT = `takibibase/${CLI_VERSION}`;
|
|
@@ -66,6 +69,78 @@ const usageError = (message) => new CliError(message, { exitCode: 2 });
|
|
|
66
69
|
|
|
67
70
|
const takibiDir = () => join(homedir(), '.takibi');
|
|
68
71
|
|
|
72
|
+
const UPDATE_CHECK_TTL_MS = 86_400_000;
|
|
73
|
+
const REGISTRY_URL = (process.env.TAKIBI_REGISTRY_URL ?? '').trim().replace(/\/+$/, '') || 'https://registry.npmjs.org';
|
|
74
|
+
|
|
75
|
+
/** Numeric dotted-version compare; false on anything unparseable. */
|
|
76
|
+
function versionBehind(current, latest) {
|
|
77
|
+
const parts = (v) => String(v ?? '').trim().split('.');
|
|
78
|
+
const c = parts(current);
|
|
79
|
+
const l = parts(latest);
|
|
80
|
+
for (let i = 0; i < Math.max(c.length, l.length); i++) {
|
|
81
|
+
const a = i < c.length ? Number(c[i]) : 0;
|
|
82
|
+
const b = i < l.length ? Number(l[i]) : 0;
|
|
83
|
+
if (!Number.isInteger(a) || !Number.isInteger(b)) return false;
|
|
84
|
+
if (b > a) return true;
|
|
85
|
+
if (a > b) return false;
|
|
86
|
+
}
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Once-a-day latest-version poll. Stderr only (stdout stays clean),
|
|
91
|
+
* best-effort, never fails the command. Failures are cached too, so an
|
|
92
|
+
* unreachable registry costs one slow run a day, not every invocation.
|
|
93
|
+
* Skipped for --json (errors there are a single JSON object), in CI, and
|
|
94
|
+
* with TAKIBI_NO_UPDATE_CHECK=1. */
|
|
95
|
+
async function maybeNotifyUpdate({ json = false } = {}) {
|
|
96
|
+
try {
|
|
97
|
+
if (json || process.env.CI || process.env.TAKIBI_NO_UPDATE_CHECK === '1') return;
|
|
98
|
+
const path = join(takibiDir(), 'update-check');
|
|
99
|
+
let cached = null;
|
|
100
|
+
try {
|
|
101
|
+
cached = JSON.parse(readFileSync(path, 'utf8'));
|
|
102
|
+
} catch {
|
|
103
|
+
// First run: no cache yet.
|
|
104
|
+
}
|
|
105
|
+
const fresh = cached && typeof cached.at === 'number' && Date.now() - cached.at < UPDATE_CHECK_TTL_MS;
|
|
106
|
+
let latest = null;
|
|
107
|
+
if (fresh) {
|
|
108
|
+
if (typeof cached.latest !== 'string' || !cached.latest) return;
|
|
109
|
+
latest = cached.latest;
|
|
110
|
+
} else {
|
|
111
|
+
try {
|
|
112
|
+
const res = await fetch(`${REGISTRY_URL}/takibibase/latest`, { signal: AbortSignal.timeout(1200) });
|
|
113
|
+
const v = res.ok ? (await res.json())?.version : null;
|
|
114
|
+
if (typeof v === 'string' && v) latest = v;
|
|
115
|
+
} catch {
|
|
116
|
+
// Offline: fall through below.
|
|
117
|
+
}
|
|
118
|
+
if (!latest && cached && typeof cached.latest === 'string' && cached.latest) {
|
|
119
|
+
// Failed refresh keeps the last known version: a timeout or 5xx must
|
|
120
|
+
// not delete a nudge we already knew about. The fresh timestamp still
|
|
121
|
+
// stops us hammering the registry.
|
|
122
|
+
latest = cached.latest;
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
mkdirSync(takibiDir(), { recursive: true });
|
|
126
|
+
writeFileSync(path, JSON.stringify({ at: Date.now(), latest }));
|
|
127
|
+
} catch {
|
|
128
|
+
// Read-only home: notifying still works, caching just retries daily.
|
|
129
|
+
}
|
|
130
|
+
if (!latest) return;
|
|
131
|
+
}
|
|
132
|
+
if (!versionBehind(CLI_VERSION, latest)) return;
|
|
133
|
+
err(`takibi: ${latest} is available (you have ${CLI_VERSION}).`);
|
|
134
|
+
err(
|
|
135
|
+
CLI_INFO.singleFile
|
|
136
|
+
? 'hint: download the latest takibi.mjs from https://takibibase.com/downloads/takibi.mjs'
|
|
137
|
+
: 'hint: `npm i -g takibibase@latest`, or `npx takibibase@latest` for one-shot runs.',
|
|
138
|
+
);
|
|
139
|
+
} catch {
|
|
140
|
+
// Offline, broken cache, slow registry: stay silent, run the command.
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
69
144
|
/** First non-blank, non-comment line of a file, or null when missing. */
|
|
70
145
|
function firstLine(path) {
|
|
71
146
|
if (!existsSync(path)) return null;
|
|
@@ -753,8 +828,10 @@ async function main(argv) {
|
|
|
753
828
|
const { globals, rest } = parseArgv(argv);
|
|
754
829
|
if (globals.version) {
|
|
755
830
|
out(`takibi ${CLI_VERSION}`);
|
|
831
|
+
await maybeNotifyUpdate({ json: globals.json });
|
|
756
832
|
return;
|
|
757
833
|
}
|
|
834
|
+
await maybeNotifyUpdate({ json: globals.json });
|
|
758
835
|
const [cmd, ...tokens] = rest;
|
|
759
836
|
if (globals.help || !cmd) {
|
|
760
837
|
out(HELP);
|