@mushi-mushi/cli 0.27.0 → 0.28.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/dist/init.d.ts CHANGED
@@ -1,5 +1,106 @@
1
1
  import { FrameworkId } from './detect.js';
2
2
 
3
+ /**
4
+ * FILE: packages/cli/src/config.ts
5
+ * PURPOSE: CLI configuration read/write — manages persistent credentials.
6
+ *
7
+ * v0.9 — XDG Base Directory Specification compliance.
8
+ * The canonical config now lives at:
9
+ * - Linux/macOS: $XDG_CONFIG_HOME/mushi/config.json
10
+ * (defaults to ~/.config/mushi/config.json)
11
+ * - Windows: %APPDATA%/mushi/config.json
12
+ * (defaults to ~/AppData/Roaming/mushi/config.json)
13
+ * - Anywhere: falls back to ~/.mushirc when neither exists, and
14
+ * one-way-migrates a legacy ~/.mushirc into the new
15
+ * XDG path on first run after the upgrade.
16
+ *
17
+ * Security: the file is written with 0o600 (owner read/write only) so
18
+ * other local users on a shared box cannot read the API key. On load,
19
+ * if an existing config was written before this change (0o644), we
20
+ * proactively chmod it down. The containing directory inherits 0o700.
21
+ */
22
+ /**
23
+ * An ingest-only (report:write) key minted for the SDK env vars, bound to the
24
+ * project it belongs to so a later `mushi config projectId …` can never pair
25
+ * it with another project. Kept so re-running the wizard or `mushi connect
26
+ * --write-env` does not need a new browser sign-in just to mint one — the
27
+ * private `apiKey` above carries mcp:read and must never go into app env.
28
+ */
29
+ interface SavedSdkKey {
30
+ projectId: string;
31
+ key: string;
32
+ }
33
+ interface CliConfig {
34
+ /** Private CLI + MCP key (report:write + mcp:read). Never written to app env. */
35
+ apiKey?: string;
36
+ endpoint?: string;
37
+ projectId?: string;
38
+ /** Public SDK ingest key for `projectId` (see {@link SavedSdkKey}). */
39
+ sdkKey?: SavedSdkKey;
40
+ /** URL of the Mushi admin console (e.g. http://localhost:6464 for local dev). */
41
+ consoleUrl?: string;
42
+ /**
43
+ * Random per-machine identifier sent with device-auth /start so the server
44
+ * can supersede this machine's earlier pending sign-in requests (a stale
45
+ * approval tab must not be approvable while the terminal polls a new code).
46
+ * Not a secret — it only groups requests from the same machine.
47
+ */
48
+ clientId?: string;
49
+ /**
50
+ * True once the one-time telemetry-transparency notice has been shown.
51
+ * Persisted so the notice only appears on first run.
52
+ */
53
+ telemetryNoticeShown?: boolean;
54
+ }
55
+
56
+ /**
57
+ * Outcome of {@link probeKeyScope}:
58
+ * - `ingest-only` — the key authenticates but has no MCP scope (safe to ship)
59
+ * - `mcp` — the key can call the read APIs (must stay private)
60
+ * - `invalid` — the backend rejected the key outright
61
+ * - `unreachable` — the request never got an answer
62
+ * - `unknown` — an answer that proves neither (5xx, an unexpected 403…)
63
+ * @internal Exported for tests only.
64
+ */
65
+ type KeyScopeProbeResult = 'ingest-only' | 'mcp' | 'invalid' | 'unreachable' | 'unknown';
66
+ interface KeyScopeProbe {
67
+ result: KeyScopeProbeResult;
68
+ /** HTTP status when the backend answered. */
69
+ status?: number;
70
+ /** Network error message when it did not. */
71
+ message?: string;
72
+ }
73
+ type ProbeKeyScope = (endpoint: string, apiKey: string, projectId: string) => Promise<KeyScopeProbe>;
74
+
75
+ interface CreatedProject {
76
+ id: string;
77
+ name: string;
78
+ slug: string;
79
+ /** report:write SDK ingest key, minted server-side. Null if mint failed. */
80
+ apiKey: string | null;
81
+ }
82
+ /** Create a project (server auto-mints a key). Throws on failure. */
83
+ declare function createProject(endpoint: string, cliToken: string, name: string, opts?: {
84
+ /** Narrow the auto-minted key (subset of report:write, mcp:read, mcp:write). */
85
+ scopes?: readonly string[];
86
+ }): Promise<CreatedProject>;
87
+ /**
88
+ * Mint a fresh report:write key for an existing project (raw keys can't be
89
+ * recovered, so selecting a project always mints a new one). Throws
90
+ * DeviceAuthRequestError on failure (after one transient retry) so the wizard
91
+ * can tell the user WHY minting failed instead of a bare "could not mint".
92
+ */
93
+ declare function mintProjectKey(endpoint: string, cliToken: string, projectId: string, opts?: {
94
+ /**
95
+ * Narrow the minted key. Must be a subset of the server's LOGIN_SCOPES
96
+ * (report:write, mcp:read, mcp:write) — e.g. ['mcp:read'] for a
97
+ * read-only key written into an editor's mcp.json.
98
+ */
99
+ scopes?: readonly string[];
100
+ /** Console-visible key label, e.g. 'mcp-cursor'. Default: 'cli-login'. */
101
+ label?: string;
102
+ }): Promise<string>;
103
+
3
104
  /**
4
105
  * FILE: packages/cli/src/init.ts
5
106
  * PURPOSE: `mushi init` wizard — detects framework, asks for credentials,
@@ -21,7 +122,76 @@ interface InitOptions {
21
122
  /** Audit an existing install (doctor checks) instead of running the wizard. */
22
123
  audit?: boolean;
23
124
  }
125
+ /**
126
+ * The keys the wizard ends up with, one per destination. `sdkKey` goes into
127
+ * the app's env vars, which ship inside the bundle the end user downloads, so
128
+ * it is report:write only. `cliKey` (report:write + mcp:read) goes to the
129
+ * private CLI config for `mushi` commands and the MCP server; it is absent
130
+ * when this run only learned an ingest key (a pasted key, for example).
131
+ */
132
+ interface WizardCredentials {
133
+ projectId: string;
134
+ sdkKey: string;
135
+ cliKey?: string;
136
+ }
24
137
  declare function runInit(options?: InitOptions): Promise<void>;
138
+ type SavedCredentialsResolution = {
139
+ kind: 'ready';
140
+ credentials: WizardCredentials;
141
+ }
142
+ /** The saved key is the private CLI key; an SDK key still has to be minted. */
143
+ | {
144
+ kind: 'needs-sdk-key';
145
+ projectId: string;
146
+ cliKey: string;
147
+ } | {
148
+ kind: 'unusable';
149
+ reason: string;
150
+ };
151
+ /**
152
+ * Decide what the credentials saved by `mushi login` (or an earlier wizard
153
+ * run) can be used for. The saved `apiKey` normally carries mcp:read, so it
154
+ * is never written to app env; the ingest key saved for the same project is,
155
+ * and so is an `apiKey` the backend confirms is ingest-only.
156
+ */
157
+ declare function resolveSavedCredentials(saved: Pick<CliConfig, 'sdkKey'> & {
158
+ projectId: string;
159
+ apiKey: string;
160
+ }, endpoint: string, probe: ProbeKeyScope): Promise<SavedCredentialsResolution>;
161
+ interface WizardMintDeps {
162
+ createProject: typeof createProject;
163
+ mintProjectKey: typeof mintProjectKey;
164
+ }
165
+ type WizardMintTarget = {
166
+ kind: 'existing';
167
+ projectId: string;
168
+ } | {
169
+ kind: 'new';
170
+ name: string;
171
+ };
172
+ interface WizardMintResult {
173
+ projectId: string;
174
+ /** Name the server gave a project this call created. */
175
+ createdName?: string;
176
+ sdkKey: string;
177
+ cliKey?: string;
178
+ /** Why the CLI key could not be minted. The SDK key is still usable. */
179
+ cliKeyError?: string;
180
+ }
181
+ /**
182
+ * Mint one key per destination after browser approval: a report:write key
183
+ * for the app's env vars and, unless the caller already holds one, a
184
+ * report:write + mcp:read key for the private CLI config. Throws when the
185
+ * project cannot be created or the SDK key cannot be minted; a failed CLI
186
+ * key is reported in `cliKeyError` instead, because the SDK install can
187
+ * still finish without it. Minting is not idempotent, so nothing retries.
188
+ */
189
+ declare function mintWizardKeys(deps: WizardMintDeps, args: {
190
+ endpoint: string;
191
+ cliToken: string;
192
+ target: WizardMintTarget;
193
+ withCliKey: boolean;
194
+ }): Promise<WizardMintResult>;
25
195
  /**
26
196
  * Strip whitespace, quotes, and any control characters a user might paste by
27
197
  * accident. Prevents env-file injection via newlines in a pasted secret.
@@ -37,5 +207,13 @@ declare function sanitizeSecret(raw: string): string;
37
207
  * stay on the safe side — better a false warning than a silent leak.
38
208
  */
39
209
  declare function isEnvFileCoveredByGitignore(gitignoreContent: string, envFile: string): boolean;
210
+ /**
211
+ * The CLI config after a wizard run. The private key slot gets the CLI key;
212
+ * when this run only learned an ingest key, a key already saved for the same
213
+ * project is kept (it may carry mcp:read the ingest key lacks). The ingest
214
+ * key is saved too, bound to its project, so the next run can reuse it
215
+ * without a browser sign-in.
216
+ */
217
+ declare function planCliConfigUpdate(existing: CliConfig, credentials: WizardCredentials, endpoint: string): CliConfig;
40
218
 
41
- export { type InitOptions, isEnvFileCoveredByGitignore, runInit, sanitizeSecret };
219
+ export { type InitOptions, type SavedCredentialsResolution, type WizardCredentials, type WizardMintDeps, type WizardMintResult, type WizardMintTarget, isEnvFileCoveredByGitignore, mintWizardKeys, planCliConfigUpdate, resolveSavedCredentials, runInit, sanitizeSecret };