@kinlab/kin 0.2.13 → 0.2.14
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/README.md +32 -3
- package/bin/kin-mcp.mjs +10 -2
- package/bin/kin.mjs +9 -2
- package/lib/provision.mjs +37 -18
- package/lib/resolve.mjs +67 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -6,12 +6,14 @@ semantic system of record for software work.
|
|
|
6
6
|
```sh
|
|
7
7
|
npm install -g @kinlab/kin
|
|
8
8
|
kin --version
|
|
9
|
+
kin setup --intent agent
|
|
9
10
|
```
|
|
10
11
|
|
|
11
12
|
or zero-install:
|
|
12
13
|
|
|
13
14
|
```sh
|
|
14
15
|
npx -y @kinlab/kin --version
|
|
16
|
+
npx -y @kinlab/kin setup --intent agent --no-interactive
|
|
15
17
|
```
|
|
16
18
|
|
|
17
19
|
## What it does
|
|
@@ -29,6 +31,26 @@ The launcher and the shell installer (`scripts/install.sh`) share the same insta
|
|
|
29
31
|
contract (`$KIN_HOME`, default `~/.kin`): either lane satisfies the other, and neither
|
|
30
32
|
silently downgrades an install the other made.
|
|
31
33
|
|
|
34
|
+
Run `kin setup --intent agent` after provisioning. Setup writes the Kin MCP server into
|
|
35
|
+
detected AI clients with the `agent-default` tool profile, adds the managed bin directory
|
|
36
|
+
to your shell profile, installs the shell/session hook, and records the install ledger used
|
|
37
|
+
by `kin setup status`, `kin doctor --fix`, and `kin setup uninstall`.
|
|
38
|
+
|
|
39
|
+
## Version pinning
|
|
40
|
+
|
|
41
|
+
Every `@kinlab/kin` release pins one managed `kin` release — its own package version.
|
|
42
|
+
Each run compares that pin against whatever is already installed at `$KIN_HOME`:
|
|
43
|
+
|
|
44
|
+
- **installed is older than the pin** — upgrades it automatically (a one-line notice
|
|
45
|
+
on stderr, no prompt, no opt-in required).
|
|
46
|
+
- **installed is newer than the pin** — refuses the downgrade and exits with an
|
|
47
|
+
actionable error instead of running anything; re-run with `KIN_LAUNCHER_ADOPT=1` to
|
|
48
|
+
force it on purpose (e.g. deliberately pinning to an older release).
|
|
49
|
+
- **installed matches the pin** — runs it as-is.
|
|
50
|
+
|
|
51
|
+
`KIN_LAUNCHER_ADOPT=1` always forces a fresh provision of the pinned release, even when
|
|
52
|
+
the installed version already matches.
|
|
53
|
+
|
|
32
54
|
## Environment
|
|
33
55
|
|
|
34
56
|
| Variable | Effect |
|
|
@@ -36,14 +58,21 @@ silently downgrades an install the other made.
|
|
|
36
58
|
| `KIN_HOME` | Root of the managed install (default `~/.kin`). |
|
|
37
59
|
| `KIN_MANAGED_BIN` | Explicit path to a `kin` binary; disables provisioning entirely. |
|
|
38
60
|
| `KIN_NO_PROVISION=1` | Never touch the network; fail loud if no binary is present. |
|
|
39
|
-
| `KIN_LAUNCHER_ADOPT=1` |
|
|
61
|
+
| `KIN_LAUNCHER_ADOPT=1` | Force re-provisioning to the pinned release, including over a newer install that would otherwise be refused as a downgrade. |
|
|
40
62
|
|
|
41
63
|
## MCP setup
|
|
42
64
|
|
|
43
|
-
|
|
65
|
+
`kin setup --intent agent` is the preferred MCP setup path. It writes an absolute path to
|
|
66
|
+
the managed native `kin` binary, so agents do not depend on inheriting your shell `PATH`.
|
|
67
|
+
|
|
68
|
+
Any MCP client can also use the included server manually:
|
|
44
69
|
|
|
45
70
|
```json
|
|
46
|
-
{
|
|
71
|
+
{
|
|
72
|
+
"command": "npx",
|
|
73
|
+
"args": ["-y", "@kinlab/kin", "mcp", "start"],
|
|
74
|
+
"env": { "KIN_MCP_TOOL_PROFILE": "agent-default" }
|
|
75
|
+
}
|
|
47
76
|
```
|
|
48
77
|
|
|
49
78
|
`@kinlab/kin-mcp` remains published for existing configurations; new setups should use
|
package/bin/kin-mcp.mjs
CHANGED
|
@@ -26,14 +26,22 @@ if (argv.includes('--version') || argv.includes('-V')) {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
let binary = null;
|
|
29
|
+
let provisionError = null;
|
|
29
30
|
try {
|
|
30
31
|
binary = await ensureProvisioned();
|
|
31
32
|
} catch (error) {
|
|
32
|
-
|
|
33
|
+
provisionError = error;
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
if (!binary) {
|
|
36
|
-
|
|
37
|
+
// The error message is already complete and actionable (e.g. a declined
|
|
38
|
+
// downgrade names the binary that IS present) — printing the generic
|
|
39
|
+
// not-provisioned message on top of it would contradict it.
|
|
40
|
+
if (provisionError) {
|
|
41
|
+
process.stderr.write(`kin-mcp: provisioning failed: ${provisionError.message}\n`);
|
|
42
|
+
} else {
|
|
43
|
+
process.stderr.write(`${notProvisionedMessage('kin')}\n`);
|
|
44
|
+
}
|
|
37
45
|
process.exit(1);
|
|
38
46
|
}
|
|
39
47
|
|
package/bin/kin.mjs
CHANGED
|
@@ -33,13 +33,20 @@ try {
|
|
|
33
33
|
|
|
34
34
|
if (!binary) {
|
|
35
35
|
if (provisionError) {
|
|
36
|
+
// The error message is already complete and actionable (e.g. a declined
|
|
37
|
+
// downgrade names the binary that IS present) — printing the generic
|
|
38
|
+
// not-provisioned message on top of it would contradict it.
|
|
36
39
|
process.stderr.write(`kin: provisioning failed: ${provisionError.message}\n`);
|
|
40
|
+
if (wantsVersion) {
|
|
41
|
+
process.stdout.write(launcherVersionLine(', provisioning failed'));
|
|
42
|
+
}
|
|
43
|
+
process.exit(1);
|
|
37
44
|
}
|
|
38
45
|
if (wantsVersion) {
|
|
39
46
|
// Honest launcher identity; success only when provisioning was explicitly
|
|
40
47
|
// disabled rather than broken.
|
|
41
|
-
process.stdout.write(launcherVersionLine(
|
|
42
|
-
process.exit(
|
|
48
|
+
process.stdout.write(launcherVersionLine(', not installed'));
|
|
49
|
+
process.exit(0);
|
|
43
50
|
}
|
|
44
51
|
process.stderr.write(`${notProvisionedMessage('kin')}\n`);
|
|
45
52
|
process.exit(1);
|
package/lib/provision.mjs
CHANGED
|
@@ -20,6 +20,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
20
20
|
|
|
21
21
|
import {
|
|
22
22
|
binaryName,
|
|
23
|
+
compareVersions,
|
|
23
24
|
kinHome,
|
|
24
25
|
managedBinaryPath,
|
|
25
26
|
readLauncherStamp,
|
|
@@ -196,16 +197,26 @@ export function probeBinaryVersion(binary, spawnImpl = spawnSync) {
|
|
|
196
197
|
* Ensure the managed `kin` binary matching this launcher's pinned release is
|
|
197
198
|
* present, provisioning (or re-provisioning) when needed. Returns the binary
|
|
198
199
|
* path, or null when nothing usable exists and provisioning is unavailable.
|
|
200
|
+
* Throws when an installed release outranks the pin and the caller has not
|
|
201
|
+
* opted into a downgrade — this function never returns a value while quietly
|
|
202
|
+
* declining to honor the pin.
|
|
199
203
|
*
|
|
200
204
|
* Policy, in order:
|
|
201
205
|
* - $KIN_MANAGED_BIN is an explicit user pin: use it as-is, never provision.
|
|
202
206
|
* - $KIN_NO_PROVISION=1: resolve-only, no network.
|
|
203
|
-
* -
|
|
204
|
-
*
|
|
205
|
-
* -
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
207
|
+
* - $KIN_LAUNCHER_ADOPT=1: always (re-)provision the pinned release,
|
|
208
|
+
* regardless of what is already installed.
|
|
209
|
+
* - nothing installed yet: provision the pinned release.
|
|
210
|
+
* - installed version is known — from the launcher stamp when this package
|
|
211
|
+
* provisioned it (no probe needed), otherwise by probing a foreign
|
|
212
|
+
* install's `--version` once:
|
|
213
|
+
* - installed == pinned: reuse it (a foreign match adopts the stamp).
|
|
214
|
+
* - installed < pinned: the pin is an upgrade — provision it
|
|
215
|
+
* automatically with a one-line notice. Never a silent no-op.
|
|
216
|
+
* - installed > pinned: the pin would downgrade a newer install — fail
|
|
217
|
+
* loud with an actionable message instead of running anything.
|
|
218
|
+
* - installed version unparseable (foreign binary that will not report a
|
|
219
|
+
* version): can't prove direction, so respect it — notice and reuse.
|
|
209
220
|
*/
|
|
210
221
|
export async function ensureProvisioned(opts = {}) {
|
|
211
222
|
const {
|
|
@@ -229,29 +240,37 @@ export async function ensureProvisioned(opts = {}) {
|
|
|
229
240
|
|
|
230
241
|
const doProvision = () => provision(target, { env, platform, arch, fetchImpl, log });
|
|
231
242
|
|
|
243
|
+
if (env.KIN_LAUNCHER_ADOPT === '1') {
|
|
244
|
+
return doProvision();
|
|
245
|
+
}
|
|
232
246
|
if (!existing) {
|
|
233
247
|
return doProvision();
|
|
234
248
|
}
|
|
235
249
|
|
|
250
|
+
const installedPath = managedBinaryPath('kin', env, platform);
|
|
236
251
|
const stamp = readLauncherStamp(env);
|
|
237
|
-
|
|
252
|
+
const installed = stamp ?? probeBinaryVersion(existing, spawnImpl);
|
|
253
|
+
|
|
254
|
+
if (installed === null) {
|
|
255
|
+
log(
|
|
256
|
+
`kin: using existing managed kin (version unknown) at ${installedPath} ` +
|
|
257
|
+
`(this @kinlab/kin pins ${target}; set KIN_LAUNCHER_ADOPT=1 to re-provision).`,
|
|
258
|
+
);
|
|
238
259
|
return existing;
|
|
239
260
|
}
|
|
240
|
-
if (stamp !== null) {
|
|
241
|
-
return doProvision();
|
|
242
|
-
}
|
|
243
261
|
|
|
244
|
-
const
|
|
245
|
-
if (
|
|
246
|
-
writeLauncherStamp(target, env);
|
|
262
|
+
const cmp = compareVersions(target, installed);
|
|
263
|
+
if (cmp === 0) {
|
|
264
|
+
if (stamp === null) writeLauncherStamp(target, env);
|
|
247
265
|
return existing;
|
|
248
266
|
}
|
|
249
|
-
if (
|
|
267
|
+
if (cmp > 0) {
|
|
268
|
+
log(`kin: managed kin ${installed} is older than the pinned ${target}; upgrading automatically.`);
|
|
250
269
|
return doProvision();
|
|
251
270
|
}
|
|
252
|
-
|
|
253
|
-
`
|
|
254
|
-
|
|
271
|
+
throw new Error(
|
|
272
|
+
`refusing to downgrade managed kin ${installed} to the pinned ${target} (at ${installedPath}). ` +
|
|
273
|
+
'This @kinlab/kin would replace a newer managed install with an older one; nothing was run. ' +
|
|
274
|
+
'Set KIN_LAUNCHER_ADOPT=1 to force it if the downgrade is intended.',
|
|
255
275
|
);
|
|
256
|
-
return existing;
|
|
257
276
|
}
|
package/lib/resolve.mjs
CHANGED
|
@@ -33,6 +33,73 @@ export function targetKinVersion() {
|
|
|
33
33
|
return packageVersion();
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Compare two dotted version strings by semver precedence: numeric release
|
|
38
|
+
* segments first, then any `-prerelease` suffix (a release outranks its own
|
|
39
|
+
* prerelease; prerelease identifiers compare numerically when both sides are
|
|
40
|
+
* numeric, else lexically; on an equal shared prefix, fewer identifiers ranks
|
|
41
|
+
* lower). Mirrors the precedence `kin update` applies on the native side
|
|
42
|
+
* (crates/kin-cli/src/commands/update.rs). Returns -1 when `a` is older than
|
|
43
|
+
* `b`, 0 when equal, 1 when `a` is newer than `b`. Non-numeric release
|
|
44
|
+
* segments parse as 0 rather than throwing, so a malformed string degrades to
|
|
45
|
+
* a comparison instead of crashing the launcher.
|
|
46
|
+
*/
|
|
47
|
+
export function compareVersions(a, b) {
|
|
48
|
+
const [aCore, aPre] = splitVersion(a);
|
|
49
|
+
const [bCore, bPre] = splitVersion(b);
|
|
50
|
+
const core = compareNumericParts(aCore, bCore);
|
|
51
|
+
return core !== 0 ? core : comparePrereleaseParts(aPre, bPre);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function splitVersion(version) {
|
|
55
|
+
const trimmed = String(version).trim().replace(/^v/, '');
|
|
56
|
+
const dash = trimmed.indexOf('-');
|
|
57
|
+
const core = dash === -1 ? trimmed : trimmed.slice(0, dash);
|
|
58
|
+
const pre = dash === -1 ? null : trimmed.slice(dash + 1).split('.');
|
|
59
|
+
const nums = core.split('.').map((part) => {
|
|
60
|
+
const n = Number.parseInt(part, 10);
|
|
61
|
+
return Number.isFinite(n) ? n : 0;
|
|
62
|
+
});
|
|
63
|
+
return [nums, pre];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function compareNumericParts(a, b) {
|
|
67
|
+
const len = Math.max(a.length, b.length);
|
|
68
|
+
for (let i = 0; i < len; i += 1) {
|
|
69
|
+
const x = a[i] ?? 0;
|
|
70
|
+
const y = b[i] ?? 0;
|
|
71
|
+
if (x !== y) return x < y ? -1 : 1;
|
|
72
|
+
}
|
|
73
|
+
return 0;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function comparePrereleaseParts(a, b) {
|
|
77
|
+
if (a === null && b === null) return 0;
|
|
78
|
+
if (a === null) return 1; // a released version outranks its own prerelease
|
|
79
|
+
if (b === null) return -1;
|
|
80
|
+
const len = Math.max(a.length, b.length);
|
|
81
|
+
for (let i = 0; i < len; i += 1) {
|
|
82
|
+
if (i >= a.length) return -1; // shared prefix equal, fewer identifiers ranks lower
|
|
83
|
+
if (i >= b.length) return 1;
|
|
84
|
+
const cmp = comparePrereleaseIdentifier(a[i], b[i]);
|
|
85
|
+
if (cmp !== 0) return cmp;
|
|
86
|
+
}
|
|
87
|
+
return 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function comparePrereleaseIdentifier(a, b) {
|
|
91
|
+
const aNumeric = /^\d+$/.test(a);
|
|
92
|
+
const bNumeric = /^\d+$/.test(b);
|
|
93
|
+
if (aNumeric && bNumeric) {
|
|
94
|
+
const x = Number.parseInt(a, 10);
|
|
95
|
+
const y = Number.parseInt(b, 10);
|
|
96
|
+
return x === y ? 0 : x < y ? -1 : 1;
|
|
97
|
+
}
|
|
98
|
+
if (aNumeric) return -1; // numeric identifiers rank below alphanumeric
|
|
99
|
+
if (bNumeric) return 1;
|
|
100
|
+
return a === b ? 0 : a < b ? -1 : 1;
|
|
101
|
+
}
|
|
102
|
+
|
|
36
103
|
/**
|
|
37
104
|
* Map a Node platform+arch pair to the Rust target triple of the managed
|
|
38
105
|
* binary. Pure and injectable so it is testable for every supported host.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kinlab/kin",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "Canonical installer and launcher for Kin — provisions and runs the managed kin + kin-daemon release. MCP is one included mode (kin mcp start).",
|
|
3
|
+
"version": "0.2.14",
|
|
4
|
+
"description": "Canonical installer and launcher for Kin, the semantic system of record for software work — provisions and runs the managed kin + kin-daemon release. MCP is one included mode (kin mcp start).",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|