@uniqbit/mate-core 0.15.2-canary.0 → 0.15.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniqbit/mate-core",
3
- "version": "0.15.2-canary.0",
3
+ "version": "0.15.2",
4
4
  "description": "Core framework and plugin APIs for Mate.",
5
5
  "license": "MIT",
6
6
  "files": [
@@ -8,7 +8,7 @@ This block is kept for AGENTS.md compatibility and must not restate that policy.
8
8
  ## Agent Notes
9
9
 
10
10
  - Use absolute paths when a tool needs a file path. Do not create literal `$MATE_REPO_PATH` or `$MATE_ARTIFACT_PATH` directories.
11
- - When reporting information to me, be extremely concise and sacrifice grammar for the sake of concision. Apply this same preference to JSDoc
11
+ - When reporting information to me, be extremely concise and sacrifice grammar for the sake of concision. Apply this same preference to JSDoc.
12
12
  - Never add a `Co-Authored-By: <model>` trailer or model-attribution footer to commit messages.
13
13
  - Never commit, push, or open a pull request in the working repo (`$MATE_REPO_PATH`) unless the user explicitly asks for it.
14
14
 
@@ -330,8 +330,12 @@ export function createOpenspecPlugin(deps: OpenSpecPluginDeps = {}): CapabilityP
330
330
  ],
331
331
  async apply(ctx) {
332
332
  const tools = deriveOpenSpecTools(ctx.activeProviders);
333
- if (tools.length > 0 && (await ensureOpenSpecInstalled(ctx, depsWithDefaults))) {
334
- await reconcileOpenSpecTools(ctx, depsWithDefaults.runCommand);
333
+ if (ctx.mode === "setup" || tools.length > 0) {
334
+ if (await ensureOpenSpecInstalled(ctx, depsWithDefaults)) {
335
+ if (tools.length > 0) {
336
+ await reconcileOpenSpecTools(ctx, depsWithDefaults.runCommand);
337
+ }
338
+ }
335
339
  }
336
340
  await reconcileOpenSpecSchema(ctx);
337
341
  },
@@ -9,10 +9,11 @@ export { TOKENSAVE_WORKING_REPO_EXCLUDE_ENTRIES } from "./tokensave-shared";
9
9
 
10
10
  export const TOKENSAVE_SUPPORTED_AGENTS = new Set(["claude", "opencode"]);
11
11
  export const TOKENSAVE_STORE_DIR = ".tokensave";
12
+ export const TOKENSAVE_MIN_RUST_VERSION = "1.91.0";
12
13
  const TOKENSAVE_CLAUDE_MD_MARKER = "## MANDATORY: No Explore Agents When Tokensave Is Available";
13
14
  const TOKENSAVE_STORE_EXCLUDE_ENTRY = `${TOKENSAVE_STORE_DIR}/`;
14
15
  const TOKENSAVE_BREW_INSTALL_CMD = "brew install aovestdipaperino/tap/tokensave";
15
- const TOKENSAVE_CARGO_INSTALL_CMD = "cargo install tokensave";
16
+ const TOKENSAVE_CARGO_INSTALL_CMD = "cargo install --locked tokensave";
16
17
  const TOKENSAVE_SCOOP_INSTALL_CMD =
17
18
  "scoop bucket add tokensave https://github.com/aovestdipaperino/scoop-bucket && scoop install tokensave";
18
19
 
@@ -53,6 +54,17 @@ export const tokensaveDeps = {
53
54
  async runShellCommand(command: string): Promise<void> {
54
55
  await runShellCommand(command);
55
56
  },
57
+ rustcVersion(): TokensaveRunResult {
58
+ const result = spawnSync("rustc", ["--version"], {
59
+ encoding: "utf8",
60
+ stdio: ["ignore", "pipe", "pipe"],
61
+ });
62
+ return {
63
+ ok: result.status === 0 && !result.error,
64
+ stderr: result.stderr ?? "",
65
+ stdout: result.stdout ?? "",
66
+ };
67
+ },
56
68
  platform(): NodeJS.Platform {
57
69
  return process.platform;
58
70
  },
@@ -148,6 +160,22 @@ function installTokensaveAgentIntegrations(providers: string[], cwd: string): vo
148
160
  }
149
161
  }
150
162
 
163
+ function getCargoRustVersionError(): string | undefined {
164
+ const result = tokensaveDeps.rustcVersion();
165
+ const version = `${result.stdout}\n${result.stderr}`.match(/\brustc\s+(\d+)\.(\d+)\.(\d+)/);
166
+ if (!result.ok || !version) {
167
+ return `TokenSave requires Rust ${TOKENSAVE_MIN_RUST_VERSION} or newer. Install or upgrade Rust via https://rustup.rs.`;
168
+ }
169
+
170
+ const installed = version.slice(1, 4).map(Number);
171
+ const minimum = TOKENSAVE_MIN_RUST_VERSION.split(".").map(Number);
172
+ const firstDifference = installed.findIndex((part, index) => part !== minimum[index]);
173
+ if (firstDifference !== -1 && installed[firstDifference] < minimum[firstDifference]) {
174
+ return `TokenSave requires Rust ${TOKENSAVE_MIN_RUST_VERSION} or newer, but rustc ${version[1]}.${version[2]}.${version[3]} is installed. Upgrade Rust via https://rustup.rs.`;
175
+ }
176
+ return undefined;
177
+ }
178
+
151
179
  function getTokensaveInstallPlan(): { command: string; run: () => Promise<void> } | undefined {
152
180
  const pathValue = tokensaveDeps.pathValue();
153
181
  const platform = tokensaveDeps.platform();
@@ -169,7 +197,11 @@ function getTokensaveInstallPlan(): { command: string; run: () => Promise<void>
169
197
  if (tokensaveDeps.isCommandOnPath("cargo", pathValue)) {
170
198
  return {
171
199
  command: TOKENSAVE_CARGO_INSTALL_CMD,
172
- run: () => tokensaveDeps.runCommand("cargo", ["install", "tokensave"]),
200
+ run: async () => {
201
+ const rustVersionError = getCargoRustVersionError();
202
+ if (rustVersionError) throw new Error(rustVersionError);
203
+ await tokensaveDeps.runCommand("cargo", ["install", "--locked", "tokensave"]);
204
+ },
173
205
  };
174
206
  }
175
207
 
@@ -192,9 +224,10 @@ export async function ensureTokensaveInstalled(repoPath: string): Promise<boolea
192
224
  process.stdout.write(`tokensave binary not found. Installing with:\n ${installPlan.command}\n`);
193
225
  try {
194
226
  await installPlan.run();
195
- } catch {
227
+ } catch (error) {
228
+ const detail = error instanceof Error ? `: ${error.message}` : "";
196
229
  process.stderr.write(
197
- `tokensave: install failed - install manually with \`${installPlan.command}\`\n`,
230
+ `tokensave: install failed${detail} - install manually with \`${installPlan.command}\`\n`,
198
231
  );
199
232
  return false;
200
233
  }