@kinlab/kin 0.2.13
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 +50 -0
- package/bin/kin-mcp.mjs +48 -0
- package/bin/kin.mjs +57 -0
- package/lib/provision.mjs +257 -0
- package/lib/resolve.mjs +138 -0
- package/package.json +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# @kinlab/kin
|
|
2
|
+
|
|
3
|
+
The canonical npm install surface for [Kin](https://github.com/firelock-ai/kin), the
|
|
4
|
+
semantic system of record for software work.
|
|
5
|
+
|
|
6
|
+
```sh
|
|
7
|
+
npm install -g @kinlab/kin
|
|
8
|
+
kin --version
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
or zero-install:
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
npx -y @kinlab/kin --version
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## What it does
|
|
18
|
+
|
|
19
|
+
`@kinlab/kin` ships two thin launchers, not a JavaScript reimplementation:
|
|
20
|
+
|
|
21
|
+
- **`kin`** — provisions the managed native `kin` + `kin-daemon` release for your
|
|
22
|
+
platform on first run (downloaded from the matching GitHub release, verified against
|
|
23
|
+
its published SHA-256), installs it under `~/.kin/bin`, then passes every invocation
|
|
24
|
+
straight through to the real binary and mirrors its exit.
|
|
25
|
+
- **`kin-mcp`** — compatibility entrypoint that starts Kin's MCP server
|
|
26
|
+
(`kin mcp start`). MCP is one included mode of Kin, not a separate product.
|
|
27
|
+
|
|
28
|
+
The launcher and the shell installer (`scripts/install.sh`) share the same install
|
|
29
|
+
contract (`$KIN_HOME`, default `~/.kin`): either lane satisfies the other, and neither
|
|
30
|
+
silently downgrades an install the other made.
|
|
31
|
+
|
|
32
|
+
## Environment
|
|
33
|
+
|
|
34
|
+
| Variable | Effect |
|
|
35
|
+
| --- | --- |
|
|
36
|
+
| `KIN_HOME` | Root of the managed install (default `~/.kin`). |
|
|
37
|
+
| `KIN_MANAGED_BIN` | Explicit path to a `kin` binary; disables provisioning entirely. |
|
|
38
|
+
| `KIN_NO_PROVISION=1` | Never touch the network; fail loud if no binary is present. |
|
|
39
|
+
| `KIN_LAUNCHER_ADOPT=1` | Allow re-provisioning over a version-skewed non-npm install. |
|
|
40
|
+
|
|
41
|
+
## MCP setup
|
|
42
|
+
|
|
43
|
+
Any MCP client can use the included server:
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{ "command": "npx", "args": ["-y", "@kinlab/kin", "mcp", "start"] }
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`@kinlab/kin-mcp` remains published for existing configurations; new setups should use
|
|
50
|
+
this package.
|
package/bin/kin-mcp.mjs
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
// Copyright 2026 Firelock, LLC
|
|
4
|
+
|
|
5
|
+
// `kin-mcp` — compatibility launcher for Kin's MCP server.
|
|
6
|
+
//
|
|
7
|
+
// MCP is one included mode of Kin, not a separate product. This bin exists so
|
|
8
|
+
// existing `@kinlab/kin-mcp` users keep a working `kin-mcp` entrypoint; it
|
|
9
|
+
// provisions the managed release like the `kin` launcher and starts
|
|
10
|
+
// `kin mcp start`, forwarding any extra argv. Progress and failures go to
|
|
11
|
+
// stderr only — stdout belongs to the MCP stdio protocol.
|
|
12
|
+
|
|
13
|
+
import os from 'node:os';
|
|
14
|
+
import { spawnSync } from 'node:child_process';
|
|
15
|
+
|
|
16
|
+
import { packageVersion, targetKinVersion, notProvisionedMessage } from '../lib/resolve.mjs';
|
|
17
|
+
import { ensureProvisioned } from '../lib/provision.mjs';
|
|
18
|
+
|
|
19
|
+
const argv = process.argv.slice(2);
|
|
20
|
+
|
|
21
|
+
if (argv.includes('--version') || argv.includes('-V')) {
|
|
22
|
+
process.stdout.write(
|
|
23
|
+
`@kinlab/kin ${packageVersion()} (kin-mcp compatibility launcher; provisions kin ${targetKinVersion()})\n`,
|
|
24
|
+
);
|
|
25
|
+
process.exit(0);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let binary = null;
|
|
29
|
+
try {
|
|
30
|
+
binary = await ensureProvisioned();
|
|
31
|
+
} catch (error) {
|
|
32
|
+
process.stderr.write(`kin-mcp: provisioning failed: ${error.message}\n`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!binary) {
|
|
36
|
+
process.stderr.write(`${notProvisionedMessage('kin')}\n`);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const result = spawnSync(binary, ['mcp', 'start', ...argv], { stdio: 'inherit' });
|
|
41
|
+
if (result.error) {
|
|
42
|
+
process.stderr.write(`kin-mcp: failed to launch managed binary: ${result.error.message}\n`);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
if (result.signal) {
|
|
46
|
+
process.exit(128 + (os.constants.signals[result.signal] ?? 0));
|
|
47
|
+
}
|
|
48
|
+
process.exit(result.status ?? 0);
|
package/bin/kin.mjs
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
// Copyright 2026 Firelock, LLC
|
|
4
|
+
|
|
5
|
+
// `kin` — canonical launcher/passthrough for the Kin CLI.
|
|
6
|
+
//
|
|
7
|
+
// Provisions the managed native `kin` (+ `kin-daemon`) release on first run,
|
|
8
|
+
// then forwards argv straight to it and mirrors its exit. `--version` therefore
|
|
9
|
+
// proves provisioning end-to-end: it answers with the real binary's version.
|
|
10
|
+
// When provisioning is unavailable (offline, disabled), it fails loud with an
|
|
11
|
+
// actionable message — it never pretends to run.
|
|
12
|
+
|
|
13
|
+
import os from 'node:os';
|
|
14
|
+
import { spawnSync } from 'node:child_process';
|
|
15
|
+
|
|
16
|
+
import { packageVersion, targetKinVersion, notProvisionedMessage } from '../lib/resolve.mjs';
|
|
17
|
+
import { ensureProvisioned } from '../lib/provision.mjs';
|
|
18
|
+
|
|
19
|
+
const argv = process.argv.slice(2);
|
|
20
|
+
const wantsVersion = argv.includes('--version') || argv.includes('-V');
|
|
21
|
+
|
|
22
|
+
function launcherVersionLine(note) {
|
|
23
|
+
return `@kinlab/kin ${packageVersion()} (launcher; provisions kin ${targetKinVersion()}${note})\n`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let binary = null;
|
|
27
|
+
let provisionError = null;
|
|
28
|
+
try {
|
|
29
|
+
binary = await ensureProvisioned();
|
|
30
|
+
} catch (error) {
|
|
31
|
+
provisionError = error;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!binary) {
|
|
35
|
+
if (provisionError) {
|
|
36
|
+
process.stderr.write(`kin: provisioning failed: ${provisionError.message}\n`);
|
|
37
|
+
}
|
|
38
|
+
if (wantsVersion) {
|
|
39
|
+
// Honest launcher identity; success only when provisioning was explicitly
|
|
40
|
+
// disabled rather than broken.
|
|
41
|
+
process.stdout.write(launcherVersionLine(provisionError ? ', provisioning failed' : ', not installed'));
|
|
42
|
+
process.exit(provisionError ? 1 : 0);
|
|
43
|
+
}
|
|
44
|
+
process.stderr.write(`${notProvisionedMessage('kin')}\n`);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const result = spawnSync(binary, argv, { stdio: 'inherit' });
|
|
49
|
+
if (result.error) {
|
|
50
|
+
process.stderr.write(`kin: failed to launch managed binary: ${result.error.message}\n`);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
if (result.signal) {
|
|
54
|
+
// Mirror signal deaths honestly instead of reporting success.
|
|
55
|
+
process.exit(128 + (os.constants.signals[result.signal] ?? 0));
|
|
56
|
+
}
|
|
57
|
+
process.exit(result.status ?? 0);
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright 2026 Firelock, LLC
|
|
3
|
+
|
|
4
|
+
// Binary provisioning for the @kinlab/kin launcher.
|
|
5
|
+
//
|
|
6
|
+
// Downloads the platform archive of the pinned Kin release from GitHub
|
|
7
|
+
// Releases, verifies it against the per-artifact SHA-256 published next to it,
|
|
8
|
+
// and installs the binaries into <kinHome>/bin — the same directory and the
|
|
9
|
+
// same refusal semantics as scripts/install.sh, so the two install lanes are
|
|
10
|
+
// interchangeable. kin-daemon is mandatory (status/search/MCP require it): a
|
|
11
|
+
// daemon-less archive aborts before anything is moved rather than leaving a
|
|
12
|
+
// half-installed environment. Network, spawning, and the environment are all
|
|
13
|
+
// injectable so every decision here is unit testable offline.
|
|
14
|
+
|
|
15
|
+
import crypto from 'node:crypto';
|
|
16
|
+
import fs from 'node:fs';
|
|
17
|
+
import os from 'node:os';
|
|
18
|
+
import path from 'node:path';
|
|
19
|
+
import { spawnSync } from 'node:child_process';
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
binaryName,
|
|
23
|
+
kinHome,
|
|
24
|
+
managedBinaryPath,
|
|
25
|
+
readLauncherStamp,
|
|
26
|
+
resolveManagedBinary,
|
|
27
|
+
targetKinVersion,
|
|
28
|
+
writeLauncherStamp,
|
|
29
|
+
} from './resolve.mjs';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Release-artifact file name for a host. Follows the release workflow's
|
|
33
|
+
* naming (kin-{macos|linux|windows}-{x86_64|aarch64}); Windows ships as .zip.
|
|
34
|
+
* There is no native windows-aarch64 artifact — fail honestly rather than
|
|
35
|
+
* fabricate a URL (Windows-on-ARM can run the x64 build under emulation).
|
|
36
|
+
*/
|
|
37
|
+
export function artifactName(platform = process.platform, arch = process.arch) {
|
|
38
|
+
const artifacts = {
|
|
39
|
+
'darwin:arm64': 'kin-macos-aarch64.tar.gz',
|
|
40
|
+
'darwin:x64': 'kin-macos-x86_64.tar.gz',
|
|
41
|
+
'linux:arm64': 'kin-linux-aarch64.tar.gz',
|
|
42
|
+
'linux:x64': 'kin-linux-x86_64.tar.gz',
|
|
43
|
+
'win32:x64': 'kin-windows-x86_64.zip',
|
|
44
|
+
};
|
|
45
|
+
const key = `${platform}:${arch}`;
|
|
46
|
+
const file = artifacts[key];
|
|
47
|
+
if (!file) {
|
|
48
|
+
const supported = Object.keys(artifacts).join(', ');
|
|
49
|
+
throw new Error(
|
|
50
|
+
key === 'win32:arm64'
|
|
51
|
+
? 'no native windows-aarch64 Kin release artifact exists; use an x64 Node ' +
|
|
52
|
+
'under emulation (the x86_64 build runs on Windows-on-ARM), or build from source.'
|
|
53
|
+
: `no Kin release artifact for "${key}"; supported: ${supported}`,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
return file;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Download URL of a file attached to the v<version> GitHub release. */
|
|
60
|
+
export function releaseDownloadUrl(version, file) {
|
|
61
|
+
return `https://github.com/firelock-ai/kin/releases/download/v${version}/${file}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Extract the hex digest from a `shasum -a 256` style checksum file
|
|
66
|
+
* ("<hex> <name>"). Refuses anything that does not look like SHA-256.
|
|
67
|
+
*/
|
|
68
|
+
export function parseSha256File(text) {
|
|
69
|
+
const token = String(text).trim().split(/\s+/)[0] ?? '';
|
|
70
|
+
if (!/^[0-9a-f]{64}$/i.test(token)) {
|
|
71
|
+
throw new Error(`malformed .sha256 file (expected a 64-hex digest, got "${token.slice(0, 80)}")`);
|
|
72
|
+
}
|
|
73
|
+
return token.toLowerCase();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** SHA-256 hex digest of a buffer. */
|
|
77
|
+
export function sha256Hex(buf) {
|
|
78
|
+
return crypto.createHash('sha256').update(buf).digest('hex');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function fetchBuffer(url, fetchImpl) {
|
|
82
|
+
const res = await fetchImpl(url);
|
|
83
|
+
if (!res.ok) {
|
|
84
|
+
throw new Error(`download failed (${res.status}) for ${url}`);
|
|
85
|
+
}
|
|
86
|
+
return Buffer.from(await res.arrayBuffer());
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Locate the extraction root: release archives contain a kin-* subdirectory;
|
|
91
|
+
* tolerate a flat archive the way install.sh does.
|
|
92
|
+
*/
|
|
93
|
+
function extractionRoot(tmp) {
|
|
94
|
+
const sub = fs
|
|
95
|
+
.readdirSync(tmp, { withFileTypes: true })
|
|
96
|
+
.find((e) => e.isDirectory() && e.name.startsWith('kin-'));
|
|
97
|
+
return sub ? path.join(tmp, sub.name) : tmp;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Replace-then-move a binary into place (never overwrite in place: replacing
|
|
101
|
+
* a running executable's inode causes launch deadlocks on macOS). */
|
|
102
|
+
function installFile(src, dest, mode) {
|
|
103
|
+
fs.rmSync(dest, { force: true });
|
|
104
|
+
fs.copyFileSync(src, dest);
|
|
105
|
+
fs.chmodSync(dest, mode);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Download, verify, and install the pinned Kin release. Returns the installed
|
|
110
|
+
* managed `kin` path. Mirrors scripts/install.sh: kin + kin-daemon are
|
|
111
|
+
* mandatory, kin-vfs and the projection shim library are optional extras.
|
|
112
|
+
*/
|
|
113
|
+
export async function provision(version, opts = {}) {
|
|
114
|
+
const {
|
|
115
|
+
env = process.env,
|
|
116
|
+
platform = process.platform,
|
|
117
|
+
arch = process.arch,
|
|
118
|
+
fetchImpl = fetch,
|
|
119
|
+
log = (line) => process.stderr.write(`${line}\n`),
|
|
120
|
+
} = opts;
|
|
121
|
+
|
|
122
|
+
const file = artifactName(platform, arch);
|
|
123
|
+
const url = releaseDownloadUrl(version, file);
|
|
124
|
+
log(`kin: provisioning managed kin ${version} (${file})...`);
|
|
125
|
+
|
|
126
|
+
const [archive, shaText] = await Promise.all([
|
|
127
|
+
fetchBuffer(url, fetchImpl),
|
|
128
|
+
fetchBuffer(`${url}.sha256`, fetchImpl),
|
|
129
|
+
]);
|
|
130
|
+
const expected = parseSha256File(shaText.toString('utf8'));
|
|
131
|
+
const actual = sha256Hex(archive);
|
|
132
|
+
if (actual !== expected) {
|
|
133
|
+
throw new Error(
|
|
134
|
+
`SHA-256 mismatch for ${file}: expected ${expected}, got ${actual}. ` +
|
|
135
|
+
'The download may be corrupted or tampered with; refusing to install.',
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'kinlab-kin-'));
|
|
140
|
+
try {
|
|
141
|
+
const archivePath = path.join(tmp, file);
|
|
142
|
+
fs.writeFileSync(archivePath, archive);
|
|
143
|
+
// bsdtar reads both .tar.gz and .zip with -xf, on macOS, Linux, and
|
|
144
|
+
// Windows 10+ (tar.exe ships in System32).
|
|
145
|
+
const tar = spawnSync('tar', ['-xf', archivePath, '-C', tmp], { encoding: 'utf8' });
|
|
146
|
+
if (tar.status !== 0) {
|
|
147
|
+
throw new Error(`archive extraction failed: ${tar.stderr || tar.error?.message || 'tar exited non-zero'}`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const root = extractionRoot(tmp);
|
|
151
|
+
const kinSrc = path.join(root, binaryName('kin', platform));
|
|
152
|
+
const daemonSrc = path.join(root, binaryName('kin-daemon', platform));
|
|
153
|
+
if (!fs.existsSync(kinSrc) || !fs.existsSync(daemonSrc)) {
|
|
154
|
+
throw new Error(
|
|
155
|
+
'kin-daemon (or kin) missing from the downloaded archive. kin status/search and ' +
|
|
156
|
+
'the MCP server require the daemon; refusing a daemon-less install.',
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const binDir = path.join(kinHome(env), 'bin');
|
|
161
|
+
const libDir = path.join(kinHome(env), 'lib');
|
|
162
|
+
fs.mkdirSync(binDir, { recursive: true });
|
|
163
|
+
|
|
164
|
+
installFile(kinSrc, path.join(binDir, binaryName('kin', platform)), 0o755);
|
|
165
|
+
installFile(daemonSrc, path.join(binDir, binaryName('kin-daemon', platform)), 0o755);
|
|
166
|
+
|
|
167
|
+
const vfsSrc = path.join(root, binaryName('kin-vfs', platform));
|
|
168
|
+
if (fs.existsSync(vfsSrc)) {
|
|
169
|
+
installFile(vfsSrc, path.join(binDir, binaryName('kin-vfs', platform)), 0o755);
|
|
170
|
+
}
|
|
171
|
+
for (const lib of ['libkin_vfs_shim.so', 'libkin_vfs_shim.dylib']) {
|
|
172
|
+
const libSrc = path.join(root, lib);
|
|
173
|
+
if (fs.existsSync(libSrc)) {
|
|
174
|
+
fs.mkdirSync(libDir, { recursive: true });
|
|
175
|
+
installFile(libSrc, path.join(libDir, lib), 0o644);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
writeLauncherStamp(version, env);
|
|
180
|
+
log(`kin: managed kin ${version} installed at ${binDir}`);
|
|
181
|
+
return path.join(binDir, binaryName('kin', platform));
|
|
182
|
+
} finally {
|
|
183
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** First line of `<binary> --version`, or null if it cannot be run. */
|
|
188
|
+
export function probeBinaryVersion(binary, spawnImpl = spawnSync) {
|
|
189
|
+
const res = spawnImpl(binary, ['--version'], { encoding: 'utf8' });
|
|
190
|
+
if (res.error || res.status !== 0) return null;
|
|
191
|
+
const m = String(res.stdout).match(/^kin\s+(\S+)/);
|
|
192
|
+
return m ? m[1] : null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Ensure the managed `kin` binary matching this launcher's pinned release is
|
|
197
|
+
* present, provisioning (or re-provisioning) when needed. Returns the binary
|
|
198
|
+
* path, or null when nothing usable exists and provisioning is unavailable.
|
|
199
|
+
*
|
|
200
|
+
* Policy, in order:
|
|
201
|
+
* - $KIN_MANAGED_BIN is an explicit user pin: use it as-is, never provision.
|
|
202
|
+
* - $KIN_NO_PROVISION=1: resolve-only, no network.
|
|
203
|
+
* - launcher stamp == target and the binary exists: run it (no probe cost).
|
|
204
|
+
* - stamp differs or binary missing: provision the pinned release.
|
|
205
|
+
* - no stamp but a binary exists (foreign install, e.g. install.sh): probe
|
|
206
|
+
* its version; adopt on match. On mismatch respect the foreign install —
|
|
207
|
+
* run it with a one-line notice — unless $KIN_LAUNCHER_ADOPT=1 opts into
|
|
208
|
+
* re-provisioning. Never silently downgrade someone else's install.
|
|
209
|
+
*/
|
|
210
|
+
export async function ensureProvisioned(opts = {}) {
|
|
211
|
+
const {
|
|
212
|
+
env = process.env,
|
|
213
|
+
platform = process.platform,
|
|
214
|
+
arch = process.arch,
|
|
215
|
+
fetchImpl = fetch,
|
|
216
|
+
log = (line) => process.stderr.write(`${line}\n`),
|
|
217
|
+
spawnImpl = spawnSync,
|
|
218
|
+
} = opts;
|
|
219
|
+
|
|
220
|
+
const target = targetKinVersion();
|
|
221
|
+
|
|
222
|
+
if (env.KIN_MANAGED_BIN && env.KIN_MANAGED_BIN.trim()) {
|
|
223
|
+
return resolveManagedBinary('kin', env, platform);
|
|
224
|
+
}
|
|
225
|
+
const existing = resolveManagedBinary('kin', env, platform);
|
|
226
|
+
if (env.KIN_NO_PROVISION === '1') {
|
|
227
|
+
return existing;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const doProvision = () => provision(target, { env, platform, arch, fetchImpl, log });
|
|
231
|
+
|
|
232
|
+
if (!existing) {
|
|
233
|
+
return doProvision();
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const stamp = readLauncherStamp(env);
|
|
237
|
+
if (stamp === target) {
|
|
238
|
+
return existing;
|
|
239
|
+
}
|
|
240
|
+
if (stamp !== null) {
|
|
241
|
+
return doProvision();
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const probed = probeBinaryVersion(existing, spawnImpl);
|
|
245
|
+
if (probed === target) {
|
|
246
|
+
writeLauncherStamp(target, env);
|
|
247
|
+
return existing;
|
|
248
|
+
}
|
|
249
|
+
if (env.KIN_LAUNCHER_ADOPT === '1') {
|
|
250
|
+
return doProvision();
|
|
251
|
+
}
|
|
252
|
+
log(
|
|
253
|
+
`kin: using existing managed kin ${probed ?? 'unknown'} at ${managedBinaryPath('kin', env, platform)} ` +
|
|
254
|
+
`(this @kinlab/kin pins ${target}; set KIN_LAUNCHER_ADOPT=1 to re-provision).`,
|
|
255
|
+
);
|
|
256
|
+
return existing;
|
|
257
|
+
}
|
package/lib/resolve.mjs
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright 2026 Firelock, LLC
|
|
3
|
+
|
|
4
|
+
// Managed-binary resolution for the @kinlab/kin launcher.
|
|
5
|
+
//
|
|
6
|
+
// This package is the canonical install surface for Kin: `npm i -g @kinlab/kin`
|
|
7
|
+
// gives you the `kin` and `kin-mcp` bins. The bins are thin launchers over a
|
|
8
|
+
// *managed* native binary (the real `kin` + `kin-daemon` release). This module
|
|
9
|
+
// owns the pure, side-effect-free part of that story — computing the platform
|
|
10
|
+
// target, where the managed binary is expected to live, and which release the
|
|
11
|
+
// launcher pins — so it is unit testable without spawning or touching the
|
|
12
|
+
// network. Downloading and installing live in ./provision.mjs.
|
|
13
|
+
|
|
14
|
+
import fs from 'node:fs';
|
|
15
|
+
import os from 'node:os';
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
import { fileURLToPath } from 'node:url';
|
|
18
|
+
|
|
19
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
20
|
+
|
|
21
|
+
/** This package's own version, read from its package.json (single source). */
|
|
22
|
+
export function packageVersion() {
|
|
23
|
+
const manifest = JSON.parse(fs.readFileSync(path.join(HERE, '..', 'package.json'), 'utf8'));
|
|
24
|
+
return manifest.version;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The Kin release this launcher provisions. It tracks the package version: the
|
|
29
|
+
* canonical package is versioned in lockstep with the `kin` + `kin-daemon`
|
|
30
|
+
* release it installs (the release workflow asserts package version == tag).
|
|
31
|
+
*/
|
|
32
|
+
export function targetKinVersion() {
|
|
33
|
+
return packageVersion();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Map a Node platform+arch pair to the Rust target triple of the managed
|
|
38
|
+
* binary. Pure and injectable so it is testable for every supported host.
|
|
39
|
+
*/
|
|
40
|
+
export function platformTarget(platform = process.platform, arch = process.arch) {
|
|
41
|
+
const targets = {
|
|
42
|
+
'darwin:arm64': 'aarch64-apple-darwin',
|
|
43
|
+
'darwin:x64': 'x86_64-apple-darwin',
|
|
44
|
+
'linux:arm64': 'aarch64-unknown-linux-gnu',
|
|
45
|
+
'linux:x64': 'x86_64-unknown-linux-gnu',
|
|
46
|
+
'win32:x64': 'x86_64-pc-windows-msvc',
|
|
47
|
+
'win32:arm64': 'aarch64-pc-windows-msvc',
|
|
48
|
+
};
|
|
49
|
+
const key = `${platform}:${arch}`;
|
|
50
|
+
const triple = targets[key];
|
|
51
|
+
if (!triple) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
`unsupported platform "${key}" for @kinlab/kin; supported: ${Object.keys(targets).join(', ')}`,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
return triple;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Executable file name for a bin on the host platform (adds .exe on Windows). */
|
|
60
|
+
export function binaryName(name, platform = process.platform) {
|
|
61
|
+
return platform === 'win32' ? `${name}.exe` : name;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Root of Kin's managed install, honoring $KIN_HOME (default ~/.kin). */
|
|
65
|
+
export function kinHome(env = process.env, home = os.homedir()) {
|
|
66
|
+
return env.KIN_HOME && env.KIN_HOME.trim() ? env.KIN_HOME : path.join(home, '.kin');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Absolute path where the managed `name` binary is expected. An explicit
|
|
71
|
+
* $KIN_MANAGED_BIN overrides the whole computation (an explicit user pin;
|
|
72
|
+
* provisioning never writes through it); otherwise it is
|
|
73
|
+
* <kinHome>/bin/<name>[.exe] — the same directory the shell installer
|
|
74
|
+
* (scripts/install.sh) populates, so either lane satisfies the other.
|
|
75
|
+
*/
|
|
76
|
+
export function managedBinaryPath(name = 'kin', env = process.env, platform = process.platform) {
|
|
77
|
+
if (env.KIN_MANAGED_BIN && env.KIN_MANAGED_BIN.trim()) {
|
|
78
|
+
return env.KIN_MANAGED_BIN;
|
|
79
|
+
}
|
|
80
|
+
return path.join(kinHome(env), 'bin', binaryName(name, platform));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Resolve the managed `name` binary to an absolute path if it exists on disk,
|
|
85
|
+
* else null. Never fabricates a path that is not really present.
|
|
86
|
+
*/
|
|
87
|
+
export function resolveManagedBinary(name = 'kin', env = process.env, platform = process.platform) {
|
|
88
|
+
const candidate = managedBinaryPath(name, env, platform);
|
|
89
|
+
try {
|
|
90
|
+
return fs.existsSync(candidate) ? candidate : null;
|
|
91
|
+
} catch {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Path of the launcher's version stamp: which Kin release this launcher last
|
|
98
|
+
* provisioned into <kinHome>/bin. Lets the normal launch path skip a
|
|
99
|
+
* `kin --version` probe; a binary installed by other means (install.sh) has no
|
|
100
|
+
* stamp and is probed instead of being silently trusted or clobbered.
|
|
101
|
+
*/
|
|
102
|
+
export function launcherStampPath(env = process.env) {
|
|
103
|
+
return path.join(kinHome(env), 'bin', '.kinlab-kin-version');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** The stamped provisioned version, or null when absent/unreadable. */
|
|
107
|
+
export function readLauncherStamp(env = process.env) {
|
|
108
|
+
try {
|
|
109
|
+
const text = fs.readFileSync(launcherStampPath(env), 'utf8').trim();
|
|
110
|
+
return text.length ? text : null;
|
|
111
|
+
} catch {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Record `version` as the provisioned release (directory must exist). */
|
|
117
|
+
export function writeLauncherStamp(version, env = process.env) {
|
|
118
|
+
fs.writeFileSync(launcherStampPath(env), `${version}\n`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Human-readable, honest explanation for when the managed binary is absent. */
|
|
122
|
+
export function notProvisionedMessage(name = 'kin', env = process.env, platform = process.platform) {
|
|
123
|
+
const expected = managedBinaryPath(name, env, platform);
|
|
124
|
+
const triple = (() => {
|
|
125
|
+
try {
|
|
126
|
+
return platformTarget();
|
|
127
|
+
} catch {
|
|
128
|
+
return 'unknown-target';
|
|
129
|
+
}
|
|
130
|
+
})();
|
|
131
|
+
return [
|
|
132
|
+
`kin: managed "${name}" binary not found (expected at ${expected}).`,
|
|
133
|
+
`Host target: ${triple}. @kinlab/kin ${packageVersion()} provisions the managed Kin`,
|
|
134
|
+
'release on first run; if you are seeing this, provisioning failed or was disabled',
|
|
135
|
+
'(KIN_NO_PROVISION=1). Re-run with network access, set KIN_MANAGED_BIN to an existing',
|
|
136
|
+
'kin binary, or install via https://github.com/firelock-ai/kin (scripts/install.sh).',
|
|
137
|
+
].join('\n');
|
|
138
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kinlab/kin",
|
|
3
|
+
"version": "0.2.13",
|
|
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).",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"kin": "bin/kin.mjs",
|
|
9
|
+
"kin-mcp": "bin/kin-mcp.mjs"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"bin/",
|
|
13
|
+
"lib/",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=20"
|
|
18
|
+
},
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"homepage": "https://github.com/firelock-ai/kin",
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/firelock-ai/kin.git",
|
|
26
|
+
"directory": "packages/kin"
|
|
27
|
+
},
|
|
28
|
+
"bugs": {
|
|
29
|
+
"url": "https://github.com/firelock-ai/kin/issues"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"kin",
|
|
33
|
+
"kinlab",
|
|
34
|
+
"cli",
|
|
35
|
+
"mcp",
|
|
36
|
+
"semantic"
|
|
37
|
+
],
|
|
38
|
+
"scripts": {
|
|
39
|
+
"test": "node --test",
|
|
40
|
+
"lint": "node --check ./bin/kin.mjs && node --check ./bin/kin-mcp.mjs && node --check ./lib/resolve.mjs && node --check ./lib/provision.mjs",
|
|
41
|
+
"smoke": "node bin/kin.mjs --version"
|
|
42
|
+
}
|
|
43
|
+
}
|