gcb-client 0.0.0 → 0.0.2

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/lib/helper.js ADDED
@@ -0,0 +1,290 @@
1
+ /**
2
+ * The container side: a git credential helper.
3
+ *
4
+ * git invokes this as `gcb <get|store|erase>` with a small `key=value`
5
+ * description on stdin, and reads `key=value` pairs back from stdout.
6
+ *
7
+ * What it deliberately does:
8
+ * - `store` and `erase` do nothing, ever, so no credential is written to
9
+ * `~/.git-credentials` or anywhere else;
10
+ * - it refuses a request without a `path`, because a path-less request can only be
11
+ * authorized at host granularity — exactly the over-grant this design exists to prevent;
12
+ * - on any refusal it emits `quit=1` and exits 0. That is the git credential protocol's way
13
+ * of saying "stop asking helpers": an exit code alone would let git fall through to
14
+ * another helper and silently authenticate as someone else;
15
+ * - it never prints the credential to stderr, and never prints diagnostics to stdout, where
16
+ * they would corrupt the protocol;
17
+ * - it forwards the protocol and lets the *broker* decide whether plaintext is acceptable
18
+ * for that host. That decision is therefore host-side and cannot be widened from inside the
19
+ * container; the helper only rejects a protocol it cannot reason about at all.
20
+ *
21
+ * One exception to fail-closed, and it matters because the workspace is a bind mount shared
22
+ * with the host: when no broker is configured at all (`GIT_BROKER_SOCKET` unset), the helper
23
+ * behaves as if it were not installed — no output, exit 0 — so the human's own git keeps its
24
+ * own credential chain for https remotes. It refuses with `quit=1` only when it *is*
25
+ * configured (socket set, or `GIT_BROKER_REQUIRE=1`), because that means we are meant to be
26
+ * the authority. Set `GIT_BROKER_REQUIRE=1` in the container so a missing socket variable
27
+ * can never degrade into "silently rely on something else".
28
+ *
29
+ * The helper is stateless: it holds no secret and knows no allowlist. The broker decides.
30
+ */
31
+ import { readFileSync } from 'node:fs';
32
+ import net from 'node:net';
33
+ import { StringDecoder } from 'node:string_decoder';
34
+ import { homedir } from 'node:os';
35
+ import path from 'node:path';
36
+ import { asWireResponse, encodeMessage, parseMessage } from 'gcb-core';
37
+ /** Ceiling for the description git sends us. */
38
+ const MAX_INPUT_BYTES = 8 * 1024;
39
+ /** How long to wait for the broker before giving up (fail closed, fast). */
40
+ const SOCKET_TIMEOUT_MS = 10_000;
41
+ /**
42
+ * Where to look for the socket path when no environment variable is set.
43
+ *
44
+ * A file rather than only an environment variable, because git is often spawned from a
45
+ * non-login shell that never sources a profile: relying on the environment alone means pushes
46
+ * mysteriously stop working in a fresh session. Override with `GIT_BROKER_SOCKET_FILE`.
47
+ */
48
+ const DEFAULT_SOCKET_FILE = ['.config', 'git-credential-broker', 'socket'];
49
+ /**
50
+ * Parse git's credential description.
51
+ *
52
+ * @param text - the raw stdin contents.
53
+ * @returns the key/value pairs.
54
+ */
55
+ export function parseCredentialInput(text) {
56
+ const fields = {};
57
+ for (const line of String(text).split('\n')) {
58
+ if (!line)
59
+ continue;
60
+ const separator = line.indexOf('=');
61
+ if (separator <= 0)
62
+ continue;
63
+ fields[line.slice(0, separator).trim()] = line.slice(separator + 1);
64
+ }
65
+ return fields;
66
+ }
67
+ /**
68
+ * Read a stream to the end, with a size ceiling.
69
+ *
70
+ * Chunks are normalized to `Buffer` rather than assumed: `process.stdin` yields buffers, but
71
+ * any stream in string or object mode yields strings, and `Buffer.concat` rejects those.
72
+ *
73
+ * @param stream - the stream to drain.
74
+ * @param maxBytes - ceiling; exceeding it rejects.
75
+ * @returns the decoded contents.
76
+ */
77
+ export function readAll(stream, maxBytes = MAX_INPUT_BYTES) {
78
+ return new Promise((resolve, reject) => {
79
+ const chunks = [];
80
+ let size = 0;
81
+ stream.on('data', (chunk) => {
82
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
83
+ size += buffer.length;
84
+ if (size > maxBytes) {
85
+ reject(new Error('credential description is implausibly large'));
86
+ stream.destroy();
87
+ return;
88
+ }
89
+ chunks.push(buffer);
90
+ });
91
+ stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
92
+ stream.on('error', reject);
93
+ });
94
+ }
95
+ /**
96
+ * Send one request to the broker over its unix socket and await one response.
97
+ *
98
+ * @param socketPath - the broker socket path.
99
+ * @param request - the request payload.
100
+ * @param timeoutMs - how long to wait for an answer.
101
+ * @returns the parsed response.
102
+ */
103
+ export function requestOverSocket(socketPath, request, timeoutMs = SOCKET_TIMEOUT_MS) {
104
+ return new Promise((resolve, reject) => {
105
+ const socket = net.connect({ path: socketPath });
106
+ const decoder = new StringDecoder('utf8');
107
+ let buffer = '';
108
+ let settled = false;
109
+ const settle = (callback, argument) => {
110
+ if (settled)
111
+ return;
112
+ settled = true;
113
+ socket.destroy();
114
+ callback(argument);
115
+ };
116
+ socket.setTimeout(timeoutMs, () => {
117
+ settle(reject, new Error(`the broker did not answer within ${timeoutMs}ms`));
118
+ });
119
+ socket.on('connect', () => socket.write(encodeMessage(request)));
120
+ socket.on('data', (chunk) => {
121
+ buffer += decoder.write(chunk);
122
+ const newline = buffer.indexOf('\n');
123
+ if (newline < 0)
124
+ return;
125
+ const message = parseMessage(buffer.slice(0, newline));
126
+ if (message)
127
+ settle(resolve, asWireResponse(message));
128
+ else
129
+ settle(reject, new Error('the broker sent a malformed response'));
130
+ });
131
+ socket.on('error', (error) => {
132
+ settle(reject, new Error(`cannot reach the broker at ${socketPath}: ${error.code ?? error.message}`));
133
+ });
134
+ socket.on('close', () => {
135
+ settle(reject, new Error('the broker closed the connection without answering'));
136
+ });
137
+ });
138
+ }
139
+ /**
140
+ * Decide where the broker lives, in precedence order:
141
+ *
142
+ * 1. `--socket <path>` on the command line;
143
+ * 2. `GIT_BROKER_SOCKET`;
144
+ * 3. the contents of `GIT_BROKER_SOCKET_FILE`, defaulting to
145
+ * `$HOME/.config/git-credential-broker/socket`.
146
+ *
147
+ * An empty or missing result means "not configured in this environment", which the caller
148
+ * treats as "behave as if not installed".
149
+ *
150
+ * @param explicitSocket - the `--socket` argument, if any.
151
+ * @param env - the environment to read.
152
+ * @returns the resolved socket path, or undefined when nothing is configured.
153
+ */
154
+ export function resolveSocketPath(explicitSocket, env) {
155
+ if (explicitSocket)
156
+ return explicitSocket;
157
+ const fromEnv = env['GIT_BROKER_SOCKET'];
158
+ if (fromEnv)
159
+ return fromEnv;
160
+ const file = env['GIT_BROKER_SOCKET_FILE'] ?? path.join(env['HOME'] ?? homedir(), ...DEFAULT_SOCKET_FILE);
161
+ try {
162
+ const fromFile = readFileSync(file, 'utf8').trim();
163
+ if (fromFile)
164
+ return fromFile;
165
+ }
166
+ catch {
167
+ // No socket file: this environment is not configured for the broker.
168
+ }
169
+ return undefined;
170
+ }
171
+ /**
172
+ * Parse the helper's own command line.
173
+ *
174
+ * @param argv - arguments after the script name.
175
+ * @returns the parsed arguments.
176
+ */
177
+ export function parseArgs(argv) {
178
+ let operation = 'get';
179
+ let socketPath;
180
+ let strict = false;
181
+ const rest = [];
182
+ for (let index = 0; index < argv.length; index += 1) {
183
+ const argument = argv[index];
184
+ if (argument === undefined)
185
+ continue;
186
+ if (argument === '--socket') {
187
+ socketPath = argv[index + 1];
188
+ index += 1;
189
+ }
190
+ else if (argument.startsWith('--socket=')) {
191
+ socketPath = argument.slice('--socket='.length);
192
+ }
193
+ else if (argument === '--strict') {
194
+ strict = true;
195
+ }
196
+ else {
197
+ rest.push(argument);
198
+ }
199
+ }
200
+ if (rest.length > 0)
201
+ operation = rest[0];
202
+ return { operation, socketPath, strict };
203
+ }
204
+ /**
205
+ * Run the helper.
206
+ *
207
+ * Always resolves with an exit code; refusals still exit 0 on purpose (see the module
208
+ * comment) so that git stops instead of falling through to another helper.
209
+ *
210
+ * @param argv - arguments after the script name.
211
+ * @param io - injectable streams and environment.
212
+ * @returns the process exit code.
213
+ */
214
+ export async function runHelper(argv, io = {}) {
215
+ const { stdin = process.stdin, stdout = process.stdout, stderr = process.stderr, env = process.env, } = io;
216
+ const { operation, socketPath: socketArgument, strict: strictArgument } = parseArgs(argv);
217
+ // git calls `store` after a successful authentication and `erase` after a rejected one.
218
+ // Both are intentionally no-ops: nothing about a credential is ever persisted.
219
+ if (operation !== 'get')
220
+ return 0;
221
+ const socketPath = resolveSocketPath(socketArgument, env);
222
+ const strict = strictArgument || env['GIT_BROKER_REQUIRE'] === '1';
223
+ const refuse = (message) => {
224
+ stdout.write('quit=1\n');
225
+ stderr.write(`gcb: ${message}\n`);
226
+ return 0;
227
+ };
228
+ // Drain the description before deciding anything: git closes the pipe once it has
229
+ // written, and walking away without reading can surface as EPIPE on git's side.
230
+ let input;
231
+ try {
232
+ input = parseCredentialInput(await readAll(stdin));
233
+ }
234
+ catch (error) {
235
+ return refuse(`could not read the credential request: ${error.message}`);
236
+ }
237
+ // Only a configured environment is ours to police. Repository configuration lives in the
238
+ // bind-mounted workspace and is therefore shared with the host, so the human's own git may
239
+ // invoke this helper too. When we are not configured, act as if we were never installed:
240
+ // silence and exit 0 lets git move on to the next helper. Emitting `quit=1` here would stop
241
+ // that chain and break the host's own https credentials.
242
+ if (!socketPath && !strict)
243
+ return 0;
244
+ const protocol = input['protocol'];
245
+ if (protocol !== 'https' && protocol !== 'http') {
246
+ return refuse(`refusing protocol ${JSON.stringify(protocol ?? '')}: only http and https are understood`);
247
+ }
248
+ if (!input['host']) {
249
+ return refuse('the credential request names no host');
250
+ }
251
+ if (!input['path']) {
252
+ return refuse('the credential request carries no repository path, so only a host-wide credential could be issued; ' +
253
+ 'run `git config --global credential.useHttpPath true` to let the broker authorize a single repository');
254
+ }
255
+ if (!socketPath) {
256
+ return refuse('GIT_BROKER_SOCKET is not set while GIT_BROKER_REQUIRE=1, so the broker cannot be reached');
257
+ }
258
+ // Git has no per-push argument channel to the credential helper, so the helper reads its
259
+ // environment: GCB_PROXY=<url> pins a proxy, GCB_PROXY= (empty) goes direct, unset keeps the
260
+ // broker's environment. This choice only affects minting the credential; the push transport's
261
+ // own proxy is still Git's http.proxy / HTTPS_PROXY / NO_PROXY.
262
+ const configuredProxy = env['GCB_PROXY'];
263
+ const proxy = configuredProxy === undefined ? undefined : configuredProxy === '' ? null : configuredProxy;
264
+ let response;
265
+ try {
266
+ response = await requestOverSocket(socketPath, {
267
+ v: 1,
268
+ op: 'credential',
269
+ protocol: input['protocol'],
270
+ host: input['host'],
271
+ path: input['path'],
272
+ proxy,
273
+ session: env['DSH_SESSION_ID'] ?? null,
274
+ pid: process.pid,
275
+ });
276
+ }
277
+ catch (error) {
278
+ return refuse(error.message);
279
+ }
280
+ if (!response.ok) {
281
+ const detail = response.reason ? `: ${response.reason}` : '';
282
+ return refuse(`the broker refused this request (${response.code ?? 'unknown'})${detail}`);
283
+ }
284
+ if (typeof response.username !== 'string' || typeof response.password !== 'string' || !response.password) {
285
+ return refuse('the broker returned an incomplete credential');
286
+ }
287
+ stdout.write(`username=${response.username}\npassword=${response.password}\n`);
288
+ return 0;
289
+ }
290
+ //# sourceMappingURL=helper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helper.js","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"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"}
package/lib/index.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ import type { CommandBackend } from 'gcb-core';
2
+ export { runHelper } from './helper.ts';
3
+ export declare const CLIENT_HELP: string;
4
+ export declare const clientBackend: CommandBackend;
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAE/C,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAkBxC,eAAO,MAAM,WAAW,QAsB2C,CAAC;AAoCpE,eAAO,MAAM,aAAa,EAAE,cAK3B,CAAC"}
package/lib/index.js ADDED
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Container-side entry point: the git credential helper and the socket-backed commands.
3
+ *
4
+ * The host commands are not imported here; installing `gcb-client` never pulls `yaml`.
5
+ */
6
+ import { GH_ACTIONS } from 'gcb-core';
7
+ export { runHelper } from './helper.js';
8
+ /**
9
+ * The catalogued subcommands of one domain, for the help text.
10
+ *
11
+ * The dispatch below reads the same table, so this cannot list a subcommand that does not run and a
12
+ * new action shows up in the help without a second edit.
13
+ *
14
+ * @param domain - the command before the colon, e.g. `pr`.
15
+ * @returns the subcommands, joined for one usage line.
16
+ */
17
+ function cataloguedSubcommands(domain) {
18
+ return Object.values(GH_ACTIONS)
19
+ .filter((action) => action.command[0] === domain)
20
+ .map((action) => action.command[1])
21
+ .join('|');
22
+ }
23
+ export const CLIENT_HELP = `gcb-client — container-side git credential helper and broker client.
24
+
25
+ Usage: gcb <command> [options]
26
+
27
+ Commands
28
+ setup Configure this environment's git to use the broker
29
+ probe Ask the broker what it would do for a repository, without pushing
30
+ logs Read a workflow job's log through the broker
31
+ pr Use the broker's pull-request lifecycle; these run as gh actions:
32
+ ${cataloguedSubcommands('pr')}
33
+ run Run a workflow run action: ${cataloguedSubcommands('run')}
34
+ gh Run a catalogued GitHub action through the broker
35
+
36
+ As a git credential helper
37
+ gcb get|store|erase # what git calls; store and erase are no-ops
38
+
39
+ Environment
40
+ GIT_BROKER_SOCKET Broker socket path
41
+ GIT_BROKER_REQUIRE=1 Fail closed instead of behaving as if not installed when unconfigured
42
+ GIT_BROKER_SOCKET_FILE Where to read the socket path from when no variable is set
43
+ GCB_PROXY Per-push broker proxy: a URL, or empty for direct
44
+
45
+ Host commands such as init/stage/compose/diagnose are in gcb-host.`;
46
+ async function run(argv) {
47
+ const [command, ...rest] = argv;
48
+ switch (command) {
49
+ case 'setup':
50
+ return (await import('./commands/setup.js')).runSetup(rest);
51
+ case 'probe':
52
+ return (await import('./commands/probe.js')).runProbe(rest);
53
+ case 'logs':
54
+ return (await import('./commands/logs.js')).runLogs(rest);
55
+ case 'pr': {
56
+ // The catalog is the one source for an action, so the shorthand asks it rather than keeping a
57
+ // second list: a subcommand the catalog does not have stays with the flag-only lifecycle.
58
+ // Bare `pr` asks the catalog too, where `run` needs no branch: the flag-only path would only
59
+ // tell it that --repo is required.
60
+ if (rest.length === 0)
61
+ return (await import('./commands/gh.js')).runGh(['pr']);
62
+ const sub = rest[0];
63
+ if (sub === 'review' && rest.some((argument) => argument === '--id' || argument.startsWith('--id='))) {
64
+ return (await import('./commands/pr.js')).runPr(rest.slice(1));
65
+ }
66
+ if (sub !== undefined && Object.hasOwn(GH_ACTIONS, `pr:${sub}`)) {
67
+ return (await import('./commands/gh.js')).runGh(['pr', ...rest], { unreachableExitCode: 3 });
68
+ }
69
+ return (await import('./commands/pr.js')).runPr(rest);
70
+ }
71
+ case 'run':
72
+ return (await import('./commands/gh.js')).runGh(['run', ...rest]);
73
+ case 'gh':
74
+ return (await import('./commands/gh.js')).runGh(rest);
75
+ default:
76
+ process.stderr.write(`gcb: unknown client command \`${command ?? ''}\`.\n`);
77
+ return 1;
78
+ }
79
+ }
80
+ export const clientBackend = {
81
+ role: 'client',
82
+ commands: ['setup', 'probe', 'logs', 'pr', 'run', 'gh'],
83
+ help: CLIENT_HELP,
84
+ run,
85
+ };
86
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAGtC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC;;;;;;;;GAQG;AACH,SAAS,qBAAqB,CAAC,MAAc;IAC3C,OAAO,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC;SAC7B,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC;SAChD,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;SAClC,IAAI,CAAC,GAAG,CAAC,CAAC;AACf,CAAC;AAED,MAAM,CAAC,MAAM,WAAW,GAAG;;;;;;;;;eASZ,qBAAqB,CAAC,IAAI,CAAC;0CACA,qBAAqB,CAAC,KAAK,CAAC;;;;;;;;;;;;mEAYH,CAAC;AAEpE,KAAK,UAAU,GAAG,CAAC,IAAuB;IACxC,MAAM,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IAChC,QAAQ,OAAO,EAAE,CAAC;QAChB,KAAK,OAAO;YACV,OAAO,CAAC,MAAM,MAAM,CAAC,qBAAqB,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC9D,KAAK,OAAO;YACV,OAAO,CAAC,MAAM,MAAM,CAAC,qBAAqB,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC9D,KAAK,MAAM;YACT,OAAO,CAAC,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC5D,KAAK,IAAI,EAAE,CAAC;YACV,8FAA8F;YAC9F,0FAA0F;YAC1F,6FAA6F;YAC7F,mCAAmC;YACnC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,CAAC,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;YAC/E,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;YACpB,IAAI,GAAG,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,KAAK,MAAM,IAAI,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC;gBACrG,OAAO,CAAC,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YACjE,CAAC;YACD,IAAI,GAAG,KAAK,SAAS,IAAI,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,EAAE,CAAC,EAAE,CAAC;gBAChE,OAAO,CAAC,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,EAAE,EAAE,mBAAmB,EAAE,CAAC,EAAE,CAAC,CAAC;YAC/F,CAAC;YACD,OAAO,CAAC,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACxD,CAAC;QACD,KAAK,KAAK;YACR,OAAO,CAAC,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC;QACpE,KAAK,IAAI;YACP,OAAO,CAAC,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACxD;YACE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iCAAiC,OAAO,IAAI,EAAE,OAAO,CAAC,CAAC;YAC5E,OAAO,CAAC,CAAC;IACb,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,aAAa,GAAmB;IAC3C,IAAI,EAAE,QAAQ;IACd,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC;IACvD,IAAI,EAAE,WAAW;IACjB,GAAG;CACJ,CAAC"}
package/package.json CHANGED
@@ -1,5 +1,36 @@
1
1
  {
2
2
  "name": "gcb-client",
3
- "version": "0.0.0",
4
- "packageManager": "yarn@4.18.1"
3
+ "version": "0.0.2",
4
+ "description": "Container-side gcb client: credential helper, setup, and socket-backed commands.",
5
+ "license": "MIT",
6
+ "author": "JounQin <admin@1stg.me>",
7
+ "homepage": "https://github.com/code-vaults/git-credential-broker#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/code-vaults/git-credential-broker.git"
11
+ },
12
+ "type": "module",
13
+ "engines": {
14
+ "node": ">=24"
15
+ },
16
+ "bin": {
17
+ "gcb": "./bin/gcb.js"
18
+ },
19
+ "files": [
20
+ "lib",
21
+ "bin"
22
+ ],
23
+ "exports": {
24
+ ".": {
25
+ "types": "./lib/index.d.ts",
26
+ "default": "./lib/index.js"
27
+ }
28
+ },
29
+ "dependencies": {
30
+ "gcb-core": "0.0.2"
31
+ },
32
+ "publishConfig": {
33
+ "access": "public",
34
+ "registry": "https://registry.npmjs.org/"
35
+ }
5
36
  }
package/README.md DELETED
@@ -1 +0,0 @@
1
- # gcb