bantamkit-mcp 0.32.1 → 0.34.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.
- package/README.md +196 -23
- package/assets/schemas/shiftwork-checkpoint.json +5 -1
- package/assets/tools/bantamkit_read.json +1 -3
- package/assets/tools/repo_map.json +1 -3
- package/assets/tools/shiftwork_clock_in.json +1 -1
- package/assets/tools/shiftwork_clock_out.json +38 -3
- package/assets/tools/skill_audit.json +1 -1
- package/assets/tools/token_ledger.json +1 -1
- package/dist/cli.js +5 -4
- package/dist/eventlog.js +29 -5
- package/dist/hostinstall.d.ts +39 -7
- package/dist/hostinstall.js +105 -11
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/mcp/pyargs.js +5 -0
- package/dist/mcp/server.d.ts +20 -8
- package/dist/mcp/server.js +47 -14
- package/dist/memory/cli.js +1 -1
- package/dist/memory/store.d.ts +47 -0
- package/dist/memory/store.js +67 -1
- package/dist/npminstall.d.ts +54 -0
- package/dist/npminstall.js +141 -0
- package/dist/selfupdate.d.ts +39 -38
- package/dist/selfupdate.js +62 -108
- package/dist/shiftwork.d.ts +19 -2
- package/dist/shiftwork.js +268 -10
- package/package.json +1 -1
package/dist/hostinstall.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { keptCli, keptPrefix } from './npminstall.js';
|
|
2
|
+
export { keptCli, keptPrefix };
|
|
1
3
|
/** The four hosts, in the order they print in `--help`. */
|
|
2
4
|
export declare const HOSTS: readonly ["claude", "claude-desktop", "copilot", "cursor"];
|
|
3
5
|
export type Host = (typeof HOSTS)[number];
|
|
@@ -21,16 +23,46 @@ export declare function configKey(host: Host): string;
|
|
|
21
23
|
export declare function entryFor(host: Host, command: string, args: string[]): Record<string, unknown>;
|
|
22
24
|
/** Register this server with `host`. Returns the report to print; throws `InstallError`. */
|
|
23
25
|
export declare function install(host: Host, command: string, args: string[], force?: boolean): string;
|
|
26
|
+
/** The seams `thisCommand` takes, so no test ever reaches npm or the network. */
|
|
27
|
+
export interface CommandOptions {
|
|
28
|
+
/** The install shape. Default: `currentInstall().shape` — never a second detector. */
|
|
29
|
+
readonly shape?: () => string;
|
|
30
|
+
/** Runs an argv and returns `[exit code, output]`. Default: `npminstall.runInstaller`. */
|
|
31
|
+
readonly installer?: (command: string[]) => [number, string];
|
|
32
|
+
/** This process's version. Default: the `package.json` this build ships with. */
|
|
33
|
+
readonly version?: string;
|
|
34
|
+
}
|
|
24
35
|
/**
|
|
25
|
-
* What a host should run to get THIS server.
|
|
36
|
+
* What a host should run to get THIS server: `<absolute node> <absolute dist/cli.js>`.
|
|
37
|
+
*
|
|
38
|
+
* NOT `npx -y bantamkit-mcp` ANY MORE — see the module header for the measurement: with a warm
|
|
39
|
+
* cache and no network that command hangs silently, so a host that launches it offline sees a
|
|
40
|
+
* handshake timeout. Not a bare `node` either, because a GUI host's PATH need not have one, and
|
|
41
|
+
* not a `.bin` shim, whose `#!/usr/bin/env node` fails the same way (measured: exit 127).
|
|
42
|
+
* `process.execPath` is the node binary running THIS process, which is known to work.
|
|
43
|
+
*
|
|
44
|
+
* WHICH `cli.js` DEPENDS ON THE INSTALL SHAPE, and the shape is `currentInstall()`'s answer:
|
|
26
45
|
*
|
|
27
|
-
* `
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
46
|
+
* - `ephemeral` (an `npx` cache): this process's own `cli.js` lives in a cache npm may discard,
|
|
47
|
+
* so the recorded one is the KEPT install at `keptPrefix()`. If its manifest already reports
|
|
48
|
+
* this version OR A NEWER ONE and its `cli.js` is there, nothing runs — a second `--install`
|
|
49
|
+
* for another host works offline, and a stale `npx` cache never moves a kept install that
|
|
50
|
+
* `--update` took further back to its own version (J51-9a). Otherwise `npm install --prefix <kept> bantamkit-mcp@<this version>`
|
|
51
|
+
* runs once, through the same spawner `--update` uses, and the `cli.js` is confirmed after.
|
|
52
|
+
* npm creates a missing prefix directory itself (measured, npm 11.6.2), so nothing is made
|
|
53
|
+
* here first and a failed install leaves no empty directory of this module's making.
|
|
54
|
+
* - every other shape (`registry`, `local-file`, `linked`, `checkout`) is already kept, so it
|
|
55
|
+
* is this process's own `dist/cli.js`.
|
|
56
|
+
*
|
|
57
|
+
* Every failure is an `InstallError` thrown BEFORE any host configuration is read or written.
|
|
32
58
|
*/
|
|
33
|
-
export declare function thisCommand(): {
|
|
59
|
+
export declare function thisCommand(options?: CommandOptions): {
|
|
34
60
|
command: string;
|
|
35
61
|
args: string[];
|
|
36
62
|
};
|
|
63
|
+
/**
|
|
64
|
+
* `--install <host>` whole: resolve the command (making the kept install if this is an `npx`
|
|
65
|
+
* cache), THEN touch the host. The order is the property: a refusal from npm leaves the host's
|
|
66
|
+
* file byte-unchanged with no backup, because `install` has not been reached.
|
|
67
|
+
*/
|
|
68
|
+
export declare function installSelf(host: Host, force?: boolean, options?: CommandOptions): string;
|
package/dist/hostinstall.js
CHANGED
|
@@ -17,15 +17,29 @@
|
|
|
17
17
|
* runtimes write different bytes for the same install.
|
|
18
18
|
*
|
|
19
19
|
* WHAT IS RULED DIFFERENT, and it is one field: the COMMAND. This side records
|
|
20
|
-
*
|
|
21
|
-
* should be the thing that answers, and neither side should send a host
|
|
22
|
-
* other's runtime. `docs/porting.md` carries the row.
|
|
20
|
+
* `<absolute node> <absolute .../dist/cli.js>`; the reference records its own console script.
|
|
21
|
+
* The thing installed should be the thing that answers, and neither side should send a host
|
|
22
|
+
* looking for the other's runtime. `docs/porting.md` carries the row.
|
|
23
|
+
*
|
|
24
|
+
* JOB51 CHANGED THIS SIDE'S COMMAND, AND THE REASON IS A MEASUREMENT. It used to be
|
|
25
|
+
* `npx -y bantamkit-mcp`. On npm 11.6.2, with a WARM cache and the network cut (proxies pointed
|
|
26
|
+
* at a closed port, which leaves the cache key alone), that command — pinned, unpinned or
|
|
27
|
+
* `@latest` — hung silently past 45 s with zero bytes on stdout, so a host reports a handshake
|
|
28
|
+
* timeout. A kept `npm install --prefix` launched as `<abs node> <abs dist/cli.js>` answered in
|
|
29
|
+
* 0.11 s under a GUI-shaped PATH (`/usr/bin:/bin:/usr/sbin:/sbin`), where that install's own
|
|
30
|
+
* `.bin/bantamkit-mcp` shim exits 127 (`env: node: No such file or directory`). The user ruled
|
|
31
|
+
* (2026-09-15): `--install` makes a one-time kept install under `~/.bantamkit`, and the host
|
|
32
|
+
* launches it without the network. See `thisCommand`.
|
|
23
33
|
*/
|
|
24
34
|
import { spawnSync } from 'node:child_process';
|
|
25
35
|
import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync, } from 'node:fs';
|
|
26
36
|
import { homedir } from 'node:os';
|
|
27
37
|
import { dirname, join } from 'node:path';
|
|
38
|
+
import { fileURLToPath } from 'node:url';
|
|
39
|
+
import { currentInstall, Undetermined } from './mcp/identity.js';
|
|
40
|
+
import { compareVersions, keptCli, keptManifest, keptPrefix, PACKAGE, runInstaller, shlexJoin } from './npminstall.js';
|
|
28
41
|
import { dumpJson, fromJs } from './pyjson.js';
|
|
42
|
+
export { keptCli, keptPrefix };
|
|
29
43
|
/** The four hosts, in the order they print in `--help`. */
|
|
30
44
|
export const HOSTS = ['claude', 'claude-desktop', 'copilot', 'cursor'];
|
|
31
45
|
/** The entry name written into every host. */
|
|
@@ -233,15 +247,95 @@ export function install(host, command, args, force = false) {
|
|
|
233
247
|
lines.push(` backup : ${copied}`);
|
|
234
248
|
return lines.join('\n');
|
|
235
249
|
}
|
|
250
|
+
function ownVersion() {
|
|
251
|
+
return JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'))
|
|
252
|
+
.version;
|
|
253
|
+
}
|
|
254
|
+
/** The kept install's version, or `null` when there is no readable manifest with one. */
|
|
255
|
+
function keptVersion(prefix) {
|
|
256
|
+
try {
|
|
257
|
+
const manifest = JSON.parse(readFileSync(keptManifest(prefix), 'utf8'));
|
|
258
|
+
return typeof manifest.version === 'string' ? manifest.version : null;
|
|
259
|
+
}
|
|
260
|
+
catch {
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
/** The installer's last non-blank line — stderr's, when npm wrote any, since it is appended last. */
|
|
265
|
+
function lastLine(output) {
|
|
266
|
+
const lines = output.split(/\r?\n/).map((l) => l.trim()).filter((l) => l !== '');
|
|
267
|
+
return lines.length === 0 ? 'it printed nothing' : (lines[lines.length - 1] ?? '');
|
|
268
|
+
}
|
|
236
269
|
/**
|
|
237
|
-
* What a host should run to get THIS server.
|
|
270
|
+
* What a host should run to get THIS server: `<absolute node> <absolute dist/cli.js>`.
|
|
271
|
+
*
|
|
272
|
+
* NOT `npx -y bantamkit-mcp` ANY MORE — see the module header for the measurement: with a warm
|
|
273
|
+
* cache and no network that command hangs silently, so a host that launches it offline sees a
|
|
274
|
+
* handshake timeout. Not a bare `node` either, because a GUI host's PATH need not have one, and
|
|
275
|
+
* not a `.bin` shim, whose `#!/usr/bin/env node` fails the same way (measured: exit 127).
|
|
276
|
+
* `process.execPath` is the node binary running THIS process, which is known to work.
|
|
238
277
|
*
|
|
239
|
-
*
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
278
|
+
* WHICH `cli.js` DEPENDS ON THE INSTALL SHAPE, and the shape is `currentInstall()`'s answer:
|
|
279
|
+
*
|
|
280
|
+
* - `ephemeral` (an `npx` cache): this process's own `cli.js` lives in a cache npm may discard,
|
|
281
|
+
* so the recorded one is the KEPT install at `keptPrefix()`. If its manifest already reports
|
|
282
|
+
* this version OR A NEWER ONE and its `cli.js` is there, nothing runs — a second `--install`
|
|
283
|
+
* for another host works offline, and a stale `npx` cache never moves a kept install that
|
|
284
|
+
* `--update` took further back to its own version (J51-9a). Otherwise `npm install --prefix <kept> bantamkit-mcp@<this version>`
|
|
285
|
+
* runs once, through the same spawner `--update` uses, and the `cli.js` is confirmed after.
|
|
286
|
+
* npm creates a missing prefix directory itself (measured, npm 11.6.2), so nothing is made
|
|
287
|
+
* here first and a failed install leaves no empty directory of this module's making.
|
|
288
|
+
* - every other shape (`registry`, `local-file`, `linked`, `checkout`) is already kept, so it
|
|
289
|
+
* is this process's own `dist/cli.js`.
|
|
290
|
+
*
|
|
291
|
+
* Every failure is an `InstallError` thrown BEFORE any host configuration is read or written.
|
|
244
292
|
*/
|
|
245
|
-
export function thisCommand() {
|
|
246
|
-
|
|
293
|
+
export function thisCommand(options = {}) {
|
|
294
|
+
let shape;
|
|
295
|
+
try {
|
|
296
|
+
shape = (options.shape ?? (() => currentInstall().shape))();
|
|
297
|
+
}
|
|
298
|
+
catch (e) {
|
|
299
|
+
if (!(e instanceof Undetermined))
|
|
300
|
+
throw e;
|
|
301
|
+
throw new InstallError(`--install could not tell how this install was made, so it will not guess which copy a host should launch: ${e.message}`);
|
|
302
|
+
}
|
|
303
|
+
const node = process.execPath;
|
|
304
|
+
if (shape !== 'ephemeral') {
|
|
305
|
+
return { command: node, args: [fileURLToPath(new URL('./cli.js', import.meta.url))] };
|
|
306
|
+
}
|
|
307
|
+
const version = options.version ?? ownVersion();
|
|
308
|
+
const prefix = keptPrefix();
|
|
309
|
+
const cli = keptCli(prefix);
|
|
310
|
+
const kept = keptVersion(prefix);
|
|
311
|
+
// NEVER LOWER THE KEPT INSTALL (J51-9a). An `npx` cache can be older than the kept install —
|
|
312
|
+
// `--update` moved the kept install on, and this cache was filled weeks ago — and every host
|
|
313
|
+
// already launches the kept one, so installing THIS version over it would move them all back.
|
|
314
|
+
// A kept version at or above this one is recorded as it is. `compareVersions` is total: a
|
|
315
|
+
// component that is not all digits sorts after every number, so a version it cannot read as
|
|
316
|
+
// numbers counts as newer and is left alone rather than overwritten. A blank one is no version.
|
|
317
|
+
if (kept !== null && kept.trim() !== '' && compareVersions(kept, version) >= 0 && existsSync(cli)) {
|
|
318
|
+
return { command: node, args: [cli] };
|
|
319
|
+
}
|
|
320
|
+
const command = ['npm', 'install', '--prefix', prefix, `${PACKAGE}@${version}`];
|
|
321
|
+
const rendered = shlexJoin(command);
|
|
322
|
+
const [code, output] = (options.installer ?? runInstaller)(command);
|
|
323
|
+
const refused = `could not make the kept install at ${prefix}, so no host configuration was changed: ${rendered} exited ${code}`;
|
|
324
|
+
if (code !== 0)
|
|
325
|
+
throw new InstallError(`${refused}: ${lastLine(output)}`);
|
|
326
|
+
if (!existsSync(cli))
|
|
327
|
+
throw new InstallError(`${refused} but left no ${cli}`);
|
|
328
|
+
return { command: node, args: [cli] };
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* `--install <host>` whole: resolve the command (making the kept install if this is an `npx`
|
|
332
|
+
* cache), THEN touch the host. The order is the property: a refusal from npm leaves the host's
|
|
333
|
+
* file byte-unchanged with no backup, because `install` has not been reached.
|
|
334
|
+
*/
|
|
335
|
+
export function installSelf(host, force = false, options = {}) {
|
|
336
|
+
if (!HOSTS.includes(host)) {
|
|
337
|
+
throw new InstallError(`unknown host '${host}'; choose one of: ${HOSTS.join(', ')}`);
|
|
338
|
+
}
|
|
339
|
+
const { command, args } = thisCommand(options);
|
|
340
|
+
return install(host, command, args, force);
|
|
247
341
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export { absolutise, blockKey, claimSlot, collapse, dream, formatFixed3, mergeBo
|
|
|
18
18
|
export type { DateHit, DreamMerge, DreamResult, SimilarPair, Superseded } from './memory/dream.js';
|
|
19
19
|
export { CAP_BYTES, DEFAULT_RELATIVE_PATH, defaultPath, encodeRecord, EVENT_LOG_ENV, EventLog, formatTimestamp, resolvePath, SCHEMA_VERSION, } from './eventlog.js';
|
|
20
20
|
export type { DetailValue } from './eventlog.js';
|
|
21
|
-
export { DEFAULT_INDEX_BUDGET, DUPLICATE_JACCARD, jaccard, MemoryBudgetExceeded, MemoryStore, MemoryValidationError, pyCompareLt, pyEqualValue, pyHashKey, pyText, RECALL_MIN_SCORE_RATIO, sortScored, tokens, VALID_TYPES, } from './memory/store.js';
|
|
21
|
+
export { BANTAMKIT_GITIGNORE_TEXT, DEFAULT_INDEX_BUDGET, DUPLICATE_JACCARD, ensureBantamkitGitignore, jaccard, MemoryBudgetExceeded, MemoryStore, MemoryValidationError, pyCompareLt, pyEqualValue, pyHashKey, pyText, RECALL_MIN_SCORE_RATIO, sortScored, tokens, VALID_TYPES, } from './memory/store.js';
|
|
22
22
|
export type { MemoryStoreOptions, SaveResult, StoreInternals } from './memory/store.js';
|
|
23
23
|
export { asPyOSError, cmpCodepoint, matchesMd, normcase, pyDecodeUtf8, pyCwd, pyExists, pyExpanduser, pyHome, pyIsAbsolute, pyIsDir, pyIsFile, pyJoin, pyLexists, pyMkdirParents, pyMtimeDate, pyName, PyOSError, pyParent, pyParents, pyReadText, pyReplace, pyRepr, pyResolve, PyRuntimeError, pyScandirNames, PyUnicodeDecodeError, pyAppendText, pyStatIsDir, pySuffix, pyUnlink, pyWithSuffix, pyWriteText, sortedPathNames, sortedPathParts, STRERROR_NAMES, } from './memory/pyfs.js';
|
|
24
24
|
export { clockIn, clockOut, HISTORY_RING_SIZE, SCHEMA_NAME, status, TERMINAL_UNIT_STATUS, timestamp, } from './shiftwork.js';
|
package/dist/index.js
CHANGED
|
@@ -9,6 +9,6 @@ export { countFacts, discoverProjectStore, loadGrants, MEMORY_DIR_ENV, PROJECT_S
|
|
|
9
9
|
export { layerLabel, Memory, normalizeName, profileStore } from './memory/component.js';
|
|
10
10
|
export { absolutise, blockKey, claimSlot, collapse, dream, formatFixed3, mergeBodies, mergeDescriptions, mergeLinks, PROFILE_LAYER, PROJECT_LAYER, pyIntDigits, sortedNames, splitBlocks, SUPERSEDED_HEADING, } from './memory/dream.js';
|
|
11
11
|
export { CAP_BYTES, DEFAULT_RELATIVE_PATH, defaultPath, encodeRecord, EVENT_LOG_ENV, EventLog, formatTimestamp, resolvePath, SCHEMA_VERSION, } from './eventlog.js';
|
|
12
|
-
export { DEFAULT_INDEX_BUDGET, DUPLICATE_JACCARD, jaccard, MemoryBudgetExceeded, MemoryStore, MemoryValidationError, pyCompareLt, pyEqualValue, pyHashKey, pyText, RECALL_MIN_SCORE_RATIO, sortScored, tokens, VALID_TYPES, } from './memory/store.js';
|
|
12
|
+
export { BANTAMKIT_GITIGNORE_TEXT, DEFAULT_INDEX_BUDGET, DUPLICATE_JACCARD, ensureBantamkitGitignore, jaccard, MemoryBudgetExceeded, MemoryStore, MemoryValidationError, pyCompareLt, pyEqualValue, pyHashKey, pyText, RECALL_MIN_SCORE_RATIO, sortScored, tokens, VALID_TYPES, } from './memory/store.js';
|
|
13
13
|
export { asPyOSError, cmpCodepoint, matchesMd, normcase, pyDecodeUtf8, pyCwd, pyExists, pyExpanduser, pyHome, pyIsAbsolute, pyIsDir, pyIsFile, pyJoin, pyLexists, pyMkdirParents, pyMtimeDate, pyName, PyOSError, pyParent, pyParents, pyReadText, pyReplace, pyRepr, pyResolve, PyRuntimeError, pyScandirNames, PyUnicodeDecodeError, pyAppendText, pyStatIsDir, pySuffix, pyUnlink, pyWithSuffix, pyWriteText, sortedPathNames, sortedPathParts, STRERROR_NAMES, } from './memory/pyfs.js';
|
|
14
14
|
export { clockIn, clockOut, HISTORY_RING_SIZE, SCHEMA_NAME, status, TERMINAL_UNIT_STATUS, timestamp, } from './shiftwork.js';
|
package/dist/mcp/pyargs.js
CHANGED
|
@@ -112,6 +112,9 @@ export const ARG_MODELS = {
|
|
|
112
112
|
// `maximum` are advisory to the client for `limit` and for `offset`'s floor; the handler
|
|
113
113
|
// clamps `limit` to `[1, 200]` and `offset` to `>= 0` itself, the way `memory_recall`
|
|
114
114
|
// clamps `k`. `offset`'s CEILING is bound in the model, as it is on the reference (F2).
|
|
115
|
+
// DORMANT — `bantamkit_read` left the roster (job50 I5, 2026-09-12). The model stays so the
|
|
116
|
+
// roster line can return with its validation intact; `tools/call` consults `MCP_TOOLS`
|
|
117
|
+
// before this table, so the entry serves nothing today.
|
|
115
118
|
bantamkit_read: {
|
|
116
119
|
model: 'bantamkit_readArguments',
|
|
117
120
|
fields: [req('path', 'str'), opt('part', 'str'), opt('offset', 'int', { le: OFFSET_MAXIMUM }), opt('limit', 'int')],
|
|
@@ -144,6 +147,8 @@ export const ARG_MODELS = {
|
|
|
144
147
|
// explicit `null` is the default. No `le`, because the reference binds none — the
|
|
145
148
|
// manifest's `minimum: 0` is advisory to the client and the handler's own refusal is the
|
|
146
149
|
// negative one, exactly as `skill_audit`'s `budget` works.
|
|
150
|
+
// DORMANT — `repo_map` left the roster (job50 I5, 2026-09-12); kept for the same reason as
|
|
151
|
+
// `bantamkit_read` above, and equally unreachable from the wire.
|
|
147
152
|
repo_map: {
|
|
148
153
|
model: 'repo_mapArguments',
|
|
149
154
|
fields: [req('root', 'str'), opt('focus', 'listStr'), opt('budget', 'int')],
|
package/dist/mcp/server.d.ts
CHANGED
|
@@ -4,17 +4,25 @@ import { EventLog } from '../eventlog.js';
|
|
|
4
4
|
import type { Memory } from '../memory/component.js';
|
|
5
5
|
import type { RawStdioTransport } from './transport.js';
|
|
6
6
|
/**
|
|
7
|
-
* The
|
|
7
|
+
* The twelve, in the order `build_server` lists them — which is the order `tools/list` emits.
|
|
8
8
|
*
|
|
9
9
|
* `bantamkit_status` went LAST rather than first, `memory_compact` after it rather than
|
|
10
|
-
* beside `memory_save` where a reader would look for it, `
|
|
11
|
-
* `
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
10
|
+
* beside `memory_save` where a reader would look for it, `skill_audit` after that,
|
|
11
|
+
* `memory_dream` after that and `token_ledger` after that, for the same reason the reference
|
|
12
|
+
* appends: registration order IS the served order, and appending is the only edit that
|
|
13
|
+
* leaves the others where every existing declaration says they are.
|
|
14
|
+
*
|
|
15
|
+
* `bantamkit_read` (tenth) and `repo_map` (thirteenth) LEFT this list on the user's ruling
|
|
16
|
+
* of 2026-09-12 (job50, I5), the same ruling that took them out of the reference's
|
|
17
|
+
* `tools = [...]`: measured over the transcript corpus, neither was called — auto-mode routes
|
|
18
|
+
* discovery and reading through Bash — and every request re-sent their descriptions. Their
|
|
19
|
+
* assets claim NO surface now (`"surfaces": []`), so putting either name back here without
|
|
20
|
+
* also restoring `"mcp"` to its asset is refused by `fromManifest` at startup; and a name
|
|
21
|
+
* that is not here is `Unknown tool:` on `tools/call` even though `ARG_MODELS` still holds
|
|
22
|
+
* its argument model. The handlers below are DORMANT, not gone: a roster decision, not a
|
|
23
|
+
* deletion of working code.
|
|
16
24
|
*/
|
|
17
|
-
export declare const MCP_TOOLS: readonly ["memory_save", "memory_recall", "validate_json", "shiftwork_clock_in", "shiftwork_clock_out", "shiftwork_status", "build_identity", "bantamkit_status", "memory_compact", "
|
|
25
|
+
export declare const MCP_TOOLS: readonly ["memory_save", "memory_recall", "validate_json", "shiftwork_clock_in", "shiftwork_clock_out", "shiftwork_status", "build_identity", "bantamkit_status", "memory_compact", "skill_audit", "memory_dream", "token_ledger"];
|
|
18
26
|
/**
|
|
19
27
|
* The advertisement for one tool, out of its manifest, with the surface checked.
|
|
20
28
|
*
|
|
@@ -35,6 +43,10 @@ export declare function fromManifest(name: string): Record<string, unknown>;
|
|
|
35
43
|
* The last paragraph of every `repo_map` reply, refusal excepted. FIXED AND MANDATORY, and
|
|
36
44
|
* byte-identical to the reference's `REPO_MAP_TAIL`.
|
|
37
45
|
*
|
|
46
|
+
* DORMANT — `repo_map` left the roster (job50 I5, 2026-09-12). The tail, `REPO_MAP_EMPTY`
|
|
47
|
+
* and `repoMapReply` below stay with the handler: a roster decision, not a deletion of
|
|
48
|
+
* working code.
|
|
49
|
+
*
|
|
38
50
|
* Roadmap row 10's build gate was "build only after #4 shows discovery tokens dominate",
|
|
39
51
|
* and #4 REFUTED it: discovery is 0.114 % of real prompt tokens because 97.8 % of the bill
|
|
40
52
|
* is `cache_read`. The feature ships on an explicit ruling to build it anyway, as a
|
package/dist/mcp/server.js
CHANGED
|
@@ -17,10 +17,14 @@
|
|
|
17
17
|
* framing, request routing, the initialize handshake, cancellation, error codes.
|
|
18
18
|
*
|
|
19
19
|
* THE `surfaces` GATE IS LOAD-BEARING. `assets/tools/` serves the eval agent too, and three
|
|
20
|
-
* of the
|
|
21
|
-
* `
|
|
22
|
-
*
|
|
23
|
-
*
|
|
20
|
+
* of the seventeen manifests (`document_list`, `document_read`, `file_graph`) claim only
|
|
21
|
+
* `agent`, while two (`bantamkit_read`, `repo_map`) claim NO surface at all since job50 I5
|
|
22
|
+
* (2026-09-12). `fromManifest` REFUSES those by name rather than filtering them out, because
|
|
23
|
+
* a filter is indistinguishable from a typo: a manifest renamed or a surface dropped would
|
|
24
|
+
* silently shrink the served set, and the field would be a comment. `MCP_TOOLS` below is the
|
|
25
|
+
* SAME literal list the reference's `build_server` holds, in the same order, and the asset is
|
|
26
|
+
* the gate on every name in it — not the source of the names, because the served ORDER is
|
|
27
|
+
* part of the wire and no asset carries it.
|
|
24
28
|
*/
|
|
25
29
|
import { existsSync, readFileSync, realpathSync, statSync } from 'node:fs';
|
|
26
30
|
import { dirname, join } from 'node:path';
|
|
@@ -44,15 +48,23 @@ import { ARG_MODELS, PyValidationFailure, validateArguments } from './pyargs.js'
|
|
|
44
48
|
import { sdkJson } from './sdkjson.js';
|
|
45
49
|
import { degradedConditions, degradedNotice, SERVED_PROMPTS, SERVED_RESOURCE_TEMPLATES, statusReport, STATUS_NAME, STATUS_PROMPT_DESCRIPTION, STATUS_PROMPT_TAIL, STATUS_PROMPT_TITLE, } from './status.js';
|
|
46
50
|
/**
|
|
47
|
-
* The
|
|
51
|
+
* The twelve, in the order `build_server` lists them — which is the order `tools/list` emits.
|
|
48
52
|
*
|
|
49
53
|
* `bantamkit_status` went LAST rather than first, `memory_compact` after it rather than
|
|
50
|
-
* beside `memory_save` where a reader would look for it, `
|
|
51
|
-
* `
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
54
|
+
* beside `memory_save` where a reader would look for it, `skill_audit` after that,
|
|
55
|
+
* `memory_dream` after that and `token_ledger` after that, for the same reason the reference
|
|
56
|
+
* appends: registration order IS the served order, and appending is the only edit that
|
|
57
|
+
* leaves the others where every existing declaration says they are.
|
|
58
|
+
*
|
|
59
|
+
* `bantamkit_read` (tenth) and `repo_map` (thirteenth) LEFT this list on the user's ruling
|
|
60
|
+
* of 2026-09-12 (job50, I5), the same ruling that took them out of the reference's
|
|
61
|
+
* `tools = [...]`: measured over the transcript corpus, neither was called — auto-mode routes
|
|
62
|
+
* discovery and reading through Bash — and every request re-sent their descriptions. Their
|
|
63
|
+
* assets claim NO surface now (`"surfaces": []`), so putting either name back here without
|
|
64
|
+
* also restoring `"mcp"` to its asset is refused by `fromManifest` at startup; and a name
|
|
65
|
+
* that is not here is `Unknown tool:` on `tools/call` even though `ARG_MODELS` still holds
|
|
66
|
+
* its argument model. The handlers below are DORMANT, not gone: a roster decision, not a
|
|
67
|
+
* deletion of working code.
|
|
56
68
|
*/
|
|
57
69
|
export const MCP_TOOLS = [
|
|
58
70
|
'memory_save',
|
|
@@ -64,12 +76,12 @@ export const MCP_TOOLS = [
|
|
|
64
76
|
'build_identity',
|
|
65
77
|
'bantamkit_status',
|
|
66
78
|
'memory_compact',
|
|
67
|
-
'bantamkit_read',
|
|
68
79
|
'skill_audit',
|
|
69
80
|
'memory_dream',
|
|
70
|
-
'repo_map',
|
|
71
81
|
'token_ledger',
|
|
72
82
|
];
|
|
83
|
+
/** The served set, for the call path: a name outside it is unknown, whatever `ARG_MODELS` says. */
|
|
84
|
+
const SERVED = new Set(MCP_TOOLS);
|
|
73
85
|
/**
|
|
74
86
|
* The advertisement for one tool, out of its manifest, with the surface checked.
|
|
75
87
|
*
|
|
@@ -169,6 +181,10 @@ const EXISTS_IGNORED = new Set(['ENOENT', 'ENOTDIR', 'EBADF', 'ELOOP']);
|
|
|
169
181
|
* The last paragraph of every `repo_map` reply, refusal excepted. FIXED AND MANDATORY, and
|
|
170
182
|
* byte-identical to the reference's `REPO_MAP_TAIL`.
|
|
171
183
|
*
|
|
184
|
+
* DORMANT — `repo_map` left the roster (job50 I5, 2026-09-12). The tail, `REPO_MAP_EMPTY`
|
|
185
|
+
* and `repoMapReply` below stay with the handler: a roster decision, not a deletion of
|
|
186
|
+
* working code.
|
|
187
|
+
*
|
|
172
188
|
* Roadmap row 10's build gate was "build only after #4 shows discovery tokens dominate",
|
|
173
189
|
* and #4 REFUTED it: discovery is 0.114 % of real prompt tokens because 97.8 % of the bill
|
|
174
190
|
* is `cache_read`. The feature ships on an explicit ruling to build it anyway, as a
|
|
@@ -243,6 +259,9 @@ function recordRaise(log, tool, body) {
|
|
|
243
259
|
/**
|
|
244
260
|
* The last `docread.extract` result this server produced, and what it was OF.
|
|
245
261
|
*
|
|
262
|
+
* DORMANT — its one user, `bantamkit_read`, left the roster (job50 I5, 2026-09-12); kept
|
|
263
|
+
* with the handler so the roster line can come back without re-deriving the cache.
|
|
264
|
+
*
|
|
246
265
|
* The port of `mcpserver._DocumentCache` (register entry (i), `docs/roadmap-toolbox.md` row
|
|
247
266
|
* 8). `bantamkit_read` re-parsed the whole document on EVERY call, so a caller paging a
|
|
248
267
|
* 12,001-row sheet in 200-row pages parsed the workbook once per page — paging was O(N^2) in
|
|
@@ -539,6 +558,12 @@ function runTool(name, memory, args, version, log, documents) {
|
|
|
539
558
|
return { value: { t: 'str', v: noted(outcome.reply) }, wrapped: true };
|
|
540
559
|
}
|
|
541
560
|
case 'bantamkit_read': {
|
|
561
|
+
// DORMANT — NOT ON THE ROSTER since job50 I5 (user ruling, 2026-09-12): `bantamkit_read`
|
|
562
|
+
// left `MCP_TOOLS` because the transcript corpus showed it was never called. The reader
|
|
563
|
+
// (`docread`, `contract`) and its tests are untouched; this arm, `DocumentCache` and
|
|
564
|
+
// `OFFSET_MAXIMUM` are kept, unreachable from the wire (`SERVED` gates the call path),
|
|
565
|
+
// so the roster line can return without a rewrite.
|
|
566
|
+
//
|
|
542
567
|
// The reader on the MCP surface (job43): `docread` digests, `contract` words it. The
|
|
543
568
|
// handler makes the SAME `contract` calls the reference's `bantamkit_read` makes,
|
|
544
569
|
// with the path standing in for the document name, so the two servers print the
|
|
@@ -736,6 +761,11 @@ function runTool(name, memory, args, version, log, documents) {
|
|
|
736
761
|
});
|
|
737
762
|
}
|
|
738
763
|
case 'repo_map': {
|
|
764
|
+
// DORMANT — NOT ON THE ROSTER since job50 I5 (user ruling, 2026-09-12): `repo_map` left
|
|
765
|
+
// `MCP_TOOLS` because the transcript corpus showed it was never called. The engine
|
|
766
|
+
// (`repomap.ts`) and its tests are untouched; this arm is kept, unreachable from the
|
|
767
|
+
// wire (`SERVED` gates the call path), so the roster line can return without a rewrite.
|
|
768
|
+
//
|
|
739
769
|
// The ranked definition map on the MCP surface (job45 row 10): `repomap` measures,
|
|
740
770
|
// this serves it. Byte for byte the reference's `repo_map` handler.
|
|
741
771
|
//
|
|
@@ -991,7 +1021,10 @@ export function buildServer(memory, wire, version, log = EventLog.fromEnv(memory
|
|
|
991
1021
|
let structuredValue = null;
|
|
992
1022
|
let isError;
|
|
993
1023
|
try {
|
|
994
|
-
|
|
1024
|
+
// The roster decides what exists, not the argument table: `ARG_MODELS` still carries
|
|
1025
|
+
// the DORMANT `bantamkit_read` and `repo_map` models (job50 I5), and the reference
|
|
1026
|
+
// answers `Unknown tool:` for a handler it never registered.
|
|
1027
|
+
const model = SERVED.has(name) ? ARG_MODELS[name] : undefined;
|
|
995
1028
|
if (model === undefined)
|
|
996
1029
|
throw new BantamError(`Unknown tool: ${name}`);
|
|
997
1030
|
const bound = validateArguments(model, args);
|
package/dist/memory/cli.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* `docs/memory.md` states, as a design decision, that `lint`, `archived` and `restore`
|
|
6
6
|
* are **not** agent tools — "lifecycle is an operator decision, not a model decision" —
|
|
7
|
-
* and the
|
|
7
|
+
* and the twelve-tool surface holds to it; `compact` is the one exception since job42
|
|
8
8
|
* (`memory_compact`, the on-refusal path the model reaches itself). `runtime-py` made that
|
|
9
9
|
* position coherent in August 2026 by giving the operator `python -m bantamkit.memory`.
|
|
10
10
|
* `runtime-ts` did not. MEASURED 2026-08-24 before this file existed: `node dist/cli.js -h`
|
package/dist/memory/store.d.ts
CHANGED
|
@@ -1,6 +1,53 @@
|
|
|
1
1
|
import { BantamError } from '../errors.js';
|
|
2
2
|
import type { Fact, FactValue } from './factfile.js';
|
|
3
3
|
export type { Fact } from './factfile.js';
|
|
4
|
+
/**
|
|
5
|
+
* The exact bytes written into a `.bantamkit/.gitignore` the first time such a directory is
|
|
6
|
+
* brought into existence (user ruling 2026-09-15, `.shiftwork/notes-job51/P0-probes.md`).
|
|
7
|
+
* Both runtimes hold this text byte-for-byte -- a conformance case pins it against the
|
|
8
|
+
* Python literal -- so it is spelled once, here, rather than re-typed at each call site.
|
|
9
|
+
* `runtime-py/src/bantamkit/memory/store.py`'s `BANTAMKIT_GITIGNORE_TEXT` is the reference.
|
|
10
|
+
*/
|
|
11
|
+
export declare const BANTAMKIT_GITIGNORE_TEXT: string;
|
|
12
|
+
/**
|
|
13
|
+
* Best-effort, idempotent: give `directory` a self-ignoring `.gitignore` if it is literally
|
|
14
|
+
* named `.bantamkit` AND `created` says THIS CALLER'S OWN mkdir is what just brought it into
|
|
15
|
+
* existence.
|
|
16
|
+
*
|
|
17
|
+
* USER RULING #2 (2026-09-15, after J51-8 measured the fallout of the first rule): the
|
|
18
|
+
* ignore file is written only when bantamkit itself creates the `.bantamkit` directory. An
|
|
19
|
+
* existing `.bantamkit` -- made by an earlier bantamkit, by hand, or checked out from git;
|
|
20
|
+
* with or without a `.gitignore` already in it -- is never given one. That is why `created`
|
|
21
|
+
* is the caller's job, not this function's: only the caller knows whether `.bantamkit`
|
|
22
|
+
* existed immediately before ITS mkdir, because by the time this function runs the directory
|
|
23
|
+
* unconditionally exists either way. (Was: written whenever the ignore file itself was
|
|
24
|
+
* absent, regardless of whether `.bantamkit` predated the call -- which meant deleting the
|
|
25
|
+
* file never stuck, and a store a team already commits would silently start ignoring new
|
|
26
|
+
* fact files after an upgrade. `P0-probes.md` ruling 4.)
|
|
27
|
+
*
|
|
28
|
+
* THE PROPERTY, not the mechanism: whenever a `.bantamkit` directory is brought into
|
|
29
|
+
* existence -- by `MemoryStore.ensureDirs`, or by anything else in this runtime that creates
|
|
30
|
+
* one, such as `EventLog`'s own append-time `mkdirSync` when the event log is on and the
|
|
31
|
+
* project store was never saved to -- this is the one place that decides whether it gets
|
|
32
|
+
* ignored. A second creator that skipped this call, or that got `created` wrong, would leave
|
|
33
|
+
* a `.bantamkit` that git can see (or ignore one it should not), which is the whole bug this
|
|
34
|
+
* closes.
|
|
35
|
+
*
|
|
36
|
+
* NEVER REWRITES. A `.gitignore` that already exists -- whatever its bytes -- is left
|
|
37
|
+
* exactly as it is, kept as a second guard even when `created` is `true`: an operator who
|
|
38
|
+
* deleted it to commit the directory keeps that decision, and this never diffs its own
|
|
39
|
+
* output against what is on disk.
|
|
40
|
+
*
|
|
41
|
+
* NEVER RAISES. Failing to write the ignore file must never be why a fact does not get
|
|
42
|
+
* saved -- the parent write still has to succeed exactly as it does today. An unwritable
|
|
43
|
+
* filesystem, a permissions error, or the directory disappearing under this call are all
|
|
44
|
+
* swallowed, mirroring the reference's `except OSError: pass`.
|
|
45
|
+
*
|
|
46
|
+
* NEVER CREATES `directory` ITSELF. A write into a missing parent fails like any other
|
|
47
|
+
* filesystem error here, so calling this before `directory` exists is a no-op, not a way to
|
|
48
|
+
* bring `.bantamkit` into existence early.
|
|
49
|
+
*/
|
|
50
|
+
export declare function ensureBantamkitGitignore(directory: string, created: boolean): void;
|
|
4
51
|
/** `sorted(VALID_TYPES)` is what the error message interpolates, so the order is load-bearing. */
|
|
5
52
|
export declare const VALID_TYPES: readonly ["feedback", "project", "reference", "user"];
|
|
6
53
|
/**
|
package/dist/memory/store.js
CHANGED
|
@@ -42,7 +42,67 @@ import { basename } from 'node:path';
|
|
|
42
42
|
import { BantamError } from '../errors.js';
|
|
43
43
|
import { formatFact, parseFrontmatter, pySplit, pyStrip, todayLocal } from './factfile.js';
|
|
44
44
|
import { PyScalar } from './pyyaml.js';
|
|
45
|
-
import { asPyOSError, cmpCodepoint, matchesMd, pyExists, pyJoin, pyLexists, pyMkdirParents, pyMtimeDate, pyName, pyReadText, pyReplace, pyScandirNames, pySuffix, pyUnlink, pyWithSuffix, pyWriteText, PyOSError, sortedPathNames, } from './pyfs.js';
|
|
45
|
+
import { asPyOSError, cmpCodepoint, matchesMd, pyExists, pyJoin, pyLexists, pyMkdirParents, pyMtimeDate, pyName, pyParent, pyReadText, pyReplace, pyScandirNames, pySuffix, pyUnlink, pyWithSuffix, pyWriteText, PyOSError, sortedPathNames, } from './pyfs.js';
|
|
46
|
+
/**
|
|
47
|
+
* The exact bytes written into a `.bantamkit/.gitignore` the first time such a directory is
|
|
48
|
+
* brought into existence (user ruling 2026-09-15, `.shiftwork/notes-job51/P0-probes.md`).
|
|
49
|
+
* Both runtimes hold this text byte-for-byte -- a conformance case pins it against the
|
|
50
|
+
* Python literal -- so it is spelled once, here, rather than re-typed at each call site.
|
|
51
|
+
* `runtime-py/src/bantamkit/memory/store.py`'s `BANTAMKIT_GITIGNORE_TEXT` is the reference.
|
|
52
|
+
*/
|
|
53
|
+
export const BANTAMKIT_GITIGNORE_TEXT = '# Created by bantamkit: this directory is local state. Delete this file to commit it.\n' +
|
|
54
|
+
'*\n';
|
|
55
|
+
/**
|
|
56
|
+
* Best-effort, idempotent: give `directory` a self-ignoring `.gitignore` if it is literally
|
|
57
|
+
* named `.bantamkit` AND `created` says THIS CALLER'S OWN mkdir is what just brought it into
|
|
58
|
+
* existence.
|
|
59
|
+
*
|
|
60
|
+
* USER RULING #2 (2026-09-15, after J51-8 measured the fallout of the first rule): the
|
|
61
|
+
* ignore file is written only when bantamkit itself creates the `.bantamkit` directory. An
|
|
62
|
+
* existing `.bantamkit` -- made by an earlier bantamkit, by hand, or checked out from git;
|
|
63
|
+
* with or without a `.gitignore` already in it -- is never given one. That is why `created`
|
|
64
|
+
* is the caller's job, not this function's: only the caller knows whether `.bantamkit`
|
|
65
|
+
* existed immediately before ITS mkdir, because by the time this function runs the directory
|
|
66
|
+
* unconditionally exists either way. (Was: written whenever the ignore file itself was
|
|
67
|
+
* absent, regardless of whether `.bantamkit` predated the call -- which meant deleting the
|
|
68
|
+
* file never stuck, and a store a team already commits would silently start ignoring new
|
|
69
|
+
* fact files after an upgrade. `P0-probes.md` ruling 4.)
|
|
70
|
+
*
|
|
71
|
+
* THE PROPERTY, not the mechanism: whenever a `.bantamkit` directory is brought into
|
|
72
|
+
* existence -- by `MemoryStore.ensureDirs`, or by anything else in this runtime that creates
|
|
73
|
+
* one, such as `EventLog`'s own append-time `mkdirSync` when the event log is on and the
|
|
74
|
+
* project store was never saved to -- this is the one place that decides whether it gets
|
|
75
|
+
* ignored. A second creator that skipped this call, or that got `created` wrong, would leave
|
|
76
|
+
* a `.bantamkit` that git can see (or ignore one it should not), which is the whole bug this
|
|
77
|
+
* closes.
|
|
78
|
+
*
|
|
79
|
+
* NEVER REWRITES. A `.gitignore` that already exists -- whatever its bytes -- is left
|
|
80
|
+
* exactly as it is, kept as a second guard even when `created` is `true`: an operator who
|
|
81
|
+
* deleted it to commit the directory keeps that decision, and this never diffs its own
|
|
82
|
+
* output against what is on disk.
|
|
83
|
+
*
|
|
84
|
+
* NEVER RAISES. Failing to write the ignore file must never be why a fact does not get
|
|
85
|
+
* saved -- the parent write still has to succeed exactly as it does today. An unwritable
|
|
86
|
+
* filesystem, a permissions error, or the directory disappearing under this call are all
|
|
87
|
+
* swallowed, mirroring the reference's `except OSError: pass`.
|
|
88
|
+
*
|
|
89
|
+
* NEVER CREATES `directory` ITSELF. A write into a missing parent fails like any other
|
|
90
|
+
* filesystem error here, so calling this before `directory` exists is a no-op, not a way to
|
|
91
|
+
* bring `.bantamkit` into existence early.
|
|
92
|
+
*/
|
|
93
|
+
export function ensureBantamkitGitignore(directory, created) {
|
|
94
|
+
if (pyName(directory) !== '.bantamkit' || !created)
|
|
95
|
+
return;
|
|
96
|
+
const gitignore = pyJoin(directory, '.gitignore');
|
|
97
|
+
if (pyExists(gitignore))
|
|
98
|
+
return;
|
|
99
|
+
try {
|
|
100
|
+
pyWriteText(gitignore, BANTAMKIT_GITIGNORE_TEXT);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
/* best-effort: a missing ignore file is not worth a lost fact */
|
|
104
|
+
}
|
|
105
|
+
}
|
|
46
106
|
/** `sorted(VALID_TYPES)` is what the error message interpolates, so the order is load-bearing. */
|
|
47
107
|
export const VALID_TYPES = ['feedback', 'project', 'reference', 'user'];
|
|
48
108
|
/**
|
|
@@ -535,6 +595,11 @@ export class MemoryStore {
|
|
|
535
595
|
* what lets one conformance case pin this line byte for byte.
|
|
536
596
|
*/
|
|
537
597
|
ensureDirs() {
|
|
598
|
+
// Checked BEFORE the mkdir below, per user ruling #2: this is the one moment that can
|
|
599
|
+
// tell whether `.bantamkit` already existed. After the mkdir it unconditionally exists
|
|
600
|
+
// either way, so the answer has to be captured now or not at all.
|
|
601
|
+
const bantamkitDir = pyParent(this.root);
|
|
602
|
+
const bantamkitDirExistedBefore = pyExists(bantamkitDir);
|
|
538
603
|
try {
|
|
539
604
|
pyMkdirParents(pyJoin(this.root, 'facts'));
|
|
540
605
|
pyMkdirParents(pyJoin(this.root, 'archive'));
|
|
@@ -545,6 +610,7 @@ export class MemoryStore {
|
|
|
545
610
|
throw new MemoryValidationError(`memory store could not be created: ${this.root}; the directory is not` +
|
|
546
611
|
' there and this filesystem would not make it, so nothing was written');
|
|
547
612
|
}
|
|
613
|
+
ensureBantamkitGitignore(bantamkitDir, !bantamkitDirExistedBefore);
|
|
548
614
|
}
|
|
549
615
|
// ---- ops ----
|
|
550
616
|
/**
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/** The npm package this install would upgrade. Divergent by construction (`DISTRIBUTION`). */
|
|
2
|
+
export declare const PACKAGE = "bantamkit-mcp";
|
|
3
|
+
/**
|
|
4
|
+
* Where `--install` keeps the one-time install a host launches: `<homedir>/.bantamkit/mcp`.
|
|
5
|
+
*
|
|
6
|
+
* DEFINED ONCE, HERE, because two flags must agree on it byte for byte: `--install` makes it and
|
|
7
|
+
* records its `cli.js`, and `--update` from an `npx` cache patches it in place. A second spelling
|
|
8
|
+
* of this path would be a second place for the two to disagree about a user's install.
|
|
9
|
+
*
|
|
10
|
+
* `homedir()` and nothing else — `HOME` on POSIX, `USERPROFILE` on Windows — so pointing those at
|
|
11
|
+
* a scratch directory moves it, which is what makes the tests possible.
|
|
12
|
+
*/
|
|
13
|
+
export declare function keptPrefix(home?: string): string;
|
|
14
|
+
/** The `dist/cli.js` inside the kept install — what a host is told to run with node. */
|
|
15
|
+
export declare function keptCli(prefix?: string): string;
|
|
16
|
+
/** The `package.json` inside the kept install, whose `version` says whether it is current. */
|
|
17
|
+
export declare function keptManifest(prefix?: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* `shlex.join`: `shlex.quote` each word and space them.
|
|
20
|
+
*
|
|
21
|
+
* Hand-rolled from CPython's own rule — `_find_unsafe = re.compile(r'[^\w@%+=:,./-]', re.ASCII)`
|
|
22
|
+
* — because the rendered command is printed to the operator by `UPDATING`, by `COMMAND_FAILED`
|
|
23
|
+
* and by the `local-file` route, and a prefix path with a space in it is the normal case on
|
|
24
|
+
* macOS and on Windows. Single quotes, with an embedded `'` closed and reopened the way
|
|
25
|
+
* `shlex.quote` does it, so the line can be pasted back into a shell unchanged.
|
|
26
|
+
*/
|
|
27
|
+
export declare function shlexJoin(command: readonly string[]): string;
|
|
28
|
+
/**
|
|
29
|
+
* Run the installer, capture what it said, return both.
|
|
30
|
+
*
|
|
31
|
+
* CAPTURED RATHER THAN INHERITED so the report has one shape whether or not anybody is
|
|
32
|
+
* watching, and so a test can inject a substitute and assert on the command WITHOUT a real
|
|
33
|
+
* install ever running. On the `--install` path this also keeps npm's progress off stdout,
|
|
34
|
+
* which is the operator's report and nothing else.
|
|
35
|
+
*
|
|
36
|
+
* THE ONE PLACE THIS IS NOT THE REFERENCE'S BEHAVIOUR: `stderr=STDOUT` genuinely interleaves
|
|
37
|
+
* the two streams in the order they happened, and `spawnSync` has no fd-dup, so the two pipes
|
|
38
|
+
* are concatenated instead — stdout, then stderr. npm writes its progress to stderr and its
|
|
39
|
+
* result to stdout, so an operator reading a failure still gets both, in two blocks rather than
|
|
40
|
+
* one. The installer's own words are never compared across runtimes; the sentences around them
|
|
41
|
+
* are.
|
|
42
|
+
*
|
|
43
|
+
* `shell: true` ON WINDOWS ONLY, the idiom `hostinstall.installViaClaudeCli` established for
|
|
44
|
+
* the same reason: npm is a `npm.cmd` batch shim there and `spawnSync` returns ENOENT for a
|
|
45
|
+
* batch file without a shell. Arguments are quoted because `cmd.exe` gets a string.
|
|
46
|
+
*/
|
|
47
|
+
export declare function runInstaller(command: readonly string[]): [number, string];
|
|
48
|
+
/**
|
|
49
|
+
* -1 when the index is ahead, 0 when they agree, 1 when the installed version is ahead.
|
|
50
|
+
*
|
|
51
|
+
* The shorter of the two is padded with numeric zeros, so `0.30` and `0.30.0` agree — which is
|
|
52
|
+
* what a person means by them and what both registries would print for one release.
|
|
53
|
+
*/
|
|
54
|
+
export declare function compareVersions(installed: string, latest: string): number;
|