@takara-ai/miru-code 1.5.3 → 1.6.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 +78 -25
- package/docs/miru-workflow-diagram.svg +47 -0
- package/docs/self-hosted-sagemaker.md +1 -1
- package/package.json +3 -1
- package/scripts/install-git-hooks.ts +29 -0
- package/src/auth/device.ts +298 -0
- package/src/auth/providers.ts +119 -0
- package/src/auth/types.ts +65 -0
- package/src/cli.ts +34 -5
- package/src/credentials.ts +173 -65
- package/src/embeddings/openai.ts +3 -2
- package/src/env.ts +3 -1
- package/src/help.ts +11 -7
- package/src/installer/agents.ts +13 -6
- package/src/installer/installer.ts +66 -6
- package/src/installer/search-policy.ts +1 -1
- package/src/installer/style-packs/caveman.ts +118 -0
- package/src/mcp/auth-tool.ts +131 -0
- package/src/mcp/server.ts +3 -0
- package/src/prompt.ts +8 -5
- package/src/setup.ts +100 -69
- package/tokenizer/tokenizer.json +1 -1
package/src/setup.ts
CHANGED
|
@@ -1,13 +1,23 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { authenticateWithProvider } from "./auth/providers.ts";
|
|
2
|
+
import { credentialAccessToken } from "./auth/types.ts";
|
|
3
|
+
import {
|
|
4
|
+
divider,
|
|
5
|
+
fail,
|
|
6
|
+
hint,
|
|
7
|
+
info,
|
|
8
|
+
printBrandBanner,
|
|
9
|
+
success,
|
|
10
|
+
writeStderr,
|
|
11
|
+
writeStdout,
|
|
12
|
+
} from "./cli-ui.ts";
|
|
2
13
|
import {
|
|
3
14
|
beginModeSwitch,
|
|
4
15
|
clearStoredCredentials,
|
|
5
16
|
loadStoredCredentials,
|
|
6
17
|
readStoredCredentials,
|
|
7
18
|
resolveCredentialsPath,
|
|
8
|
-
type StoredSageMakerCredentials,
|
|
9
19
|
saveStoredCredentials,
|
|
10
|
-
|
|
20
|
+
setStoredCredentialsEnvToken,
|
|
11
21
|
} from "./credentials.ts";
|
|
12
22
|
import {
|
|
13
23
|
isSageMakerConfigured,
|
|
@@ -15,29 +25,20 @@ import {
|
|
|
15
25
|
type SageMakerEmbeddingConfig,
|
|
16
26
|
validateSageMakerConnection,
|
|
17
27
|
} from "./embeddings/sagemaker.ts";
|
|
18
|
-
import { validateEmbeddingApiKey } from "./embeddings/validate.ts";
|
|
19
28
|
import { hasTakaraApiKeyInEnv, resolveEmbeddingApiKey } from "./env.ts";
|
|
20
|
-
import {
|
|
29
|
+
import { promptText } from "./prompt.ts";
|
|
21
30
|
import { Spinner } from "./spinner.ts";
|
|
22
31
|
|
|
23
|
-
async function promptApiKey(): Promise<string> {
|
|
24
|
-
let key = "";
|
|
25
|
-
while (!key) {
|
|
26
|
-
key = await promptHidden("Takara API key (input hidden): ");
|
|
27
|
-
if (!key) {
|
|
28
|
-
fail("API key cannot be empty.");
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
return key;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
32
|
export interface RunSetupOptions {
|
|
35
33
|
apiKey?: string;
|
|
34
|
+
device?: boolean;
|
|
36
35
|
force?: boolean;
|
|
37
36
|
skipValidation?: boolean;
|
|
38
37
|
sagemaker?: boolean;
|
|
39
38
|
sagemakerArn?: string;
|
|
40
39
|
profile?: string;
|
|
40
|
+
allowManualFallback?: boolean;
|
|
41
|
+
interactive?: boolean;
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
export interface RunSetupResult {
|
|
@@ -47,6 +48,7 @@ export interface RunSetupResult {
|
|
|
47
48
|
|
|
48
49
|
export interface ParsedSetupCliArgs {
|
|
49
50
|
apiKey?: string;
|
|
51
|
+
device: boolean;
|
|
50
52
|
force: boolean;
|
|
51
53
|
clear: boolean;
|
|
52
54
|
sagemaker: boolean;
|
|
@@ -54,7 +56,11 @@ export interface ParsedSetupCliArgs {
|
|
|
54
56
|
profile?: string;
|
|
55
57
|
}
|
|
56
58
|
|
|
57
|
-
export type SetupCliArgError =
|
|
59
|
+
export type SetupCliArgError =
|
|
60
|
+
| "clear_with_key"
|
|
61
|
+
| "sagemaker_with_key"
|
|
62
|
+
| "device_with_key"
|
|
63
|
+
| "device_with_sagemaker";
|
|
58
64
|
|
|
59
65
|
/** Parse `miru setup` argv (everything after the `setup` command). */
|
|
60
66
|
export function parseSetupCliArgs(rest: string[]): {
|
|
@@ -62,6 +68,7 @@ export function parseSetupCliArgs(rest: string[]): {
|
|
|
62
68
|
error?: SetupCliArgError;
|
|
63
69
|
} {
|
|
64
70
|
let apiKey: string | undefined;
|
|
71
|
+
let device = false;
|
|
65
72
|
let force = false;
|
|
66
73
|
let clear = false;
|
|
67
74
|
let sagemaker = false;
|
|
@@ -74,6 +81,8 @@ export function parseSetupCliArgs(rest: string[]): {
|
|
|
74
81
|
force = true;
|
|
75
82
|
} else if (arg === "--clear") {
|
|
76
83
|
clear = true;
|
|
84
|
+
} else if (arg === "--device") {
|
|
85
|
+
device = true;
|
|
77
86
|
} else if ((arg === "--key" || arg === "-k") && rest[i + 1]) {
|
|
78
87
|
apiKey = rest[++i];
|
|
79
88
|
} else if (arg === "--sagemaker") {
|
|
@@ -91,6 +100,7 @@ export function parseSetupCliArgs(rest: string[]): {
|
|
|
91
100
|
|
|
92
101
|
const args: ParsedSetupCliArgs = {
|
|
93
102
|
apiKey,
|
|
103
|
+
device,
|
|
94
104
|
force,
|
|
95
105
|
clear,
|
|
96
106
|
sagemaker,
|
|
@@ -98,12 +108,18 @@ export function parseSetupCliArgs(rest: string[]): {
|
|
|
98
108
|
profile,
|
|
99
109
|
};
|
|
100
110
|
|
|
101
|
-
if (clear && apiKey) {
|
|
111
|
+
if (clear && (apiKey || device || sagemaker)) {
|
|
102
112
|
return { args, error: "clear_with_key" };
|
|
103
113
|
}
|
|
104
114
|
if (sagemaker && apiKey) {
|
|
105
115
|
return { args, error: "sagemaker_with_key" };
|
|
106
116
|
}
|
|
117
|
+
if (device && apiKey) {
|
|
118
|
+
return { args, error: "device_with_key" };
|
|
119
|
+
}
|
|
120
|
+
if (device && sagemaker) {
|
|
121
|
+
return { args, error: "device_with_sagemaker" };
|
|
122
|
+
}
|
|
107
123
|
return { args };
|
|
108
124
|
}
|
|
109
125
|
|
|
@@ -147,7 +163,9 @@ export async function runSageMakerSetup(options: RunSetupOptions = {}): Promise<
|
|
|
147
163
|
writeStdout("Miru will connect directly to your self-hosted SageMaker embedding endpoint.");
|
|
148
164
|
hint("Miru only inherits AWS credentials from a profile you've already configured —");
|
|
149
165
|
hint("it never creates or writes to ~/.aws. Run `aws configure --profile <name>` first.");
|
|
150
|
-
hint(
|
|
166
|
+
hint(
|
|
167
|
+
"This replaces any stored Takara credentials — only one embedding mode is active at a time.",
|
|
168
|
+
);
|
|
151
169
|
writeStdout("");
|
|
152
170
|
|
|
153
171
|
const arnInput = options.sagemakerArn ?? (await promptSageMakerArn());
|
|
@@ -186,13 +204,12 @@ export async function runSageMakerSetup(options: RunSetupOptions = {}): Promise<
|
|
|
186
204
|
spinner.succeed("SageMaker endpoint validated");
|
|
187
205
|
}
|
|
188
206
|
|
|
189
|
-
const
|
|
190
|
-
const
|
|
191
|
-
const path = await saveStoredSageMakerCredentials(stored);
|
|
207
|
+
const hadStoredCredentials = Boolean(await readStoredCredentials());
|
|
208
|
+
const path = await saveStoredCredentials({ kind: "sagemaker", endpointArn: arnInput, profile });
|
|
192
209
|
writeStdout("");
|
|
193
210
|
success(`Saved SageMaker config to ${path}`);
|
|
194
|
-
if (
|
|
195
|
-
hint("Removed the stored Takara
|
|
211
|
+
if (hadStoredCredentials) {
|
|
212
|
+
hint("Removed the stored Takara credentials — Miru now embeds only via SageMaker.");
|
|
196
213
|
} else {
|
|
197
214
|
hint("Miru will embed via this SageMaker endpoint (Takara is not used).");
|
|
198
215
|
}
|
|
@@ -205,10 +222,11 @@ export async function runSetup(options: RunSetupOptions = {}): Promise<RunSetupR
|
|
|
205
222
|
return runSageMakerSetup(options);
|
|
206
223
|
}
|
|
207
224
|
|
|
208
|
-
|
|
225
|
+
const interactive = options.interactive ?? canPromptForCredentials();
|
|
226
|
+
if (!options.force && hasTakaraApiKeyInEnv() && !options.apiKey && !options.device) {
|
|
209
227
|
const path = resolveCredentialsPath();
|
|
210
228
|
const stored = await readStoredCredentials();
|
|
211
|
-
if (stored?.sagemaker) {
|
|
229
|
+
if (stored?.kind === "sagemaker") {
|
|
212
230
|
const saved = await saveStoredCredentials(resolveEmbeddingApiKey());
|
|
213
231
|
writeStdout("");
|
|
214
232
|
success(`Saved credentials to ${saved}`);
|
|
@@ -216,8 +234,10 @@ export async function runSetup(options: RunSetupOptions = {}): Promise<RunSetupR
|
|
|
216
234
|
writeStdout("");
|
|
217
235
|
return { path: saved, newlySaved: true };
|
|
218
236
|
}
|
|
219
|
-
if (stored
|
|
220
|
-
info(
|
|
237
|
+
if (stored) {
|
|
238
|
+
info(
|
|
239
|
+
`Credentials already configured (env + ${path}). Use --force to replace stored credentials.`,
|
|
240
|
+
);
|
|
221
241
|
return { path, newlySaved: false };
|
|
222
242
|
}
|
|
223
243
|
info("API key already set via environment variable. Stored credentials unchanged.");
|
|
@@ -226,48 +246,39 @@ export async function runSetup(options: RunSetupOptions = {}): Promise<RunSetupR
|
|
|
226
246
|
|
|
227
247
|
if (!options.force) {
|
|
228
248
|
const stored = await readStoredCredentials();
|
|
229
|
-
if (stored
|
|
230
|
-
info(`
|
|
231
|
-
|
|
249
|
+
if (stored && stored.kind !== "sagemaker" && !options.apiKey && !options.device) {
|
|
250
|
+
info(`Credentials already stored at ${resolveCredentialsPath()}. Use --force to replace.`);
|
|
251
|
+
setStoredCredentialsEnvToken(credentialAccessToken(stored));
|
|
232
252
|
return { path: resolveCredentialsPath(), newlySaved: false };
|
|
233
253
|
}
|
|
234
254
|
}
|
|
235
255
|
|
|
236
|
-
|
|
237
|
-
// stderr
|
|
256
|
+
writeStderr("");
|
|
257
|
+
// stderr: setup/MCP must not write human auth UI to stdout (JSON-RPC / piped CLI).
|
|
238
258
|
printBrandBanner(process.stderr);
|
|
239
259
|
divider("─", 48, process.stderr);
|
|
240
|
-
|
|
241
|
-
hint("Get a bearer token from Takara, then enter it below.");
|
|
242
|
-
hint(
|
|
243
|
-
"This replaces any stored SageMaker endpoint — only one embedding mode is active at a time.",
|
|
244
|
-
);
|
|
245
|
-
writeStdout("");
|
|
246
|
-
|
|
247
|
-
const apiKey = options.apiKey ?? (await promptApiKey());
|
|
260
|
+
writeStderr("");
|
|
248
261
|
|
|
249
262
|
await beginModeSwitch("takara");
|
|
250
|
-
if (!options.skipValidation) {
|
|
251
|
-
const spinner = new Spinner("Validating API key");
|
|
252
|
-
spinner.start();
|
|
253
|
-
const result = await validateEmbeddingApiKey({ apiKey });
|
|
254
|
-
if (!result.valid) {
|
|
255
|
-
spinner.stop();
|
|
256
|
-
throw new Error(result.message);
|
|
257
|
-
}
|
|
258
|
-
spinner.succeed("API key validated");
|
|
259
|
-
}
|
|
260
263
|
|
|
261
|
-
const hadSageMaker =
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
264
|
+
const hadSageMaker = (await readStoredCredentials())?.kind === "sagemaker";
|
|
265
|
+
const credentials = await authenticateWithProvider({
|
|
266
|
+
apiKey: options.apiKey,
|
|
267
|
+
device: options.device,
|
|
268
|
+
skipValidation: options.skipValidation,
|
|
269
|
+
allowManualFallback: options.allowManualFallback ?? interactive,
|
|
270
|
+
interactive,
|
|
271
|
+
});
|
|
272
|
+
const path = await saveStoredCredentials(credentials);
|
|
273
|
+
setStoredCredentialsEnvToken(
|
|
274
|
+
credentials.kind === "api_key" ? credentials.apiKey : credentials.accessToken,
|
|
275
|
+
);
|
|
276
|
+
writeStderr("");
|
|
277
|
+
hint("Congratulations! You are now signed in.");
|
|
265
278
|
if (hadSageMaker) {
|
|
266
279
|
hint("Removed the stored SageMaker endpoint — Miru now embeds only via Takara.");
|
|
267
|
-
} else {
|
|
268
|
-
hint("MCP loads this key from credentials.json automatically.");
|
|
269
280
|
}
|
|
270
|
-
|
|
281
|
+
writeStderr("");
|
|
271
282
|
return { path, newlySaved: true };
|
|
272
283
|
}
|
|
273
284
|
|
|
@@ -305,28 +316,48 @@ export async function ensureCredentials(options?: { interactive?: boolean }): Pr
|
|
|
305
316
|
return;
|
|
306
317
|
}
|
|
307
318
|
|
|
308
|
-
|
|
319
|
+
const wantsPrompt = options?.interactive ?? true;
|
|
320
|
+
let refreshError: Error | null = null;
|
|
321
|
+
try {
|
|
322
|
+
await refreshCredentialsFromStore();
|
|
323
|
+
} catch (err) {
|
|
324
|
+
refreshError = err instanceof Error ? err : new Error(String(err));
|
|
325
|
+
}
|
|
309
326
|
if (hasCredentials()) {
|
|
310
327
|
return;
|
|
311
328
|
}
|
|
312
329
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
330
|
+
if (wantsPrompt) {
|
|
331
|
+
writeStderr("");
|
|
332
|
+
if (refreshError) {
|
|
333
|
+
info(`Stored credentials could not be used: ${refreshError.message}`);
|
|
334
|
+
hint("Starting a fresh device-code login.");
|
|
335
|
+
} else {
|
|
336
|
+
info("No Takara credentials found.");
|
|
337
|
+
hint("Starting the same device-code login flow as `miru setup`.");
|
|
338
|
+
}
|
|
339
|
+
await runSetup({
|
|
340
|
+
device: true,
|
|
341
|
+
force: true,
|
|
342
|
+
allowManualFallback: false,
|
|
343
|
+
interactive: true,
|
|
344
|
+
});
|
|
319
345
|
resolveEmbeddingApiKey();
|
|
320
346
|
return;
|
|
321
347
|
}
|
|
322
348
|
|
|
323
|
-
|
|
349
|
+
if (refreshError) {
|
|
350
|
+
throw refreshError;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
writeStderr("");
|
|
324
354
|
// Brand on stderr when credentials are missing in non-interactive mode (stdout may not be a TTY).
|
|
325
355
|
printBrandBanner(process.stderr);
|
|
326
|
-
|
|
356
|
+
writeStderr("");
|
|
327
357
|
|
|
328
358
|
throw new Error(
|
|
329
|
-
"Takara
|
|
330
|
-
"
|
|
359
|
+
"Takara credentials required. If you're an agent with Miru MCP tools available, call the " +
|
|
360
|
+
"`auth` tool to sign in. Otherwise run `miru setup` or `miru setup --key TOKEN` in an " +
|
|
361
|
+
"interactive terminal, or set TAKARA_API_KEY in your environment.",
|
|
331
362
|
);
|
|
332
363
|
}
|