carrick 0.3.57 → 0.3.59

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.
Files changed (65) hide show
  1. package/README.md +117 -26
  2. package/bin/carrick.mjs +13 -2
  3. package/dist/auth/credentials.d.ts +14 -0
  4. package/dist/auth/credentials.js +86 -0
  5. package/dist/auth/credentials.js.map +1 -0
  6. package/dist/auth/oauth.d.ts +11 -0
  7. package/dist/auth/oauth.js +119 -0
  8. package/dist/auth/oauth.js.map +1 -0
  9. package/dist/auth/read.d.ts +139 -0
  10. package/dist/auth/read.js +44 -0
  11. package/dist/auth/read.js.map +1 -0
  12. package/dist/auth/run.d.ts +12 -0
  13. package/dist/auth/run.js +68 -0
  14. package/dist/auth/run.js.map +1 -0
  15. package/dist/contract.d.ts +10 -0
  16. package/dist/contract.js +3 -0
  17. package/dist/contract.js.map +1 -1
  18. package/dist/definition.d.ts +33 -0
  19. package/dist/definition.js +107 -0
  20. package/dist/definition.js.map +1 -0
  21. package/dist/diagnostics.d.ts +72 -3
  22. package/dist/diagnostics.js +275 -55
  23. package/dist/diagnostics.js.map +1 -1
  24. package/dist/hook/refresh.d.ts +32 -0
  25. package/dist/hook/refresh.js +138 -0
  26. package/dist/hook/refresh.js.map +1 -0
  27. package/dist/hook/session-start.js +13 -0
  28. package/dist/hook/session-start.js.map +1 -1
  29. package/dist/init/connect.d.ts +17 -0
  30. package/dist/init/connect.js +120 -0
  31. package/dist/init/connect.js.map +1 -0
  32. package/dist/init/mcp.d.ts +50 -0
  33. package/dist/init/mcp.js +235 -0
  34. package/dist/init/mcp.js.map +1 -0
  35. package/dist/init/projects.d.ts +46 -0
  36. package/dist/init/projects.js +137 -0
  37. package/dist/init/projects.js.map +1 -0
  38. package/dist/init/repos.d.ts +113 -19
  39. package/dist/init/repos.js +60 -39
  40. package/dist/init/repos.js.map +1 -1
  41. package/dist/init/run.d.ts +16 -0
  42. package/dist/init/run.js +243 -61
  43. package/dist/init/run.js.map +1 -1
  44. package/dist/init/settings.d.ts +1 -1
  45. package/dist/init/settings.js +1 -1
  46. package/dist/init/settings.js.map +1 -1
  47. package/dist/lens.d.ts +41 -0
  48. package/dist/lens.js +116 -0
  49. package/dist/lens.js.map +1 -0
  50. package/dist/server.js +327 -24
  51. package/dist/server.js.map +1 -1
  52. package/dist/surfaces.d.ts +28 -0
  53. package/dist/surfaces.js +70 -0
  54. package/dist/surfaces.js.map +1 -0
  55. package/package.json +6 -6
  56. package/sidecar/dist/src/capture/deno-project.d.ts +41 -0
  57. package/sidecar/dist/src/capture/deno-project.js +513 -0
  58. package/sidecar/dist/src/capture/index.d.ts +1 -0
  59. package/sidecar/dist/src/capture/index.js +60 -19
  60. package/sidecar/dist/src/capture/self-check.d.ts +2 -0
  61. package/sidecar/dist/src/capture/self-check.js +3 -2
  62. package/sidecar/dist/src/project-loader.js +22 -1
  63. package/dist/init/identity.d.ts +0 -20
  64. package/dist/init/identity.js +0 -60
  65. package/dist/init/identity.js.map +0 -1
package/README.md CHANGED
@@ -1,37 +1,92 @@
1
1
  # carrick
2
2
 
3
- Carrick indexes the TypeScript repos sitting beside each other on your disk and
4
- answers, about the file you are editing right now: which routes and calls are in
5
- it, who is on the other side of them in the other repos, and whether the
6
- contract between them still holds. It answers in your editor's Problems panel
7
- and in your coding agent's context, without either of them asking.
3
+ Carrick indexes local TypeScript repositories and identifies the routes and
4
+ calls in the file you are editing, their counterparts in indexed repositories,
5
+ and whether their contracts agree. Configured editors show diagnostics, and
6
+ coding agents can receive context through supported hooks or explicit checks.
8
7
 
9
8
  ```
10
9
  npm install -g carrick
11
- cd ~/code # the folder that holds your repos
10
+ carrick login
11
+ cd ~/code # a repo, or the folder that holds your repos
12
12
  carrick init
13
13
  ```
14
14
 
15
- `carrick init` needs a GitHub identity `gh auth login`, or a `GITHUB_TOKEN` in
16
- the environment and tells you so before it writes anything.
15
+ `carrick login` opens the browser to authorise a Carrick workspace. `init`
16
+ accepts that credential or a `CARRICK_TOKEN` environment override, and signs in
17
+ through the browser itself when a terminal has neither. GitHub CLI credentials
18
+ do not grant Carrick access.
19
+
20
+ Existing users can initialise the local workspace against a named Carrick
21
+ project:
22
+
23
+ ```
24
+ carrick init --project payments
25
+ ```
26
+
27
+ The command shows the current project assignment for every proposed GitHub
28
+ repo. When the project is not in the workspace yet, it lists the workspace's
29
+ projects and offers to create the named one from the terminal; where the API
30
+ has no such action it prints the project link instead, as before. Repository
31
+ assignment stays in the browser, so the command prints the GitHub App and
32
+ repo-assignment links and, in an interactive terminal, opens the page. The CLI
33
+ then reads `resolve-repos` until every proposed repo reports
34
+ `project_slug: "payments"`. A repo connected to another project remains
35
+ pending. In a noninteractive shell, an unmet target prints the same links and
36
+ exits with status 1 without writing the local setup. Omit `--project` to keep
37
+ the normal workspace init flow.
38
+
39
+ Credentials live in `$XDG_CONFIG_HOME/carrick/credentials.json`, falling back
40
+ to `~/.config/carrick/` on macOS and Linux or `%APPDATA%\carrick\` on Windows.
41
+ The file is written with mode 0600. `carrick logout` removes it; unset
42
+ `CARRICK_TOKEN` separately if you set that override. Revoke issued keys at
43
+ [your account](https://app.carrick.tools/account).
44
+
45
+ Rust derives the same services for CI, local indexing and `init`. An existing
46
+ `carrick.json` is authoritative. When it is missing, `init` presents workspace
47
+ packages and their compiler configuration, then creates the config once.
48
+ Review service boundaries and shared source includes before committing it.
49
+ Repeated init preserves existing config bytes, including hand edits.
50
+
51
+ Workspace detection handles a repo root or immediate sibling repositories.
52
+ An optional `carrick-workspace.json` adds paths through `repos` and removes
53
+ directory names through `exclude`; init preserves this file and does not
54
+ create one. `carrick derive --workspace . --json` previews the Rust proposal
55
+ without writing files or scanning.
56
+
57
+ Deno projects use their existing `deno.json` or `deno.jsonc` and require Deno
58
+ 2.9.4 or newer on PATH. Before local indexing, prepare their dependencies with
59
+ `deno install --frozen --node-modules-dir=none` from the Deno workspace root;
60
+ this prevents npm lifecycle scripts from running even when `allowScripts`
61
+ authorizes them. Generate any application-owned declarations through the
62
+ project's normal build before indexing.
63
+
64
+ Deno services normally omit `tsconfig`. An explicit ordinary TypeScript config
65
+ selects the TypeScript path; an explicit Deno config must be the nearest Deno
66
+ manifest, with `deno.json` taking precedence over `deno.jsonc`. Import maps must
67
+ be local files.
17
68
 
18
69
  ## What it installs
19
70
 
20
- One package. The scanner is a Rust binary that arrives as
71
+ The scanner is a Rust binary that arrives as
21
72
  `@carrick-tools/cli-<platform>-<arch>`, an optional dependency npm resolves by
22
73
  `os` and `cpu`; the type sidecar, the language server and the hook scripts come
23
- with this package. There is no postinstall step and nothing is downloaded after
24
- the install, so `--ignore-scripts` works.
74
+ with this package. Installing Carrick requires no postinstall script, so
75
+ `--ignore-scripts` works. Dependency preparation and type checking during indexing
76
+ can download registry packages; `index` and `refresh` can also download
77
+ authenticated hosted indexes.
25
78
 
26
- Node 24 or newer, because the sidecar that resolves your request and response
27
- types runs on it.
79
+ Node 24 or newer is required because the sidecar resolves request and response
80
+ types in a Node process.
28
81
 
29
82
  ## The commands
30
83
 
31
84
  | Command | What it does |
32
85
  |---|---|
33
- | `carrick init` | The repo list, the first index, the agent hooks, and the lines it cannot run for you |
34
- | `carrick index` | Scan every repo in `carrick-workspace.json` and write `.carrick/` |
86
+ | `carrick login` | Authorise a Carrick workspace in the browser, or verify `CARRICK_TOKEN` |
87
+ | `carrick logout` | Remove the saved local credential |
88
+ | `carrick init [--project SLUG]` | The repo list, optional project-assignment verification, the first index, the agent hooks, and the lines it cannot run for you |
89
+ | `carrick index` | Derive the workspace, apply optional repo overrides and write `.carrick/` |
35
90
  | `carrick refresh [--service X]` | Re-scan one repo, or all of them, and re-join |
36
91
  | `carrick check <file>` | What the index knows about that file, verdicts included |
37
92
  | `carrick touch <file>` | The same, without the verdicts |
@@ -51,28 +106,64 @@ for the machine-readable shape, pinned in
51
106
  - **Claude Code**: the hooks `carrick init` writes deliver on the edit itself.
52
107
  `claude --plugin-dir <carrick checkout>/plugin` adds the language server too.
53
108
  - **VS Code, Cursor, Windsurf**: the `carrick-tools.carrick` extension is a
54
- client on `carrick lsp --stdio`. Editor-hosted agents read the Problems panel
55
- after their own edits.
109
+ client on `carrick lsp --stdio` and publishes diagnostics in the Problems
110
+ panel. Whether an editor-hosted agent reads those diagnostics depends on its
111
+ integration and configuration.
56
112
  - **Neovim, Helix, Zed, JetBrains**: any LSP client, on the same command.
57
113
  - **Anything else**: `carrick check <file>` on demand, and the pull request
58
114
  check in CI.
59
115
 
60
- ## What a local index holds
116
+ ## In your editor
117
+
118
+ Go to definition on a call to another service jumps to the handler that serves
119
+ it, in the other repo. On a route it lists the call sites that reach it, and
120
+ the editor shows the picker. Carrick answers only inside a row the index holds,
121
+ and only when the file on the other side is on this disk, so every other jump
122
+ falls through to TypeScript exactly as it did before.
61
123
 
62
- Deterministic rows: file-based and descriptor routes, class-controller routes,
63
- imported-member calls, GraphQL schema and document rows, socket and pub/sub
64
- operations, and the types on both sides of them. A route registered on a typed
65
- receiver, and a call whose URL is assembled at the call site, are classified in
66
- the hosted index and are absent here — every answer says how many of those it
67
- counted and did not classify, so a thin answer never reads as an empty one.
124
+ ## Settings
68
125
 
69
- The hosted index covers every repo in your organisation on its main branch,
70
- including the ones you do not have checked out, and answers over MCP:
126
+ One switch per surface. In VS Code they are settings; any other LSP client
127
+ sends the same keys, with the `carrick.` prefix stripped, as
128
+ `initializationOptions`.
129
+
130
+ | Setting | Default | What it turns off |
131
+ |---|---|---|
132
+ | `carrick.binary` | the `carrick` on PATH | — the path to the CLI, not a surface |
133
+ | `carrick.diagnostics` | on | The verdicts in the Problems panel |
134
+ | `carrick.definition` | on | Cross-repo go to definition. Off means Carrick answers nothing and your other definition providers are untouched |
135
+ | `carrick.boundary` | on | The boundary: the status bar item, or the file-level row in a client without one |
136
+
137
+ `CARRICK_CHANNEL=off` in the environment turns off delivery altogether.
138
+
139
+ ## What a local index holds
140
+
141
+ A local index holds deterministic routes, calls, protocol operations and their
142
+ types, together with eligible hosted model answers. `index` and `refresh` read
143
+ authenticated hosted indexes and replay answers for unchanged files when the
144
+ hosted commit is available locally and cache versions match. Changed files
145
+ keep their local facts while their hosted model answers are withheld. Boundary
146
+ rows report enrichment status and remaining unclassified candidates; no model
147
+ runs on the local machine.
148
+
149
+ Hosted queries cover connected, indexed repositories in the selected Carrick
150
+ project, including repositories absent from this disk, using their most recent
151
+ default-branch indexes. They are available over MCP, and `carrick init`
152
+ connects the agent clients it finds on the machine: Claude Code through
153
+ `claude mcp add`, and Cursor, Windsurf and VS Code by adding a `carrick` server
154
+ to the client's own configuration file, leaving every other entry in it alone.
155
+ A machine with none of them is given the line to run:
71
156
 
72
157
  ```
73
158
  claude mcp add --scope user --transport http carrick https://api.carrick.tools/mcp
74
159
  ```
75
160
 
161
+ The first scan on a repository's default branch writes its hosted index. A
162
+ Claude Code session opened in a workspace still waiting for one starts
163
+ `carrick refresh` in the background, at most once an hour
164
+ (`CARRICK_REFRESH_COOLDOWN_MS` states another gap), so the hosted rows arrive
165
+ without a command to remember.
166
+
76
167
  ## Licence
77
168
 
78
169
  Elastic License 2.0. See
package/bin/carrick.mjs CHANGED
@@ -99,8 +99,11 @@ function extraHelp() {
99
99
  "",
100
100
  "This package also carries the commands that put those answers where you work:",
101
101
  "",
102
- " carrick init set up this folder: the repos to index, the first",
103
- " index, and the editor and agent wiring",
102
+ " carrick login sign in to a Carrick workspace",
103
+ " carrick logout remove the saved local credential",
104
+ " carrick init [--project X] set up this folder: the repos to index, the first",
105
+ " index, and the editor and agent wiring; with a",
106
+ " project, verify every repo is assigned to it",
104
107
  " carrick lsp --stdio the language server, for an editor or an agent",
105
108
  " that speaks LSP",
106
109
  " carrick hook post-edit Claude Code PostToolUse hook (reads the tool",
@@ -112,6 +115,14 @@ function extraHelp() {
112
115
  }
113
116
 
114
117
  switch (command) {
118
+ case "login": {
119
+ const { login } = await import("../dist/auth/run.js");
120
+ process.exit(await login(rest));
121
+ }
122
+ case "logout": {
123
+ const { logout } = await import("../dist/auth/run.js");
124
+ process.exit(logout(rest));
125
+ }
115
126
  case "lsp": {
116
127
  await pointAtNativeBinary();
117
128
  await import("../dist/server.js");
@@ -0,0 +1,14 @@
1
+ export declare const API_BASE = "https://api.carrick.tools";
2
+ export declare const APP_BASE = "https://app.carrick.tools";
3
+ /** Shared with the native read client. Tokens expire by server revocation. */
4
+ export type Credential = {
5
+ api_base: string;
6
+ token: string;
7
+ workspace_slug: string | null;
8
+ obtained_at: string;
9
+ };
10
+ export declare function credentialPath(env?: NodeJS.ProcessEnv): string;
11
+ export declare function readCredential(env?: NodeJS.ProcessEnv): Credential | null;
12
+ export declare function saveCredential(token: string, workspace: string | null, env?: NodeJS.ProcessEnv): void;
13
+ /** Removes the local credential only; dashboard revocation is a separate action. */
14
+ export declare function removeCredential(env?: NodeJS.ProcessEnv): boolean;
@@ -0,0 +1,86 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ export const API_BASE = "https://api.carrick.tools";
5
+ export const APP_BASE = "https://app.carrick.tools";
6
+ export function credentialPath(env = process.env) {
7
+ const base = env["XDG_CONFIG_HOME"] ||
8
+ (process.platform === "win32" ? env["APPDATA"] : undefined) ||
9
+ path.join(env["HOME"] || os.homedir(), ".config");
10
+ if (!path.isAbsolute(base))
11
+ throw new Error("Carrick's configuration directory must be an absolute path.");
12
+ return path.join(base, "carrick", "credentials.json");
13
+ }
14
+ export function readCredential(env = process.env) {
15
+ if (env["CARRICK_TOKEN"] !== undefined) {
16
+ const token = env["CARRICK_TOKEN"];
17
+ if (!token || /\s/.test(token))
18
+ throw new Error("CARRICK_TOKEN is empty or malformed. Set it to a Carrick token or unset it and run carrick login.");
19
+ return { api_base: API_BASE, token, workspace_slug: null, obtained_at: "" };
20
+ }
21
+ const file = credentialPath(env);
22
+ let fd;
23
+ try {
24
+ fd = fs.openSync(file, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
25
+ }
26
+ catch (error) {
27
+ if (error.code === "ENOENT")
28
+ return null;
29
+ throw new Error("Could not read Carrick credentials. Run carrick login.");
30
+ }
31
+ try {
32
+ const stat = fs.fstatSync(fd);
33
+ if (!stat.isFile() || (process.platform !== "win32" && (stat.mode & 0o077) !== 0))
34
+ throw new Error();
35
+ const value = JSON.parse(fs.readFileSync(fd, "utf8"));
36
+ if (typeof value !== "object" || value === null)
37
+ throw new Error();
38
+ const credential = value;
39
+ if (credential.api_base !== API_BASE || typeof credential.token !== "string" ||
40
+ !credential.token || /\s/.test(credential.token) ||
41
+ !(credential.workspace_slug === null || typeof credential.workspace_slug === "string") ||
42
+ typeof credential.obtained_at !== "string")
43
+ throw new Error();
44
+ return credential;
45
+ }
46
+ catch {
47
+ throw new Error("Carrick credentials are invalid or not private. Run carrick login.");
48
+ }
49
+ finally {
50
+ fs.closeSync(fd);
51
+ }
52
+ }
53
+ export function saveCredential(token, workspace, env = process.env) {
54
+ if (!token || /\s/.test(token))
55
+ throw new Error("The authorization server returned an invalid token.");
56
+ const file = credentialPath(env);
57
+ const directory = path.dirname(file);
58
+ fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
59
+ if (!fs.lstatSync(directory).isDirectory())
60
+ throw new Error("Carrick's credential directory must not be a symbolic link.");
61
+ if (process.platform !== "win32")
62
+ fs.chmodSync(directory, 0o700);
63
+ const temporary = fs.mkdtempSync(path.join(directory, ".login-"));
64
+ try {
65
+ const pending = path.join(temporary, "credentials.json");
66
+ const credential = { api_base: API_BASE, token, workspace_slug: workspace, obtained_at: new Date().toISOString() };
67
+ fs.writeFileSync(pending, `${JSON.stringify(credential, null, 2)}\n`, { mode: 0o600, flag: "wx" });
68
+ fs.renameSync(pending, file);
69
+ }
70
+ finally {
71
+ fs.rmSync(temporary, { recursive: true, force: true });
72
+ }
73
+ }
74
+ /** Removes the local credential only; dashboard revocation is a separate action. */
75
+ export function removeCredential(env = process.env) {
76
+ try {
77
+ fs.unlinkSync(credentialPath(env));
78
+ return true;
79
+ }
80
+ catch (error) {
81
+ if (error.code === "ENOENT")
82
+ return false;
83
+ throw new Error("Could not remove Carrick's credential file.");
84
+ }
85
+ }
86
+ //# sourceMappingURL=credentials.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credentials.js","sourceRoot":"","sources":["../../src/auth/credentials.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /** Launch a URL as an argument, never through a shell. */
2
+ export declare function openBrowser(url: string): Promise<boolean>;
3
+ export type OAuthOptions = {
4
+ fetch?: typeof fetch;
5
+ open?: (url: string) => Promise<boolean>;
6
+ say?: (message: string) => void;
7
+ timeoutMs?: number;
8
+ signal?: AbortSignal;
9
+ };
10
+ /** RFC 8252 loopback + S256 PKCE against Carrick's existing public OAuth client flow. */
11
+ export declare function authorize(options?: OAuthOptions): Promise<string>;
@@ -0,0 +1,119 @@
1
+ import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
2
+ import http from "node:http";
3
+ import { spawn } from "node:child_process";
4
+ import { APP_BASE, API_BASE } from "./credentials.js";
5
+ /** Launch a URL as an argument, never through a shell. */
6
+ export async function openBrowser(url) {
7
+ const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "rundll32.exe" : "xdg-open";
8
+ const args = process.platform === "win32" ? ["url.dll,FileProtocolHandler", url] : [url];
9
+ return await new Promise((resolve) => {
10
+ const child = spawn(command, args, { stdio: "ignore" });
11
+ child.on("error", () => resolve(false));
12
+ child.on("exit", (code) => resolve(code === 0));
13
+ });
14
+ }
15
+ /** RFC 8252 loopback + S256 PKCE against Carrick's existing public OAuth client flow. */
16
+ export async function authorize(options = {}) {
17
+ const request = options.fetch ?? fetch;
18
+ const say = options.say ?? ((message) => process.stdout.write(`${message}\n`));
19
+ const signal = AbortSignal.any([AbortSignal.timeout(options.timeoutMs ?? 5 * 60_000), ...(options.signal ? [options.signal] : [])]);
20
+ const verifier = randomBytes(32).toString("base64url");
21
+ const state = randomBytes(32).toString("base64url");
22
+ let settle;
23
+ const received = new Promise((resolve) => { settle = resolve; });
24
+ let consumed = false;
25
+ const server = http.createServer((req, res) => {
26
+ res.setHeader("Cache-Control", "no-store");
27
+ res.setHeader("Content-Type", "text/plain; charset=utf-8");
28
+ let url;
29
+ try {
30
+ url = new URL(req.url ?? "/", "http://127.0.0.1");
31
+ }
32
+ catch {
33
+ res.writeHead(400).end("Invalid authorization callback.");
34
+ return;
35
+ }
36
+ const supplied = url.searchParams.get("state") ?? "";
37
+ if (consumed || req.method !== "GET" || url.pathname !== "/callback" ||
38
+ url.searchParams.getAll("state").length !== 1 ||
39
+ Buffer.byteLength(supplied) !== Buffer.byteLength(state) ||
40
+ !timingSafeEqual(Buffer.from(supplied), Buffer.from(state))) {
41
+ res.writeHead(400).end("Invalid authorization callback.");
42
+ return;
43
+ }
44
+ if (url.searchParams.has("error")) {
45
+ consumed = true;
46
+ res.writeHead(400).end("Carrick authorization was declined. Return to your terminal.");
47
+ settle(new Error("Carrick authorization was declined. Run carrick login to try again."));
48
+ return;
49
+ }
50
+ const code = url.searchParams.get("code");
51
+ if (!code || url.searchParams.getAll("code").length !== 1) {
52
+ res.writeHead(400).end("Missing authorization code.");
53
+ return;
54
+ }
55
+ consumed = true;
56
+ res.end("Authorization received. Return to your terminal to finish Carrick login.");
57
+ settle(code);
58
+ });
59
+ server.requestTimeout = 10_000;
60
+ const cancel = () => { settle(new Error("Carrick login was cancelled or timed out. Run carrick login to try again.")); };
61
+ signal.addEventListener("abort", cancel, { once: true });
62
+ try {
63
+ signal.throwIfAborted();
64
+ await new Promise((resolve, reject) => {
65
+ server.once("error", reject);
66
+ server.listen(0, "127.0.0.1", resolve);
67
+ });
68
+ const address = server.address();
69
+ if (!address || typeof address === "string")
70
+ throw new Error("Could not open the Carrick login callback.");
71
+ const redirect = `http://127.0.0.1:${address.port}/callback`;
72
+ const registration = await request(`${APP_BASE}/oauth/register`, {
73
+ method: "POST", redirect: "error", signal,
74
+ headers: { "Content-Type": "application/json" },
75
+ body: JSON.stringify({ client_name: "Carrick CLI", redirect_uris: [redirect], token_endpoint_auth_method: "none", grant_types: ["authorization_code"], response_types: ["code"], scope: "mcp" }),
76
+ });
77
+ if (!registration.ok)
78
+ throw new Error(`Carrick client registration failed (HTTP ${registration.status}).`);
79
+ const registered = await registration.json();
80
+ if (typeof registered.client_id !== "string" || !registered.client_id)
81
+ throw new Error("Carrick registration returned no client ID.");
82
+ const url = new URL(`${APP_BASE}/oauth/authorize`);
83
+ url.search = new URLSearchParams({ response_type: "code", client_id: registered.client_id, redirect_uri: redirect, scope: "mcp", resource: `${API_BASE}/mcp`, state, code_challenge: createHash("sha256").update(verifier).digest("base64url"), code_challenge_method: "S256" }).toString();
84
+ say(`Sign in to Carrick in your browser:\n${url}`);
85
+ // Opening a browser is best effort. A manual browser can finish the same callback.
86
+ void (options.open ?? openBrowser)(url.toString()).catch(() => false);
87
+ const code = await received;
88
+ if (code instanceof Error)
89
+ throw code;
90
+ const result = await request(`${APP_BASE}/oauth/token`, {
91
+ method: "POST", redirect: "error", signal,
92
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
93
+ body: new URLSearchParams({ grant_type: "authorization_code", client_id: registered.client_id, redirect_uri: redirect, code, code_verifier: verifier, resource: `${API_BASE}/mcp` }),
94
+ });
95
+ if (!result.ok)
96
+ throw new Error(`Carrick token exchange failed (HTTP ${result.status}). Run carrick login again.`);
97
+ const body = await result.json();
98
+ if (typeof body.access_token !== "string" || !body.access_token || /\s/.test(body.access_token) ||
99
+ typeof body.token_type !== "string" || body.token_type.toLowerCase() !== "bearer" || body.scope !== "mcp") {
100
+ throw new Error("Carrick returned an invalid OAuth token response.");
101
+ }
102
+ return body.access_token;
103
+ }
104
+ catch (error) {
105
+ if (signal.aborted)
106
+ throw new Error("Carrick login was cancelled or timed out. Run carrick login to try again.");
107
+ // Never include a remote body, token, verifier or callback URL in an error.
108
+ if (error instanceof Error && error.message.startsWith("Carrick"))
109
+ throw error;
110
+ throw new Error("Carrick login could not complete. Check the connection and loopback listener, then run carrick login.");
111
+ }
112
+ finally {
113
+ signal.removeEventListener("abort", cancel);
114
+ server.closeAllConnections();
115
+ if (server.listening)
116
+ await new Promise((resolve) => server.close(() => resolve()));
117
+ }
118
+ }
119
+ //# sourceMappingURL=oauth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth.js","sourceRoot":"","sources":["../../src/auth/oauth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,139 @@
1
+ import { z } from "zod";
2
+ declare const response: z.ZodObject<{
3
+ schema: z.ZodLiteral<"carrick.resolve-repos/0">;
4
+ workspace: z.ZodObject<{
5
+ slug: z.ZodString;
6
+ billing_tier: z.ZodEnum<["free", "cross_repo", "paid"]>;
7
+ installed: z.ZodBoolean;
8
+ }, "strip", z.ZodTypeAny, {
9
+ slug: string;
10
+ billing_tier: "free" | "cross_repo" | "paid";
11
+ installed: boolean;
12
+ }, {
13
+ slug: string;
14
+ billing_tier: "free" | "cross_repo" | "paid";
15
+ installed: boolean;
16
+ }>;
17
+ allowance_sentence: z.ZodNullable<z.ZodString>;
18
+ repos: z.ZodArray<z.ZodDiscriminatedUnion<"connected", [z.ZodObject<{
19
+ full_name: z.ZodString;
20
+ connected: z.ZodLiteral<false>;
21
+ }, "strip", z.ZodTypeAny, {
22
+ connected: false;
23
+ full_name: string;
24
+ }, {
25
+ connected: false;
26
+ full_name: string;
27
+ }>, z.ZodObject<{
28
+ full_name: z.ZodString;
29
+ connected: z.ZodLiteral<true>;
30
+ project_id: z.ZodString;
31
+ project_slug: z.ZodString;
32
+ services: z.ZodArray<z.ZodObject<{
33
+ service: z.ZodString;
34
+ hash: z.ZodNullable<z.ZodString>;
35
+ updated_at: z.ZodNullable<z.ZodString>;
36
+ scanner_version: z.ZodNullable<z.ZodString>;
37
+ }, "strip", z.ZodTypeAny, {
38
+ service: string;
39
+ scanner_version: string | null;
40
+ hash: string | null;
41
+ updated_at: string | null;
42
+ }, {
43
+ service: string;
44
+ scanner_version: string | null;
45
+ hash: string | null;
46
+ updated_at: string | null;
47
+ }>, "many">;
48
+ }, "strip", z.ZodTypeAny, {
49
+ services: {
50
+ service: string;
51
+ scanner_version: string | null;
52
+ hash: string | null;
53
+ updated_at: string | null;
54
+ }[];
55
+ connected: true;
56
+ full_name: string;
57
+ project_id: string;
58
+ project_slug: string;
59
+ }, {
60
+ services: {
61
+ service: string;
62
+ scanner_version: string | null;
63
+ hash: string | null;
64
+ updated_at: string | null;
65
+ }[];
66
+ connected: true;
67
+ full_name: string;
68
+ project_id: string;
69
+ project_slug: string;
70
+ }>]>, "many">;
71
+ project_repos: z.ZodArray<z.ZodObject<{
72
+ project_slug: z.ZodString;
73
+ repos: z.ZodArray<z.ZodString, "many">;
74
+ }, "strip", z.ZodTypeAny, {
75
+ project_slug: string;
76
+ repos: string[];
77
+ }, {
78
+ project_slug: string;
79
+ repos: string[];
80
+ }>, "many">;
81
+ }, "strip", z.ZodTypeAny, {
82
+ schema: "carrick.resolve-repos/0";
83
+ workspace: {
84
+ slug: string;
85
+ billing_tier: "free" | "cross_repo" | "paid";
86
+ installed: boolean;
87
+ };
88
+ allowance_sentence: string | null;
89
+ repos: ({
90
+ connected: false;
91
+ full_name: string;
92
+ } | {
93
+ services: {
94
+ service: string;
95
+ scanner_version: string | null;
96
+ hash: string | null;
97
+ updated_at: string | null;
98
+ }[];
99
+ connected: true;
100
+ full_name: string;
101
+ project_id: string;
102
+ project_slug: string;
103
+ })[];
104
+ project_repos: {
105
+ project_slug: string;
106
+ repos: string[];
107
+ }[];
108
+ }, {
109
+ schema: "carrick.resolve-repos/0";
110
+ workspace: {
111
+ slug: string;
112
+ billing_tier: "free" | "cross_repo" | "paid";
113
+ installed: boolean;
114
+ };
115
+ allowance_sentence: string | null;
116
+ repos: ({
117
+ connected: false;
118
+ full_name: string;
119
+ } | {
120
+ services: {
121
+ service: string;
122
+ scanner_version: string | null;
123
+ hash: string | null;
124
+ updated_at: string | null;
125
+ }[];
126
+ connected: true;
127
+ full_name: string;
128
+ project_id: string;
129
+ project_slug: string;
130
+ })[];
131
+ project_repos: {
132
+ project_slug: string;
133
+ repos: string[];
134
+ }[];
135
+ }>;
136
+ export type ResolvedRepos = z.infer<typeof response>;
137
+ /** Metadata only. This client never uploads source or requests model work. */
138
+ export declare function resolveRepos(token: string, repos: string[], request?: typeof fetch, signal?: AbortSignal): Promise<ResolvedRepos>;
139
+ export {};
@@ -0,0 +1,44 @@
1
+ import { z } from "zod";
2
+ import { API_BASE } from "./credentials.js";
3
+ const service = z.object({ service: z.string(), hash: z.string().nullable(), updated_at: z.string().nullable(), scanner_version: z.string().nullable() });
4
+ const repository = z.discriminatedUnion("connected", [
5
+ z.object({ full_name: z.string(), connected: z.literal(false) }),
6
+ z.object({ full_name: z.string(), connected: z.literal(true), project_id: z.string(), project_slug: z.string(), services: z.array(service) }),
7
+ ]);
8
+ const response = z.object({
9
+ schema: z.literal("carrick.resolve-repos/0"),
10
+ workspace: z.object({ slug: z.string().regex(/^[a-zA-Z0-9_-]+$/), billing_tier: z.enum(["free", "cross_repo", "paid"]), installed: z.boolean() }),
11
+ allowance_sentence: z.string().nullable(),
12
+ repos: z.array(repository),
13
+ project_repos: z.array(z.object({ project_slug: z.string(), repos: z.array(z.string()) })),
14
+ });
15
+ /** Metadata only. This client never uploads source or requests model work. */
16
+ export async function resolveRepos(token, repos, request = fetch, signal) {
17
+ let result;
18
+ try {
19
+ result = await request(`${API_BASE}/types/check-or-upload`, {
20
+ method: "POST", redirect: "error", signal: AbortSignal.any([AbortSignal.timeout(30_000), ...(signal ? [signal] : [])]),
21
+ headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
22
+ body: JSON.stringify({ action: "resolve-repos", repos }),
23
+ });
24
+ }
25
+ catch {
26
+ throw new Error("Could not reach Carrick to verify the workspace. Check the connection and retry.");
27
+ }
28
+ if (result.status === 401)
29
+ throw new Error("Carrick rejected this credential. Run carrick login (unset CARRICK_TOKEN first if it overrides your login).");
30
+ if (!result.ok)
31
+ throw new Error(`Carrick workspace lookup failed (HTTP ${result.status}).`);
32
+ let payload;
33
+ try {
34
+ payload = await result.json();
35
+ }
36
+ catch {
37
+ throw new Error("Carrick workspace lookup returned invalid JSON.");
38
+ }
39
+ const parsed = response.safeParse(payload);
40
+ if (!parsed.success)
41
+ throw new Error("Carrick workspace lookup returned an unsupported or incomplete schema. The server must support carrick.resolve-repos/0.");
42
+ return parsed.data;
43
+ }
44
+ //# sourceMappingURL=read.js.map