@takara-ai/miru-code 1.4.0 → 1.5.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 CHANGED
@@ -238,6 +238,8 @@ Miru sends **file contents** to the [Takara inference API](https://takara.ai) wh
238
238
 
239
239
  If you index proprietary code, make sure that sending snippets to Takara's endpoint fits your security and compliance requirements. `MIRU_WORKSPACE_ROOT` is an opt-in boundary for MCP local `repo` paths only, and restricts indexing to a single workspace directory when set.
240
240
 
241
+ Enterprise self-hosted embeddings (no Takara egress): see [docs/self-hosted-sagemaker.md](docs/self-hosted-sagemaker.md).
242
+
241
243
  ## Benchmark mode
242
244
 
243
245
  Optional measurement of how many tokens Miru saves versus a simple Grep workflow (ripgrep + reading the top matched file). Useful when evaluating Miru; leave it off day-to-day. Only local repo paths are compared — git URL repos skip the comparison and return `benchmark_skipped: "local_repo_only"`.
@@ -0,0 +1,116 @@
1
+ # Self-hosted embeddings (AWS SageMaker)
2
+
3
+ Run Miru against your own SageMaker embedding endpoint instead of Takara’s hosted API (`infer.takara.ai`).
4
+
5
+ The embedding model is sold on AWS Marketplace:
6
+
7
+ **[Takara DS1 Miru (int8) — AWS Marketplace](https://aws.amazon.com/marketplace/pp/prodview-la5olwsicr6ue)**
8
+
9
+ ## Overview
10
+
11
+
12
+ | Step | Who | What |
13
+ | ---- | --------- | ---------------------------------------------------------------- |
14
+ | 1 | AWS admin | Subscribe on Marketplace and deploy a SageMaker endpoint |
15
+ | 2 | AWS admin | Create an invoke-only IAM user / profile (runbook below) |
16
+ | 3 | Developer | `miru setup --sagemaker` — Miru validates and saves the endpoint |
17
+
18
+
19
+ Miru **never** creates IAM users or writes `~/.aws`. It only inherits a profile you already have and checks that it can call the endpoint.
20
+
21
+ ## One mode at a time
22
+
23
+ Miru stores **either** a Takara API key **or** a SageMaker endpoint — never both.
24
+
25
+ - `miru setup --sagemaker …` **removes** any stored Takara API key
26
+ - `miru setup` (Takara) **removes** any stored SageMaker endpoint
27
+
28
+ You do not need `--clear` when switching; the other mode is purged automatically.
29
+
30
+ ## 1. Deploy the Marketplace endpoint
31
+
32
+ 1. Open the [Marketplace listing](https://aws.amazon.com/marketplace/pp/prodview-la5olwsicr6ue)
33
+ 2. Subscribe and deploy the model package as a SageMaker endpoint in your account
34
+ 3. Copy the endpoint ARN — it looks like:
35
+
36
+ ```text
37
+ arn:aws:sagemaker:<region>:<account-id>:endpoint/<name>
38
+ ```
39
+
40
+
41
+
42
+ ## 2. Create an invoke-only AWS profile (admin runbook)
43
+
44
+ You need an AWS identity that can call `sagemaker:InvokeEndpoint` on that ARN. The simplest path for a laptop / CI user is a **least-privilege IAM user** scoped to that one endpoint, installed as a named profile.
45
+
46
+ From a checkout of this repo, with the [AWS CLI](https://docs.aws.amazon.com/cli/) available and **admin** credentials in your current shell:
47
+
48
+ ```bash
49
+ bun run sagemaker:create-invoke-user -- \
50
+ --endpoint-arn arn:aws:sagemaker:<region>:<account-id>:endpoint/<name> \
51
+ --profile miru
52
+ ```
53
+
54
+ That script (`scripts/create-sagemaker-invoke-user.ts`):
55
+
56
+ 1. Creates an IAM user tagged for this purpose (or reuses one with the same name)
57
+ 2. Attaches an inline policy allowing **only** `sagemaker:InvokeEndpoint` on that ARN
58
+ 3. Creates an access key and writes it into `~/.aws` as the named profile (`miru` by default)
59
+
60
+ Optional flags: `--user-name <name>`, `--profile <name>` (alias: `--arn` for the endpoint).
61
+
62
+ Prefer SSO / IAM roles in production if your org requires it — the runbook script is the easy start with long-lived keys. Rotate or delete keys when someone leaves.
63
+
64
+ Smoke-test invoke (optional):
65
+
66
+ ```bash
67
+ bun run sagemaker:auth-check -- --arn arn:aws:sagemaker:<region>:<account-id>:endpoint/<name>
68
+ ```
69
+
70
+
71
+
72
+ ## 3. Point Miru at the endpoint
73
+
74
+ ```bash
75
+ miru setup --sagemaker \
76
+ --arn arn:aws:sagemaker:<region>:<account-id>:endpoint/<name> \
77
+ --profile miru
78
+ ```
79
+
80
+ Or run `miru setup --sagemaker` and answer the prompts.
81
+
82
+ **What setup does**
83
+
84
+ 1. Parses the endpoint ARN + AWS profile name
85
+ 2. Invokes the endpoint once to confirm auth and that it returns embeddings
86
+ 3. Saves the SageMaker config and **deletes any stored Takara API key**
87
+
88
+ After that, indexing and search embed via SageMaker — no egress to `infer.takara.ai`.
89
+
90
+ If `TAKARA_API_KEY` is still set in your shell or `.env.local`, remove it there too so it cannot conflict later.
91
+
92
+ ## Switch back to Takara
93
+
94
+ ```bash
95
+ miru setup
96
+ ```
97
+
98
+ Enter your Takara API key. Setup validates it, saves it, and **deletes any stored SageMaker endpoint**.
99
+
100
+ Also unset any `MIRU_SAGEMAKER_*` variables if you set them in the environment or `.env.local`.
101
+
102
+ ## Environment alternatives
103
+
104
+ Prefer `miru setup --sagemaker` so auth is checked automatically. You can also set:
105
+
106
+
107
+ | Variable | Purpose |
108
+ | -------------------------------------------------------- | --------------------------------------------------------- |
109
+ | `MIRU_SAGEMAKER_ENDPOINT_ARN` | `arn:aws:sagemaker:<region>:<account-id>:endpoint/<name>` |
110
+ | `MIRU_SAGEMAKER_ENDPOINT_NAME` + `MIRU_SAGEMAKER_REGION` | Alternative to the ARN |
111
+ | `AWS_PROFILE` / `AWS_ACCESS_KEY_ID` / … | Standard AWS credential chain |
112
+
113
+
114
+ Optional TEI-style knobs: `MIRU_SAGEMAKER_NORMALIZE`, `MIRU_SAGEMAKER_TRUNCATE`, `MIRU_SAGEMAKER_TRUNCATION_DIRECTION`, `MIRU_SAGEMAKER_PROMPT_NAME`.
115
+
116
+ See `miru help setup` and `miru -h` for the same flags in the CLI.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takara-ai/miru-code",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Miru (見る) — hybrid code search for agents. Bun, TypeScript, Takara embeddings.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -38,6 +38,7 @@
38
38
  "src",
39
39
  "grammars",
40
40
  "tokenizer",
41
+ "docs",
41
42
  "README.md",
42
43
  "LICENSE"
43
44
  ],
@@ -61,9 +62,13 @@
61
62
  "benchmark:literal": "bun run scripts/benchmark-literal-tokens.ts",
62
63
  "benchmark:quality": "bun run scripts/benchmark-quality.ts",
63
64
  "benchmark:quantization": "bun run scripts/benchmark-quantization.ts",
64
- "benchmark:tokenizer-ab": "bun run scripts/tokenizer-ab.ts"
65
+ "benchmark:tokenizer-ab": "bun run scripts/tokenizer-ab.ts",
66
+ "sagemaker:auth-check": "bun run scripts/sagemaker-auth-check.ts",
67
+ "sagemaker:create-invoke-user": "bun run scripts/create-sagemaker-invoke-user.ts",
68
+ "benchmark:embedding-latency": "bun run scripts/embedding-latency-ab.ts"
65
69
  },
66
70
  "dependencies": {
71
+ "@aws-sdk/client-sagemaker-runtime": "^3.1090.0",
67
72
  "ignore": "^7.0.5",
68
73
  "web-tree-sitter": "^0.26.9",
69
74
  "zod": "^4.4.3"
package/src/cli.ts CHANGED
@@ -38,6 +38,7 @@ import { MiruIndex } from "./miru-index.ts";
38
38
  import {
39
39
  canPromptForCredentials,
40
40
  ensureCredentials,
41
+ parseSetupCliArgs,
41
42
  runClearCredentials,
42
43
  runSetup,
43
44
  } from "./setup.ts";
@@ -471,30 +472,28 @@ async function runCli(argv: string[]): Promise<void> {
471
472
  }
472
473
 
473
474
  if (command === "setup") {
474
- let apiKey: string | undefined;
475
- let force = false;
476
- let clear = false;
477
- for (let i = 0; i < rest.length; i++) {
478
- const arg = rest[i];
479
- if (arg === "--force") {
480
- force = true;
481
- } else if (arg === "--clear") {
482
- clear = true;
483
- } else if ((arg === "--key" || arg === "-k") && rest[i + 1]) {
484
- apiKey = rest[++i];
485
- }
475
+ const { args, error } = parseSetupCliArgs(rest);
476
+ if (error === "clear_with_key") {
477
+ fail("miru setup --clear cannot be combined with --key.");
478
+ process.exit(1);
486
479
  }
487
- if (clear) {
488
- if (apiKey) {
489
- fail("miru setup --clear cannot be combined with --key.");
490
- process.exit(1);
491
- }
480
+ if (error === "sagemaker_with_key") {
481
+ fail("miru setup --sagemaker cannot be combined with --key.");
482
+ process.exit(1);
483
+ }
484
+ if (args.clear) {
492
485
  await runClearCredentials();
493
486
  return;
494
487
  }
495
- const { newlySaved } = await runSetup({ apiKey, force });
488
+ const { newlySaved } = await runSetup({
489
+ apiKey: args.apiKey,
490
+ force: args.force,
491
+ sagemaker: args.sagemaker,
492
+ sagemakerArn: args.sagemakerArn,
493
+ profile: args.profile,
494
+ });
496
495
  if (newlySaved) {
497
- const offerInstall = canPromptForCredentials() && !apiKey && !force;
496
+ const offerInstall = canPromptForCredentials() && !args.apiKey && !args.force;
498
497
  if (offerInstall) {
499
498
  const install = await promptConfirm("Configure Miru in your coding agent now?");
500
499
  if (install) {
@@ -5,9 +5,15 @@ import { hasTakaraApiKeyInEnv, normalizeTakaraApiKeyEnv } from "./env.ts";
5
5
  const CREDENTIALS_FILENAME = "credentials.json";
6
6
  const CREDENTIALS_VERSION = 1;
7
7
 
8
+ export interface StoredSageMakerCredentials {
9
+ endpoint_arn: string;
10
+ profile?: string;
11
+ }
12
+
8
13
  interface StoredCredentials {
9
14
  version: number;
10
- takara_api_key: string;
15
+ takara_api_key?: string;
16
+ sagemaker?: StoredSageMakerCredentials;
11
17
  }
12
18
 
13
19
  export function resolveCredentialsDir(): string {
@@ -46,7 +52,7 @@ export async function readStoredCredentials(): Promise<StoredCredentials | null>
46
52
  }
47
53
  try {
48
54
  const parsed = JSON.parse(await Bun.file(path).text()) as StoredCredentials;
49
- if (parsed.version !== CREDENTIALS_VERSION || !parsed.takara_api_key) {
55
+ if (parsed.version !== CREDENTIALS_VERSION || (!parsed.takara_api_key && !parsed.sagemaker)) {
50
56
  return null;
51
57
  }
52
58
  return parsed;
@@ -55,24 +61,46 @@ export async function readStoredCredentials(): Promise<StoredCredentials | null>
55
61
  }
56
62
  }
57
63
 
58
- /** Set TAKARA_API_KEY from the user credentials file when env is unset. */
64
+ function hydrateSageMakerEnv(sagemaker: StoredSageMakerCredentials): void {
65
+ process.env.MIRU_SAGEMAKER_ENDPOINT_ARN = sagemaker.endpoint_arn;
66
+ if (sagemaker.profile && !process.env.AWS_PROFILE) {
67
+ process.env.AWS_PROFILE = sagemaker.profile;
68
+ }
69
+ }
70
+
71
+ /** Hydrate TAKARA_API_KEY / SageMaker env vars from the credentials file when env is unset. */
59
72
  export async function loadStoredCredentials(): Promise<boolean> {
60
73
  normalizeTakaraApiKeyEnv();
61
- if (hasTakaraApiKeyInEnv()) {
74
+ const needsTakara = !hasTakaraApiKeyInEnv();
75
+ const needsSageMaker = !process.env.MIRU_SAGEMAKER_ENDPOINT_ARN;
76
+ if (!needsTakara && !needsSageMaker) {
62
77
  return false;
63
78
  }
79
+
64
80
  const stored = await readStoredCredentials();
65
81
  if (!stored) {
66
82
  return false;
67
83
  }
68
- process.env.TAKARA_API_KEY = stored.takara_api_key;
69
- return true;
84
+
85
+ let changed = false;
86
+ if (needsTakara && stored.takara_api_key) {
87
+ process.env.TAKARA_API_KEY = stored.takara_api_key;
88
+ changed = true;
89
+ }
90
+ if (needsSageMaker && stored.sagemaker) {
91
+ hydrateSageMakerEnv(stored.sagemaker);
92
+ changed = true;
93
+ }
94
+ return changed;
70
95
  }
71
96
 
72
97
  export async function saveStoredCredentials(apiKey: string): Promise<string> {
73
98
  const dir = resolveCredentialsDir();
74
99
  const path = resolveCredentialsPath();
75
100
  await mkdir(dir, { recursive: true, mode: 0o700 });
101
+ const existing = await readStoredCredentials();
102
+ // Takara and SageMaker modes are mutually exclusive — saving a key drops any stored endpoint.
103
+ const previousSageMaker = existing?.sagemaker;
76
104
  const payload: StoredCredentials = {
77
105
  version: CREDENTIALS_VERSION,
78
106
  takara_api_key: apiKey,
@@ -83,6 +111,39 @@ export async function saveStoredCredentials(apiKey: string): Promise<string> {
83
111
  } catch {
84
112
  // Windows may not support Unix mode bits on all filesystems.
85
113
  }
114
+ if (previousSageMaker) {
115
+ if (process.env.MIRU_SAGEMAKER_ENDPOINT_ARN === previousSageMaker.endpoint_arn) {
116
+ delete process.env.MIRU_SAGEMAKER_ENDPOINT_ARN;
117
+ }
118
+ if (previousSageMaker.profile && process.env.AWS_PROFILE === previousSageMaker.profile) {
119
+ delete process.env.AWS_PROFILE;
120
+ }
121
+ }
122
+ return path;
123
+ }
124
+
125
+ export async function saveStoredSageMakerCredentials(
126
+ sagemaker: StoredSageMakerCredentials,
127
+ ): Promise<string> {
128
+ const dir = resolveCredentialsDir();
129
+ const path = resolveCredentialsPath();
130
+ await mkdir(dir, { recursive: true, mode: 0o700 });
131
+ const existing = await readStoredCredentials();
132
+ // Takara and SageMaker modes are mutually exclusive — saving an endpoint drops any stored key.
133
+ const previousTakaraKey = existing?.takara_api_key;
134
+ const payload: StoredCredentials = {
135
+ version: CREDENTIALS_VERSION,
136
+ sagemaker,
137
+ };
138
+ await Bun.write(path, `${JSON.stringify(payload, null, 2)}\n`, { mode: 0o600 });
139
+ try {
140
+ await chmod(path, 0o600);
141
+ } catch {
142
+ // Windows may not support Unix mode bits on all filesystems.
143
+ }
144
+ if (previousTakaraKey && process.env.TAKARA_API_KEY === previousTakaraKey) {
145
+ delete process.env.TAKARA_API_KEY;
146
+ }
86
147
  return path;
87
148
  }
88
149
 
@@ -95,9 +156,18 @@ export async function clearStoredCredentials(): Promise<{ cleared: boolean; path
95
156
  const stored = await readStoredCredentials();
96
157
  await Bun.file(path).delete();
97
158
 
98
- if (stored && process.env.TAKARA_API_KEY === stored.takara_api_key) {
159
+ if (stored?.takara_api_key && process.env.TAKARA_API_KEY === stored.takara_api_key) {
99
160
  delete process.env.TAKARA_API_KEY;
100
161
  }
162
+ const sagemaker = stored?.sagemaker;
163
+ if (sagemaker) {
164
+ if (process.env.MIRU_SAGEMAKER_ENDPOINT_ARN === sagemaker.endpoint_arn) {
165
+ delete process.env.MIRU_SAGEMAKER_ENDPOINT_ARN;
166
+ }
167
+ if (sagemaker.profile && process.env.AWS_PROFILE === sagemaker.profile) {
168
+ delete process.env.AWS_PROFILE;
169
+ }
170
+ }
101
171
 
102
172
  return { cleared: true, path };
103
173
  }