carrick 0.3.58 → 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.
- package/README.md +94 -26
- package/bin/carrick.mjs +13 -2
- package/dist/auth/credentials.d.ts +14 -0
- package/dist/auth/credentials.js +86 -0
- package/dist/auth/credentials.js.map +1 -0
- package/dist/auth/oauth.d.ts +11 -0
- package/dist/auth/oauth.js +119 -0
- package/dist/auth/oauth.js.map +1 -0
- package/dist/auth/read.d.ts +139 -0
- package/dist/auth/read.js +44 -0
- package/dist/auth/read.js.map +1 -0
- package/dist/auth/run.d.ts +12 -0
- package/dist/auth/run.js +68 -0
- package/dist/auth/run.js.map +1 -0
- package/dist/contract.d.ts +10 -0
- package/dist/contract.js +3 -0
- package/dist/contract.js.map +1 -1
- package/dist/definition.js +2 -2
- package/dist/definition.js.map +1 -1
- package/dist/diagnostics.d.ts +35 -1
- package/dist/diagnostics.js +52 -11
- package/dist/diagnostics.js.map +1 -1
- package/dist/hook/refresh.d.ts +32 -0
- package/dist/hook/refresh.js +138 -0
- package/dist/hook/refresh.js.map +1 -0
- package/dist/hook/session-start.js +13 -0
- package/dist/hook/session-start.js.map +1 -1
- package/dist/init/connect.d.ts +17 -0
- package/dist/init/connect.js +120 -0
- package/dist/init/connect.js.map +1 -0
- package/dist/init/mcp.d.ts +50 -0
- package/dist/init/mcp.js +235 -0
- package/dist/init/mcp.js.map +1 -0
- package/dist/init/projects.d.ts +46 -0
- package/dist/init/projects.js +137 -0
- package/dist/init/projects.js.map +1 -0
- package/dist/init/repos.d.ts +113 -19
- package/dist/init/repos.js +60 -39
- package/dist/init/repos.js.map +1 -1
- package/dist/init/run.d.ts +16 -0
- package/dist/init/run.js +243 -61
- package/dist/init/run.js.map +1 -1
- package/dist/init/settings.d.ts +1 -1
- package/dist/init/settings.js +1 -1
- package/dist/init/settings.js.map +1 -1
- package/dist/server.js +173 -16
- package/dist/server.js.map +1 -1
- package/package.json +6 -6
- package/sidecar/dist/src/capture/deno-project.d.ts +41 -0
- package/sidecar/dist/src/capture/deno-project.js +513 -0
- package/sidecar/dist/src/capture/index.d.ts +1 -0
- package/sidecar/dist/src/capture/index.js +60 -19
- package/sidecar/dist/src/capture/self-check.d.ts +2 -0
- package/sidecar/dist/src/capture/self-check.js +3 -2
- package/sidecar/dist/src/project-loader.js +22 -1
- package/dist/init/identity.d.ts +0 -20
- package/dist/init/identity.js +0 -60
- package/dist/init/identity.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,37 +1,92 @@
|
|
|
1
1
|
# carrick
|
|
2
2
|
|
|
3
|
-
Carrick indexes
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
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
|
|
16
|
-
|
|
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
|
-
|
|
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.
|
|
24
|
-
|
|
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
|
|
27
|
-
types
|
|
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
|
|
34
|
-
| `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,8 +106,9 @@ 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
|
|
55
|
-
|
|
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.
|
|
@@ -82,20 +138,32 @@ sends the same keys, with the `carrick.` prefix stripped, as
|
|
|
82
138
|
|
|
83
139
|
## What a local index holds
|
|
84
140
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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:
|
|
94
156
|
|
|
95
157
|
```
|
|
96
158
|
claude mcp add --scope user --transport http carrick https://api.carrick.tools/mcp
|
|
97
159
|
```
|
|
98
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
|
+
|
|
99
167
|
## Licence
|
|
100
168
|
|
|
101
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
|
|
103
|
-
"
|
|
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":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACvE,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEtD,0DAA0D;AAC1D,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,GAAW;IAC3C,MAAM,OAAO,GAAG,OAAO,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,UAAU,CAAC;IACpH,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,6BAA6B,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACzF,OAAO,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QACnC,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QACxD,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;QACxC,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;AACL,CAAC;AAUD,yFAAyF;AACzF,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,UAAwB,EAAE;IACxD,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,KAAK,CAAC;IACvC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,OAAe,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,OAAO,IAAI,CAAC,CAAC,CAAC;IACvF,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,SAAS,IAAI,CAAC,GAAG,MAAM,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACpI,MAAM,QAAQ,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IACvD,MAAM,KAAK,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IACpD,IAAI,MAAuC,CAAC;IAC5C,MAAM,QAAQ,GAAG,IAAI,OAAO,CAAiB,CAAC,OAAO,EAAE,EAAE,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IACjF,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QAC5C,GAAG,CAAC,SAAS,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC;QAC3C,GAAG,CAAC,SAAS,CAAC,cAAc,EAAE,2BAA2B,CAAC,CAAC;QAC3D,IAAI,GAAQ,CAAC;QACb,IAAI,CAAC;YAAC,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,EAAE,kBAAkB,CAAC,CAAC;QAAC,CAAC;QAC1D,MAAM,CAAC;YAAC,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,iCAAiC,CAAC,CAAC;YAAC,OAAO;QAAC,CAAC;QAC5E,MAAM,QAAQ,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;QACrD,IAAI,QAAQ,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,IAAI,GAAG,CAAC,QAAQ,KAAK,WAAW;YAChE,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,KAAK,CAAC;YAC7C,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,KAAK,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC;YACxD,CAAC,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;YAChE,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,iCAAiC,CAAC,CAAC;YAC1D,OAAO;QACT,CAAC;QACD,IAAI,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YAClC,QAAQ,GAAG,IAAI,CAAC;YAChB,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,8DAA8D,CAAC,CAAC;YACvF,MAAM,CAAC,IAAI,KAAK,CAAC,qEAAqE,CAAC,CAAC,CAAC;YACzF,OAAO;QACT,CAAC;QACD,MAAM,IAAI,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC1C,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1D,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAC;YACtD,OAAO;QACT,CAAC;QACD,QAAQ,GAAG,IAAI,CAAC;QAChB,GAAG,CAAC,GAAG,CAAC,0EAA0E,CAAC,CAAC;QACpF,MAAM,CAAC,IAAI,CAAC,CAAC;IACf,CAAC,CAAC,CAAC;IACH,MAAM,CAAC,cAAc,GAAG,MAAM,CAAC;IAC/B,MAAM,MAAM,GAAG,GAAS,EAAE,GAAG,MAAM,CAAC,IAAI,KAAK,CAAC,2EAA2E,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/H,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IACzD,IAAI,CAAC;QACH,MAAM,CAAC,cAAc,EAAE,CAAC;QACxB,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC1C,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YAC7B,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC;QACzC,CAAC,CAAC,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;QACjC,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ;YAAE,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;QAC3G,MAAM,QAAQ,GAAG,oBAAoB,OAAO,CAAC,IAAI,WAAW,CAAC;QAC7D,MAAM,YAAY,GAAG,MAAM,OAAO,CAAC,GAAG,QAAQ,iBAAiB,EAAE;YAC/D,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM;YACzC,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,WAAW,EAAE,aAAa,EAAE,aAAa,EAAE,CAAC,QAAQ,CAAC,EAAE,0BAA0B,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,oBAAoB,CAAC,EAAE,cAAc,EAAE,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;SACjM,CAAC,CAAC;QACH,IAAI,CAAC,YAAY,CAAC,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,4CAA4C,YAAY,CAAC,MAAM,IAAI,CAAC,CAAC;QAC3G,MAAM,UAAU,GAAG,MAAM,YAAY,CAAC,IAAI,EAA6B,CAAC;QACxE,IAAI,OAAO,UAAU,CAAC,SAAS,KAAK,QAAQ,IAAI,CAAC,UAAU,CAAC,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;QACtI,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,QAAQ,kBAAkB,CAAC,CAAC;QACnD,GAAG,CAAC,MAAM,GAAG,IAAI,eAAe,CAAC,EAAE,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,CAAC,SAAS,EAAE,YAAY,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,QAAQ,MAAM,EAAE,KAAK,EAAE,cAAc,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,EAAE,qBAAqB,EAAE,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC5R,GAAG,CAAC,wCAAwC,GAAG,EAAE,CAAC,CAAC;QACnD,mFAAmF;QACnF,KAAK,CAAC,OAAO,CAAC,IAAI,IAAI,WAAW,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC;QACtE,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC;QAC5B,IAAI,IAAI,YAAY,KAAK;YAAE,MAAM,IAAI,CAAC;QACtC,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,QAAQ,cAAc,EAAE;YACtD,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM;YACzC,OAAO,EAAE,EAAE,cAAc,EAAE,mCAAmC,EAAE;YAChE,IAAI,EAAE,IAAI,eAAe,CAAC,EAAE,UAAU,EAAE,oBAAoB,EAAE,SAAS,EAAE,UAAU,CAAC,SAAS,EAAE,YAAY,EAAE,QAAQ,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,QAAQ,MAAM,EAAE,CAAC;SACrL,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CAAC,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,uCAAuC,MAAM,CAAC,MAAM,6BAA6B,CAAC,CAAC;QACnH,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,EAAuE,CAAC;QACtG,IAAI,OAAO,IAAI,CAAC,YAAY,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC;YAC3F,OAAO,IAAI,CAAC,UAAU,KAAK,QAAQ,IAAI,IAAI,CAAC,UAAU,CAAC,WAAW,EAAE,KAAK,QAAQ,IAAI,IAAI,CAAC,KAAK,KAAK,KAAK,EAAE,CAAC;YAC9G,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;QACvE,CAAC;QACD,OAAO,IAAI,CAAC,YAAY,CAAC;IAC3B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,MAAM,CAAC,OAAO;YAAE,MAAM,IAAI,KAAK,CAAC,2EAA2E,CAAC,CAAC;QACjH,4EAA4E;QAC5E,IAAI,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC;YAAE,MAAM,KAAK,CAAC;QAC/E,MAAM,IAAI,KAAK,CAAC,uGAAuG,CAAC,CAAC;IAC3H,CAAC;YAAS,CAAC;QACT,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC5C,MAAM,CAAC,mBAAmB,EAAE,CAAC;QAC7B,IAAI,MAAM,CAAC,SAAS;YAAE,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IAC5F,CAAC;AACH,CAAC"}
|
|
@@ -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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read.js","sourceRoot":"","sources":["../../src/auth/read.ts"],"names":[],"mappings":"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"}
|