@dbx-tools/cli 0.9.58 → 0.9.61
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 +594 -57
- package/bin/dbx-genie.ts +13 -0
- package/bin/dbx-graphiti.ts +5 -0
- package/bin/dbx-lakebase-proxy.ts +4 -0
- package/bin/dbx-model-gateway.ts +5 -0
- package/index.ts +47 -1
- package/lib/bin/dbx-genie.d.ts +2 -0
- package/lib/bin/dbx-genie.js +10 -0
- package/lib/bin/dbx-graphiti.d.ts +2 -0
- package/lib/bin/dbx-graphiti.js +4 -0
- package/lib/bin/dbx-lakebase-proxy.d.ts +2 -0
- package/lib/bin/dbx-lakebase-proxy.js +4 -0
- package/lib/bin/dbx-model-gateway.d.ts +2 -0
- package/lib/bin/dbx-model-gateway.js +4 -0
- package/lib/index.d.ts +47 -1
- package/lib/index.js +34 -2
- package/lib/src/appkit/cli.d.ts +24 -0
- package/lib/src/appkit/cli.js +54 -0
- package/lib/src/appkit/env-export.d.ts +40 -0
- package/lib/src/appkit/env-export.js +99 -0
- package/lib/src/auth/cli.d.ts +20 -0
- package/lib/src/auth/cli.js +129 -0
- package/lib/src/auth/options.d.ts +45 -0
- package/lib/src/auth/options.js +68 -0
- package/lib/src/cli.d.ts +2 -0
- package/lib/src/cli.js +19 -9
- package/lib/src/genie-code/cli.d.ts +19 -0
- package/lib/src/genie-code/cli.js +36 -0
- package/lib/src/genie-code/config.d.ts +38 -0
- package/lib/src/genie-code/config.js +111 -0
- package/lib/src/genie-code/install.d.ts +21 -0
- package/lib/src/genie-code/install.js +69 -0
- package/lib/src/genie-code/options.d.ts +7 -0
- package/lib/src/genie-code/options.js +7 -0
- package/lib/src/genie-code/runner.d.ts +21 -0
- package/lib/src/genie-code/runner.js +90 -0
- package/lib/src/genie-code/runtime.d.ts +48 -0
- package/lib/src/genie-code/runtime.js +162 -0
- package/lib/src/graphiti/cli.d.ts +18 -0
- package/lib/src/graphiti/cli.js +43 -0
- package/lib/src/graphiti/options.d.ts +10 -0
- package/lib/src/graphiti/options.js +10 -0
- package/lib/src/lakebase-proxy/cancellation.d.ts +22 -0
- package/lib/src/lakebase-proxy/cancellation.js +100 -0
- package/lib/src/lakebase-proxy/cli.d.ts +13 -0
- package/lib/src/lakebase-proxy/cli.js +54 -0
- package/lib/src/lakebase-proxy/options.d.ts +23 -0
- package/lib/src/lakebase-proxy/options.js +41 -0
- package/lib/src/lakebase-proxy/protocol.d.ts +37 -0
- package/lib/src/lakebase-proxy/protocol.js +124 -0
- package/lib/src/lakebase-proxy/proxy.d.ts +22 -0
- package/lib/src/lakebase-proxy/proxy.js +200 -0
- package/lib/src/model-gateway/cli.d.ts +19 -0
- package/lib/src/model-gateway/cli.js +63 -0
- package/lib/src/model-gateway/server.d.ts +16 -0
- package/lib/src/model-gateway/server.js +83 -0
- package/lib/src/tunnel/app.d.ts +20 -0
- package/lib/src/tunnel/app.js +52 -0
- package/lib/src/tunnel/cli.d.ts +27 -0
- package/lib/src/tunnel/cli.js +190 -0
- package/lib/src/tunnel/options.d.ts +72 -0
- package/lib/src/tunnel/options.js +138 -0
- package/lib/src/tunnel/proxy.d.ts +51 -0
- package/lib/src/tunnel/proxy.js +143 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +146 -9
- package/src/appkit/cli.ts +72 -0
- package/src/appkit/env-export.ts +124 -0
- package/src/auth/cli.ts +175 -0
- package/src/auth/options.ts +83 -0
- package/src/cli.ts +35 -8
- package/src/genie-code/cli.ts +58 -0
- package/src/genie-code/config.ts +161 -0
- package/src/genie-code/install.ts +103 -0
- package/src/genie-code/options.ts +14 -0
- package/src/genie-code/runner.ts +106 -0
- package/src/genie-code/runtime.ts +212 -0
- package/src/graphiti/cli.ts +75 -0
- package/src/graphiti/options.ts +20 -0
- package/src/lakebase-proxy/cancellation.ts +116 -0
- package/src/lakebase-proxy/cli.ts +80 -0
- package/src/lakebase-proxy/options.ts +56 -0
- package/src/lakebase-proxy/protocol.ts +170 -0
- package/src/lakebase-proxy/proxy.ts +256 -0
- package/src/model-gateway/cli.ts +93 -0
- package/src/model-gateway/server.ts +94 -0
- package/src/tunnel/app.ts +58 -0
- package/src/tunnel/cli.ts +218 -0
- package/src/tunnel/options.ts +169 -0
- package/src/tunnel/proxy.ts +194 -0
package/src/auth/cli.ts
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `dbx auth` Commander program for Databricks OAuth.
|
|
3
|
+
*
|
|
4
|
+
* The command delegates profile resolution and token lifecycle to
|
|
5
|
+
* `@dbx-tools/auth`.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import * as databricks from "@dbx-tools/auth";
|
|
11
|
+
import { addArgs, parseArgs } from "@dbx-tools/cli-args";
|
|
12
|
+
import type { DatabricksAuthClientInfo } from "@dbx-tools/shared-auth/client";
|
|
13
|
+
import { Command, CommanderError } from "commander";
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
AuthCliOptionsSchema,
|
|
17
|
+
databricksAuthOptions,
|
|
18
|
+
TokenCommandOptionsSchema,
|
|
19
|
+
type AuthCliOptions,
|
|
20
|
+
} from "./options.ts";
|
|
21
|
+
|
|
22
|
+
interface AuthContext {
|
|
23
|
+
auth: databricks.AuthClient;
|
|
24
|
+
close(): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface AuthCliDependencies {
|
|
28
|
+
createAuthClient: typeof databricks.client.createAuthClient;
|
|
29
|
+
writeJson(value: unknown): void;
|
|
30
|
+
writeText(value: string): void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const DEFAULT_DEPENDENCIES: AuthCliDependencies = {
|
|
34
|
+
createAuthClient: databricks.client.createAuthClient,
|
|
35
|
+
writeJson: (value) => {
|
|
36
|
+
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
37
|
+
},
|
|
38
|
+
writeText: (value) => {
|
|
39
|
+
process.stdout.write(`${value}\n`);
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/** Open the selected authentication client. */
|
|
44
|
+
async function openAuth(
|
|
45
|
+
options: AuthCliOptions,
|
|
46
|
+
dependencies: AuthCliDependencies,
|
|
47
|
+
): Promise<AuthContext> {
|
|
48
|
+
return {
|
|
49
|
+
auth: await dependencies.createAuthClient(databricksAuthOptions(options)),
|
|
50
|
+
close: async () => {},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Execute an auth action and close resources owned by the command. */
|
|
55
|
+
async function withAuth(
|
|
56
|
+
options: AuthCliOptions,
|
|
57
|
+
dependencies: AuthCliDependencies,
|
|
58
|
+
action: (context: AuthContext) => Promise<void>,
|
|
59
|
+
): Promise<void> {
|
|
60
|
+
const context = await openAuth(options, dependencies);
|
|
61
|
+
try {
|
|
62
|
+
await action(context);
|
|
63
|
+
} finally {
|
|
64
|
+
await context.close();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Shape a generated token record for stable CLI JSON output. */
|
|
69
|
+
function tokenJson(token: databricks.AccessToken): Record<string, unknown> {
|
|
70
|
+
return {
|
|
71
|
+
access_token: token.accessToken,
|
|
72
|
+
token_type: token.tokenType,
|
|
73
|
+
...(token.expiry ? { expiry: token.expiry } : {}),
|
|
74
|
+
...(token.scopes.length ? { scopes: token.scopes } : {}),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Select the serializable identity fields from an authentication client. */
|
|
79
|
+
function clientInfo(auth: databricks.AuthClient): DatabricksAuthClientInfo {
|
|
80
|
+
return {
|
|
81
|
+
...(auth.profile ? { profile: auth.profile } : {}),
|
|
82
|
+
host: auth.host,
|
|
83
|
+
...(auth.accountId ? { accountId: auth.accountId } : {}),
|
|
84
|
+
...(auth.workspaceId ? { workspaceId: auth.workspaceId } : {}),
|
|
85
|
+
target: auth.target,
|
|
86
|
+
authType: auth.authType,
|
|
87
|
+
principal: auth.principal,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Build the `dbx auth` Commander program without parsing arguments. */
|
|
92
|
+
export function buildProgram(
|
|
93
|
+
name = "dbx auth",
|
|
94
|
+
overrides: Partial<AuthCliDependencies> = {},
|
|
95
|
+
): Command {
|
|
96
|
+
const dependencies = { ...DEFAULT_DEPENDENCIES, ...overrides };
|
|
97
|
+
const program = addArgs(
|
|
98
|
+
new Command()
|
|
99
|
+
.name(name)
|
|
100
|
+
.description("Authenticate to Databricks with user or machine OAuth")
|
|
101
|
+
.showHelpAfterError(),
|
|
102
|
+
AuthCliOptionsSchema,
|
|
103
|
+
);
|
|
104
|
+
const options = (): AuthCliOptions => parseArgs(program, AuthCliOptionsSchema);
|
|
105
|
+
|
|
106
|
+
program
|
|
107
|
+
.command("login")
|
|
108
|
+
.description("Force browser login and return an access token")
|
|
109
|
+
.action(async () => {
|
|
110
|
+
await withAuth(options(), dependencies, async ({ auth }) => {
|
|
111
|
+
dependencies.writeJson(tokenJson(await auth.token({ login: true })));
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const tokenCommand = addArgs(
|
|
116
|
+
program.command("token").description("Return a valid access token, logging in when needed"),
|
|
117
|
+
TokenCommandOptionsSchema,
|
|
118
|
+
);
|
|
119
|
+
tokenCommand.action(async () => {
|
|
120
|
+
const tokenOptions = parseArgs(tokenCommand, TokenCommandOptionsSchema);
|
|
121
|
+
await withAuth(options(), dependencies, async ({ auth }) => {
|
|
122
|
+
const token = await auth.token({
|
|
123
|
+
...(tokenOptions.login ? {} : { login: false }),
|
|
124
|
+
...(tokenOptions.forceRefresh ? { refresh: true } : {}),
|
|
125
|
+
});
|
|
126
|
+
if (tokenOptions.format === "text") dependencies.writeText(token.accessToken);
|
|
127
|
+
else dependencies.writeJson(tokenJson(token));
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
program
|
|
132
|
+
.command("headers")
|
|
133
|
+
.description("Return current Databricks authentication headers")
|
|
134
|
+
.action(async () => {
|
|
135
|
+
await withAuth(options(), dependencies, async ({ auth }) => {
|
|
136
|
+
dependencies.writeJson(await auth.headers());
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
program
|
|
141
|
+
.command("profile")
|
|
142
|
+
.description("Print the configured or automatically detected profile")
|
|
143
|
+
.action(async () => {
|
|
144
|
+
await withAuth(options(), dependencies, async ({ auth }) => {
|
|
145
|
+
dependencies.writeText(auth.profile ?? "ambient");
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
program
|
|
150
|
+
.command("logout")
|
|
151
|
+
.description("Delete the stored credential for the selected profile")
|
|
152
|
+
.action(async () => {
|
|
153
|
+
await withAuth(options(), dependencies, async ({ auth }) => {
|
|
154
|
+
await auth.logout();
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
program
|
|
159
|
+
.command("status")
|
|
160
|
+
.description("Show the resolved authentication client configuration")
|
|
161
|
+
.action(async () => {
|
|
162
|
+
await withAuth(options(), dependencies, async ({ auth }) => {
|
|
163
|
+
dependencies.writeJson(clientInfo(auth));
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
return program;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Parse `argv` and run the selected auth operation. */
|
|
171
|
+
export async function runCli(argv: string[]): Promise<void> {
|
|
172
|
+
await buildProgram().parseAsync(argv);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export { CommanderError };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Databricks auth command-line option schemas and runtime projection.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
AuthOptionsSchema,
|
|
9
|
+
DatabricksAuthOptionsSchema,
|
|
10
|
+
type DatabricksAuthOptions,
|
|
11
|
+
} from "@dbx-tools/auth/config";
|
|
12
|
+
import { stringUtils } from "@dbx-tools/shared-core";
|
|
13
|
+
import { z } from "zod";
|
|
14
|
+
|
|
15
|
+
export const AuthCliOptionsSchema = z
|
|
16
|
+
.object({
|
|
17
|
+
profile: DatabricksAuthOptionsSchema.shape.profile,
|
|
18
|
+
host: DatabricksAuthOptionsSchema.shape.host,
|
|
19
|
+
accountId: DatabricksAuthOptionsSchema.shape.accountId,
|
|
20
|
+
workspaceId: DatabricksAuthOptionsSchema.shape.workspaceId,
|
|
21
|
+
configFile: DatabricksAuthOptionsSchema.shape.configFile,
|
|
22
|
+
clientId: DatabricksAuthOptionsSchema.shape.clientId,
|
|
23
|
+
groupId: DatabricksAuthOptionsSchema.shape.groupId,
|
|
24
|
+
authType: DatabricksAuthOptionsSchema.shape.authType,
|
|
25
|
+
scopes: z
|
|
26
|
+
.preprocess(
|
|
27
|
+
(value) =>
|
|
28
|
+
Array.isArray(value)
|
|
29
|
+
? value.flatMap((entry) => stringUtils.parseList(String(entry)))
|
|
30
|
+
: value,
|
|
31
|
+
z.array(z.string().trim().min(1)),
|
|
32
|
+
)
|
|
33
|
+
.default([])
|
|
34
|
+
.describe("OAuth scopes."),
|
|
35
|
+
target: DatabricksAuthOptionsSchema.shape.target,
|
|
36
|
+
lockTimeoutMs: AuthOptionsSchema.shape.lockTimeoutMs,
|
|
37
|
+
loginTimeoutMs: AuthOptionsSchema.shape.loginTimeoutMs,
|
|
38
|
+
refreshBufferMs: AuthOptionsSchema.shape.refreshBufferMs,
|
|
39
|
+
preferUserToMachine: z
|
|
40
|
+
.boolean()
|
|
41
|
+
.default(true)
|
|
42
|
+
.describe("Prefer a matching user profile over selected machine credentials."),
|
|
43
|
+
})
|
|
44
|
+
.strict()
|
|
45
|
+
.describe("Options shared by every Databricks auth command.");
|
|
46
|
+
|
|
47
|
+
export const TokenCommandOptionsSchema = z
|
|
48
|
+
.object({
|
|
49
|
+
forceRefresh: z.boolean().default(false).describe("Refresh the token before returning it."),
|
|
50
|
+
login: z.boolean().default(true).describe("Log in when credentials are missing or invalid."),
|
|
51
|
+
format: z
|
|
52
|
+
.enum(["json", "text"])
|
|
53
|
+
.default("json")
|
|
54
|
+
.describe("Output structured token metadata or only the access token."),
|
|
55
|
+
})
|
|
56
|
+
.strict()
|
|
57
|
+
.describe("Options for returning a Databricks access token.");
|
|
58
|
+
|
|
59
|
+
export type AuthCliOptions = z.output<typeof AuthCliOptionsSchema>;
|
|
60
|
+
|
|
61
|
+
export type TokenCommandOptions = z.output<typeof TokenCommandOptionsSchema>;
|
|
62
|
+
|
|
63
|
+
/** Convert flat CLI fields into the Node auth package's nested options. */
|
|
64
|
+
export function databricksAuthOptions(options: AuthCliOptions): DatabricksAuthOptions {
|
|
65
|
+
return {
|
|
66
|
+
profile: options.profile,
|
|
67
|
+
host: options.host,
|
|
68
|
+
accountId: options.accountId,
|
|
69
|
+
workspaceId: options.workspaceId,
|
|
70
|
+
configFile: options.configFile,
|
|
71
|
+
clientId: options.clientId,
|
|
72
|
+
groupId: options.groupId,
|
|
73
|
+
authType: options.authType,
|
|
74
|
+
scopes: options.scopes.length > 0 ? options.scopes : undefined,
|
|
75
|
+
target: options.target,
|
|
76
|
+
auth: {
|
|
77
|
+
lockTimeoutMs: options.lockTimeoutMs,
|
|
78
|
+
loginTimeoutMs: options.loginTimeoutMs,
|
|
79
|
+
refreshBufferMs: options.refreshBufferMs,
|
|
80
|
+
},
|
|
81
|
+
preferUserToMachine: options.preferUserToMachine,
|
|
82
|
+
};
|
|
83
|
+
}
|
package/src/cli.ts
CHANGED
|
@@ -59,8 +59,9 @@ export async function prepareAndRunProjen(projenArgs: string[], startDir?: strin
|
|
|
59
59
|
/**
|
|
60
60
|
* Mount `name` as a command that captures every following token verbatim and
|
|
61
61
|
* hands it to the program `load()` resolves. `helpOption(false)` is what lets
|
|
62
|
-
* `--help` through to the child instead of being answered here, and
|
|
63
|
-
*
|
|
62
|
+
* `--help` through to the child instead of being answered here, and
|
|
63
|
+
* `passThroughOptions()` keeps unknown flags in that token list so the child
|
|
64
|
+
* program owns them.
|
|
64
65
|
*/
|
|
65
66
|
function addForwardedCommand(
|
|
66
67
|
program: Command,
|
|
@@ -73,6 +74,7 @@ function addForwardedCommand(
|
|
|
73
74
|
.description(description)
|
|
74
75
|
.argument("[args...]", `arguments forwarded to ${name}`)
|
|
75
76
|
.allowUnknownOption()
|
|
77
|
+
.passThroughOptions()
|
|
76
78
|
.allowExcessArguments()
|
|
77
79
|
.helpOption(false)
|
|
78
80
|
.action(async (args: string[]) => {
|
|
@@ -88,6 +90,7 @@ export function buildProgram(name: string = PROGRAM_NAMES[0]): Command {
|
|
|
88
90
|
.description(
|
|
89
91
|
"Databricks developer tools: workspace lifecycle, AppKit env, auth, tunnels, and local proxies",
|
|
90
92
|
)
|
|
93
|
+
.enablePositionalOptions()
|
|
91
94
|
.showHelpAfterError()
|
|
92
95
|
.helpOption("-h, --help", `Show ${name} help`);
|
|
93
96
|
|
|
@@ -106,47 +109,71 @@ export function buildProgram(name: string = PROGRAM_NAMES[0]): Command {
|
|
|
106
109
|
program,
|
|
107
110
|
"appkit",
|
|
108
111
|
"AppKit helpers (env: print the environment an AppKit app resolves)",
|
|
109
|
-
async () => (await import("
|
|
112
|
+
async () => (await import("./appkit/cli.ts")).buildProgram,
|
|
110
113
|
);
|
|
111
114
|
|
|
112
115
|
addForwardedCommand(
|
|
113
116
|
program,
|
|
114
117
|
"auth",
|
|
115
118
|
"Authenticate to Databricks and manage OAuth tokens",
|
|
116
|
-
async () => (await import("
|
|
119
|
+
async () => (await import("./auth/cli.ts")).buildProgram,
|
|
117
120
|
);
|
|
118
121
|
|
|
119
122
|
addForwardedCommand(
|
|
120
123
|
program,
|
|
121
124
|
"tunnel",
|
|
122
125
|
"Run a public portr tunnel with an email-OTP gate",
|
|
123
|
-
async () => (await import("
|
|
126
|
+
async () => (await import("./tunnel/cli.ts")).buildProgram,
|
|
124
127
|
);
|
|
125
128
|
|
|
126
129
|
addForwardedCommand(
|
|
127
130
|
program,
|
|
128
131
|
"lakebase-proxy",
|
|
129
132
|
"Run the Node Databricks Lakebase PostgreSQL proxy",
|
|
130
|
-
async () => (await import("
|
|
133
|
+
async () => (await import("./lakebase-proxy/cli.ts")).buildProgram,
|
|
131
134
|
);
|
|
132
135
|
|
|
133
136
|
addForwardedCommand(
|
|
134
137
|
program,
|
|
135
138
|
"model-gateway",
|
|
136
139
|
"Run the foreground AppKit Databricks model gateway",
|
|
137
|
-
async () => (await import("
|
|
140
|
+
async () => (await import("./model-gateway/cli.ts")).buildProgram,
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
addForwardedCommand(
|
|
144
|
+
program,
|
|
145
|
+
"genie",
|
|
146
|
+
"Run managed Genie Code through a local Databricks model gateway",
|
|
147
|
+
async () => (await import("./genie-code/cli.ts")).buildProgram,
|
|
138
148
|
);
|
|
139
149
|
|
|
140
150
|
addForwardedCommand(
|
|
141
151
|
program,
|
|
142
152
|
"graphiti",
|
|
143
153
|
"Run Graphiti or manage its current-user desktop service",
|
|
144
|
-
async () => (await import("
|
|
154
|
+
async () => (await import("./graphiti/cli.ts")).buildProgram,
|
|
145
155
|
);
|
|
146
156
|
|
|
147
157
|
return program;
|
|
148
158
|
}
|
|
149
159
|
|
|
160
|
+
/** Build the complete command tree used for generated documentation. */
|
|
161
|
+
export async function buildDocumentationProgram(name: string = PROGRAM_NAMES[0]): Promise<Command> {
|
|
162
|
+
const program = buildProgram(name);
|
|
163
|
+
(program.commands as Command[]).splice(
|
|
164
|
+
1,
|
|
165
|
+
program.commands.length - 1,
|
|
166
|
+
(await import("./appkit/cli.ts")).buildProgram("appkit"),
|
|
167
|
+
(await import("./auth/cli.ts")).buildProgram("auth"),
|
|
168
|
+
(await import("./tunnel/cli.ts")).buildProgram("tunnel"),
|
|
169
|
+
(await import("./lakebase-proxy/cli.ts")).buildProgram("lakebase-proxy"),
|
|
170
|
+
(await import("./model-gateway/cli.ts")).buildProgram("model-gateway"),
|
|
171
|
+
(await import("./genie-code/cli.ts")).buildProgram("genie"),
|
|
172
|
+
(await import("./graphiti/cli.ts")).buildProgram("graphiti"),
|
|
173
|
+
);
|
|
174
|
+
return program;
|
|
175
|
+
}
|
|
176
|
+
|
|
150
177
|
/**
|
|
151
178
|
* Program name for help output: whichever bin the user actually typed, so
|
|
152
179
|
* `dbx-tools --help` doesn't document itself as `dbx`. Falls back to `dbx`.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Managed `dbx genie` command.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { addArgs, parseArgs } from "@dbx-tools/cli-args";
|
|
8
|
+
import { Command } from "commander";
|
|
9
|
+
|
|
10
|
+
import { GenieCodeCliOptionsSchema, type GenieCodeCliOptions } from "./options.ts";
|
|
11
|
+
import { runGenieCode } from "./runtime.ts";
|
|
12
|
+
|
|
13
|
+
/** Injectable managed Genie execution boundary for parser tests and embedding. */
|
|
14
|
+
export interface GenieCodeCliDependencies {
|
|
15
|
+
run(input: {
|
|
16
|
+
cwd: string;
|
|
17
|
+
genieArgs: readonly string[];
|
|
18
|
+
options: GenieCodeCliOptions;
|
|
19
|
+
}): Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const DEFAULT_DEPENDENCIES: GenieCodeCliDependencies = {
|
|
23
|
+
run: runGenieCode,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/** Build the managed Genie Code command without executing it. */
|
|
27
|
+
export function buildProgram(
|
|
28
|
+
name = "dbx genie",
|
|
29
|
+
dependencies: GenieCodeCliDependencies = DEFAULT_DEPENDENCIES,
|
|
30
|
+
): Command {
|
|
31
|
+
const program = addArgs(
|
|
32
|
+
new Command()
|
|
33
|
+
.name(name)
|
|
34
|
+
.description("Run Genie Code through an authenticated local Databricks model gateway")
|
|
35
|
+
.argument(
|
|
36
|
+
"[genieArgs...]",
|
|
37
|
+
"arguments and unknown flags forwarded to Genie Code; wrapper options may be separated with --",
|
|
38
|
+
)
|
|
39
|
+
.allowUnknownOption()
|
|
40
|
+
.passThroughOptions()
|
|
41
|
+
.allowExcessArguments()
|
|
42
|
+
.showHelpAfterError(),
|
|
43
|
+
GenieCodeCliOptionsSchema,
|
|
44
|
+
);
|
|
45
|
+
program.action(async (genieArgs: string[]) => {
|
|
46
|
+
await dependencies.run({
|
|
47
|
+
cwd: process.cwd(),
|
|
48
|
+
genieArgs,
|
|
49
|
+
options: parseArgs(program, GenieCodeCliOptionsSchema),
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
return program;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Parse argv and run managed Genie Code. */
|
|
56
|
+
export async function runCli(argv: string[]): Promise<void> {
|
|
57
|
+
await buildProgram().parseAsync(argv);
|
|
58
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate one persistent Genie Code home per exact profile-model pairing.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
8
|
+
import { chmod, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
9
|
+
import { homedir } from "node:os";
|
|
10
|
+
import { dirname, join, resolve } from "node:path";
|
|
11
|
+
import { withFileLock } from "@dbx-tools/core/file-lock";
|
|
12
|
+
import { object } from "@dbx-tools/shared-core";
|
|
13
|
+
import {
|
|
14
|
+
GenieCodeBaseConfigSchema,
|
|
15
|
+
GenieCodeConfigSchema,
|
|
16
|
+
GenieCodeProviderOverlaySchema,
|
|
17
|
+
type GenieCodeBaseConfig,
|
|
18
|
+
type GenieCodeConfig,
|
|
19
|
+
type GenieCodeProviderOverlay,
|
|
20
|
+
} from "@dbx-tools/shared-genie-code/config";
|
|
21
|
+
import { genieCodePairingName } from "@dbx-tools/shared-genie-code/options";
|
|
22
|
+
import { parse, stringify } from "smol-toml";
|
|
23
|
+
|
|
24
|
+
/** Provider identifier written to the base and invocation overlay configurations. */
|
|
25
|
+
export const GENIE_CODE_GATEWAY_PROVIDER = "dbx_tools_model_gateway";
|
|
26
|
+
|
|
27
|
+
/** Inputs used to update a persistent home and create one invocation overlay. */
|
|
28
|
+
export interface WriteGenieCodeConfigOptions {
|
|
29
|
+
bearerToken: string;
|
|
30
|
+
gatewayBaseUrl: string;
|
|
31
|
+
homeDirectory?: string;
|
|
32
|
+
model: string;
|
|
33
|
+
profile: string;
|
|
34
|
+
projectDirectory: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Stable persistent home for one exact profile-model pairing. */
|
|
38
|
+
export interface GenieCodePairingHome {
|
|
39
|
+
configPath: string;
|
|
40
|
+
home: string;
|
|
41
|
+
name: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Persistent pairing home and invocation-specific provider overlay. */
|
|
45
|
+
export interface GenieCodeHome extends GenieCodePairingHome {
|
|
46
|
+
overlayName: string;
|
|
47
|
+
overlayPath: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Derive the stable home for one exact profile-model pair. */
|
|
51
|
+
export function genieCodeHome(
|
|
52
|
+
profile: string,
|
|
53
|
+
model: string,
|
|
54
|
+
homeDirectory: string = homedir(),
|
|
55
|
+
): GenieCodePairingHome {
|
|
56
|
+
const digest = createHash("sha256")
|
|
57
|
+
.update(JSON.stringify([profile, model]))
|
|
58
|
+
.digest("hex")
|
|
59
|
+
.slice(0, 12);
|
|
60
|
+
const name = genieCodePairingName({ profile, model, digest });
|
|
61
|
+
const home = join(homeDirectory, ".dbx-tools", "genie", "profiles", name);
|
|
62
|
+
return { name, home, configPath: join(home, "config.toml") };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Render the persistent profile-model configuration. */
|
|
66
|
+
export function genieCodeBaseConfig(
|
|
67
|
+
options: WriteGenieCodeConfigOptions,
|
|
68
|
+
existingProjects: Readonly<Record<string, unknown>> = {},
|
|
69
|
+
): GenieCodeBaseConfig {
|
|
70
|
+
const projects = Object.fromEntries(
|
|
71
|
+
Object.entries(existingProjects).filter(
|
|
72
|
+
([, value]) => object.isRecord(value) && value.trust_level === "trusted",
|
|
73
|
+
),
|
|
74
|
+
);
|
|
75
|
+
return GenieCodeBaseConfigSchema.parse({
|
|
76
|
+
model_provider: GENIE_CODE_GATEWAY_PROVIDER,
|
|
77
|
+
model: options.model,
|
|
78
|
+
databricks_profile: options.profile,
|
|
79
|
+
projects: {
|
|
80
|
+
...projects,
|
|
81
|
+
[resolve(options.projectDirectory)]: {
|
|
82
|
+
trust_level: "trusted",
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
tui: {
|
|
86
|
+
model_availability_nux: {
|
|
87
|
+
[options.model]: 1,
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Render the invocation-specific guarded provider configuration. */
|
|
94
|
+
export function genieCodeProviderOverlay(
|
|
95
|
+
options: WriteGenieCodeConfigOptions,
|
|
96
|
+
): GenieCodeProviderOverlay {
|
|
97
|
+
return GenieCodeProviderOverlaySchema.parse({
|
|
98
|
+
model_providers: {
|
|
99
|
+
[GENIE_CODE_GATEWAY_PROVIDER]: {
|
|
100
|
+
name: "dbx-tools model gateway",
|
|
101
|
+
base_url: options.gatewayBaseUrl,
|
|
102
|
+
wire_api: "responses",
|
|
103
|
+
requires_openai_auth: false,
|
|
104
|
+
supports_websockets: false,
|
|
105
|
+
http_headers: {
|
|
106
|
+
Authorization: `Bearer ${options.bearerToken}`,
|
|
107
|
+
Originator: "codex",
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Render the merged configuration used by validation and tests. */
|
|
115
|
+
export function genieCodeConfig(options: WriteGenieCodeConfigOptions): GenieCodeConfig {
|
|
116
|
+
return GenieCodeConfigSchema.parse({
|
|
117
|
+
...genieCodeBaseConfig(options),
|
|
118
|
+
...genieCodeProviderOverlay(options),
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function existingProjects(path: string): Promise<Record<string, unknown>> {
|
|
123
|
+
try {
|
|
124
|
+
const value = parse(await readFile(path, "utf8"));
|
|
125
|
+
return object.isRecord(value.projects) ? value.projects : {};
|
|
126
|
+
} catch (error) {
|
|
127
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") return {};
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function writePrivateToml(path: string, value: unknown): Promise<void> {
|
|
133
|
+
const staged = join(dirname(path), `.${randomUUID()}.toml`);
|
|
134
|
+
try {
|
|
135
|
+
await writeFile(staged, stringify(value), { mode: 0o600 });
|
|
136
|
+
await chmod(staged, 0o600);
|
|
137
|
+
await rename(staged, path);
|
|
138
|
+
} finally {
|
|
139
|
+
await rm(staged, { force: true });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Update the persistent base config and write one private invocation overlay. */
|
|
144
|
+
export async function writeGenieCodeConfig(
|
|
145
|
+
options: WriteGenieCodeConfigOptions,
|
|
146
|
+
): Promise<GenieCodeHome> {
|
|
147
|
+
const base = genieCodeHome(options.profile, options.model, options.homeDirectory);
|
|
148
|
+
const overlayName = `dbx-${randomUUID().replaceAll("-", "")}`;
|
|
149
|
+
const destination = {
|
|
150
|
+
...base,
|
|
151
|
+
overlayName,
|
|
152
|
+
overlayPath: join(base.home, `${overlayName}.config.toml`),
|
|
153
|
+
};
|
|
154
|
+
await mkdir(destination.home, { recursive: true });
|
|
155
|
+
await withFileLock(["genie-code-config", destination.configPath], async () => {
|
|
156
|
+
const projects = await existingProjects(destination.configPath);
|
|
157
|
+
await writePrivateToml(destination.configPath, genieCodeBaseConfig(options, projects));
|
|
158
|
+
});
|
|
159
|
+
await writePrivateToml(destination.overlayPath, genieCodeProviderOverlay(options));
|
|
160
|
+
return destination;
|
|
161
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Install the pinned multi-file Genie Code runtime under dbx-tools ownership.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { dirname, join } from "node:path";
|
|
9
|
+
import { bin } from "@dbx-tools/core";
|
|
10
|
+
import { GENIE_CODE_VERSION } from "@dbx-tools/shared-genie-code/options";
|
|
11
|
+
|
|
12
|
+
interface GenieCodeAsset {
|
|
13
|
+
target: string;
|
|
14
|
+
sha256: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const GENIE_CODE_ASSETS = {
|
|
18
|
+
"darwin:arm64": {
|
|
19
|
+
target: "aarch64-apple-darwin",
|
|
20
|
+
sha256: "78cc7e358c8bfc62d6da26364c762be37fc2e2a10b7e83f21a3b166f901f528e",
|
|
21
|
+
},
|
|
22
|
+
"darwin:x64": {
|
|
23
|
+
target: "x86_64-apple-darwin",
|
|
24
|
+
sha256: "2021b8b27ec3116fc015c1676b5f65c3ce11d57f1dff9c302aae135f6e4f1a5f",
|
|
25
|
+
},
|
|
26
|
+
"linux:arm64": {
|
|
27
|
+
target: "aarch64-unknown-linux-musl",
|
|
28
|
+
sha256: "dfd278c267dde43e1fe7e7d7a0da8d7a1a148cdf3d2004bd30b351645c4fe0a7",
|
|
29
|
+
},
|
|
30
|
+
"linux:x64": {
|
|
31
|
+
target: "x86_64-unknown-linux-musl",
|
|
32
|
+
sha256: "e3d9a55f6d51bdc745eda4744fb827ba1df6dacf1069518f7347a59f48c89430",
|
|
33
|
+
},
|
|
34
|
+
"win32:arm64": {
|
|
35
|
+
target: "aarch64-pc-windows-msvc",
|
|
36
|
+
sha256: "aeac95bd0148f942194270ba932245cec32e98da67292ba7b008bcab675ba7f8",
|
|
37
|
+
},
|
|
38
|
+
"win32:x64": {
|
|
39
|
+
target: "x86_64-pc-windows-msvc",
|
|
40
|
+
sha256: "c9f92c8d60273250262527bca525451501c4e8351e59927a1686c305be29a922",
|
|
41
|
+
},
|
|
42
|
+
} as const satisfies Record<string, GenieCodeAsset>;
|
|
43
|
+
|
|
44
|
+
/** Host overrides used by installation tests and embedded callers. */
|
|
45
|
+
export interface InstallGenieCodeOptions {
|
|
46
|
+
homeDirectory?: string;
|
|
47
|
+
platform?: NodeJS.Platform;
|
|
48
|
+
architecture?: NodeJS.Architecture;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Resolve the pinned release asset for one supported host. */
|
|
52
|
+
export function genieCodeAsset(
|
|
53
|
+
platform: NodeJS.Platform = process.platform,
|
|
54
|
+
architecture: NodeJS.Architecture = process.arch,
|
|
55
|
+
): GenieCodeAsset {
|
|
56
|
+
const asset = GENIE_CODE_ASSETS[`${platform}:${architecture}` as keyof typeof GENIE_CODE_ASSETS];
|
|
57
|
+
if (!asset) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
`Genie Code ${GENIE_CODE_VERSION} does not support ${platform}/${architecture}`,
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
return asset;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Install and return the shared Genie Code package entrypoint. */
|
|
66
|
+
export async function installGenieCode(
|
|
67
|
+
options: InstallGenieCodeOptions = {},
|
|
68
|
+
): Promise<bin.BinContext> {
|
|
69
|
+
const platform = options.platform ?? process.platform;
|
|
70
|
+
const asset = genieCodeAsset(platform, options.architecture ?? process.arch);
|
|
71
|
+
const executable = platform === "win32" ? "genie.exe" : "genie";
|
|
72
|
+
const helper = platform === "win32" ? "genie-code-mode-host.exe" : "genie-code-mode-host";
|
|
73
|
+
const entrypoint = join("bin", executable);
|
|
74
|
+
const root = join(
|
|
75
|
+
options.homeDirectory ?? homedir(),
|
|
76
|
+
".dbx-tools",
|
|
77
|
+
"genie",
|
|
78
|
+
"releases",
|
|
79
|
+
`${GENIE_CODE_VERSION}-${asset.target}`,
|
|
80
|
+
);
|
|
81
|
+
const destination: bin.BinContext = {
|
|
82
|
+
root,
|
|
83
|
+
binDir: join(root, dirname(entrypoint)),
|
|
84
|
+
path: join(root, entrypoint),
|
|
85
|
+
};
|
|
86
|
+
const url =
|
|
87
|
+
`https://github.com/databricks/genie-code-cli/releases/download/v${GENIE_CODE_VERSION}/` +
|
|
88
|
+
`genie-package-${asset.target}.tar.gz`;
|
|
89
|
+
return bin.ensure(
|
|
90
|
+
"genie",
|
|
91
|
+
{ url, sha256: asset.sha256 },
|
|
92
|
+
{
|
|
93
|
+
destination,
|
|
94
|
+
minVersion: "0.1.0",
|
|
95
|
+
versionParser: ({ stdout }) =>
|
|
96
|
+
stdout.trim() === `genie ${GENIE_CODE_VERSION}` ? "0.1.0" : undefined,
|
|
97
|
+
package: {
|
|
98
|
+
entrypoint,
|
|
99
|
+
requiredPaths: ["genie-package.json", join("bin", helper), "genie-path", "genie-resources"],
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
);
|
|
103
|
+
}
|