@wacht/bench 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth-store.js +19 -0
- package/dist/browser.js +15 -0
- package/dist/commands.js +389 -0
- package/dist/completion.js +132 -0
- package/dist/config-workflow.js +474 -0
- package/dist/config.js +18 -0
- package/dist/context-store.js +28 -0
- package/dist/deployment-context.js +205 -0
- package/dist/guards.js +23 -0
- package/dist/init.js +535 -0
- package/dist/machine-api.js +272 -0
- package/dist/mcp.js +21 -0
- package/dist/oauth-callback.js +104 -0
- package/dist/oauth.js +236 -0
- package/dist/openapi.js +259 -0
- package/dist/pkce.js +14 -0
- package/dist/project-detect.js +64 -0
- package/dist/prompts.js +74 -0
- package/dist/resources.js +204 -0
- package/dist/skills.js +29 -0
- package/dist/types.js +1 -0
- package/dist/ui.js +104 -0
- package/dist/util.js +6 -0
- package/dist/wacht.js +18 -0
- package/package.json +33 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { AUTH_DIR, AUTH_FILE } from './config.js';
|
|
3
|
+
import { isStoredAuth } from './guards.js';
|
|
4
|
+
export async function readAuth() {
|
|
5
|
+
try {
|
|
6
|
+
const parsed = JSON.parse(await readFile(AUTH_FILE, 'utf8'));
|
|
7
|
+
return isStoredAuth(parsed) ? parsed : null;
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export async function writeAuth(auth) {
|
|
14
|
+
await mkdir(AUTH_DIR, { recursive: true });
|
|
15
|
+
await writeFile(AUTH_FILE, `${JSON.stringify(auth, null, 2)}\n`, { mode: 0o600 });
|
|
16
|
+
}
|
|
17
|
+
export async function clearAuth() {
|
|
18
|
+
await rm(AUTH_FILE, { force: true });
|
|
19
|
+
}
|
package/dist/browser.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
export function openBrowser(url) {
|
|
3
|
+
const command = process.platform === 'darwin'
|
|
4
|
+
? 'open'
|
|
5
|
+
: process.platform === 'win32'
|
|
6
|
+
? 'cmd'
|
|
7
|
+
: 'xdg-open';
|
|
8
|
+
const args = process.platform === 'win32' ? ['/c', 'start', '', url] : [url];
|
|
9
|
+
const child = spawn(command, args, {
|
|
10
|
+
detached: true,
|
|
11
|
+
stdio: 'ignore',
|
|
12
|
+
});
|
|
13
|
+
child.on('error', () => { });
|
|
14
|
+
child.unref();
|
|
15
|
+
}
|
package/dist/commands.js
ADDED
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { completionScript } from './completion.js';
|
|
3
|
+
import { configApply, configDiff, configPull, configSchemaCommand, printConfigTemplate, } from './config-workflow.js';
|
|
4
|
+
import { clearDeployment, createDeploymentCommand, createProjectCommand, currentDeployment, selectDeployment, } from './deployment-context.js';
|
|
5
|
+
import { initProject, initStarter } from './init.js';
|
|
6
|
+
import { apiCommand, listProjects } from './machine-api.js';
|
|
7
|
+
import { openApiCall, openApiDescribe, openApiList, openApiRefresh } from './openapi.js';
|
|
8
|
+
import { printMcpConfig } from './mcp.js';
|
|
9
|
+
import { authStatus, login, logout } from './oauth.js';
|
|
10
|
+
import { createOrg, createUser, createWorkspace, getOrg, getUser, getWorkspace, listOrgs, listUsers, listWorkspaces, } from './resources.js';
|
|
11
|
+
import { installSkills } from './skills.js';
|
|
12
|
+
import { banner } from './ui.js';
|
|
13
|
+
function collect(value, previous) {
|
|
14
|
+
previous.push(value);
|
|
15
|
+
return previous;
|
|
16
|
+
}
|
|
17
|
+
function context(command) {
|
|
18
|
+
const options = command.optsWithGlobals();
|
|
19
|
+
if (options.color === false) {
|
|
20
|
+
process.env.NO_COLOR = '1';
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
json: !!options.json,
|
|
24
|
+
quiet: !!options.quiet,
|
|
25
|
+
banner: options.banner !== false,
|
|
26
|
+
interactive: options.interactive !== false && !options.json && !options.quiet,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function initArgs(options) {
|
|
30
|
+
const args = [];
|
|
31
|
+
if (options.client)
|
|
32
|
+
args.push('--client', options.client);
|
|
33
|
+
if (options.installSkills)
|
|
34
|
+
args.push('--install-skills');
|
|
35
|
+
if (options.skipAgents)
|
|
36
|
+
args.push('--skip-agents');
|
|
37
|
+
if (options.skipConfig)
|
|
38
|
+
args.push('--skip-config');
|
|
39
|
+
if (options.skipEnv)
|
|
40
|
+
args.push('--skip-env');
|
|
41
|
+
if (options.skipGuide)
|
|
42
|
+
args.push('--skip-guide');
|
|
43
|
+
if (options.skipTemplates)
|
|
44
|
+
args.push('--skip-templates');
|
|
45
|
+
return args;
|
|
46
|
+
}
|
|
47
|
+
export async function runCli(args) {
|
|
48
|
+
const program = new Command();
|
|
49
|
+
program
|
|
50
|
+
.name('wacht')
|
|
51
|
+
.description('AI development workbench for Wacht')
|
|
52
|
+
.showHelpAfterError()
|
|
53
|
+
.option('--json', 'emit machine-readable JSON where supported')
|
|
54
|
+
.option('--quiet', 'suppress nonessential human output')
|
|
55
|
+
.option('--no-interactive', 'do not prompt; require explicit arguments and flags')
|
|
56
|
+
.option('--no-banner', 'hide the Wacht Bench banner')
|
|
57
|
+
.option('--no-color', 'disable colored output')
|
|
58
|
+
.addHelpText('beforeAll', () => {
|
|
59
|
+
const options = program.opts();
|
|
60
|
+
if (options.json || options.quiet || options.banner === false)
|
|
61
|
+
return '';
|
|
62
|
+
return `${banner()}\n\n`;
|
|
63
|
+
});
|
|
64
|
+
program
|
|
65
|
+
.command('init')
|
|
66
|
+
.description('bootstrap the current project for Wacht development')
|
|
67
|
+
.option('--client <client>', 'assistant client metadata', 'cursor')
|
|
68
|
+
.option('--starter [framework]', 'clone a Wacht starter (nextjs, react-router, tanstack) instead of bootstrapping the current dir')
|
|
69
|
+
.option('--target <dir>', 'target directory when using --starter (defaults to ./wacht-<framework>-starter)')
|
|
70
|
+
.option('--install-skills', 'run npx skills add after writing config')
|
|
71
|
+
.option('--skip-agents', 'do not create or update AGENTS.md')
|
|
72
|
+
.option('--skip-config', 'do not write .wacht/bench.json')
|
|
73
|
+
.option('--skip-env', 'do not write .env.wacht.example')
|
|
74
|
+
.option('--skip-guide', 'do not write .wacht/BOOTSTRAP.md')
|
|
75
|
+
.option('--skip-templates', 'do not write starter templates under .wacht/templates')
|
|
76
|
+
.action(async (options) => {
|
|
77
|
+
const ctx = context(program);
|
|
78
|
+
if (options.starter) {
|
|
79
|
+
const framework = typeof options.starter === 'string' ? options.starter : undefined;
|
|
80
|
+
await initStarter({
|
|
81
|
+
framework,
|
|
82
|
+
target: options.target,
|
|
83
|
+
install: !!options.installSkills,
|
|
84
|
+
client: options.client ?? 'cursor',
|
|
85
|
+
}, ctx);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
await initProject(initArgs(options), ctx);
|
|
89
|
+
});
|
|
90
|
+
program
|
|
91
|
+
.command('login')
|
|
92
|
+
.description('log in with Wacht OAuth using Authorization Code + PKCE')
|
|
93
|
+
.option('--skip-select', 'do not select an active deployment after login')
|
|
94
|
+
.action(async (options) => {
|
|
95
|
+
const ctx = context(program);
|
|
96
|
+
await login(ctx);
|
|
97
|
+
if (!options.skipSelect && ctx.interactive) {
|
|
98
|
+
await selectDeployment(ctx, { optional: true });
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
program
|
|
102
|
+
.command('logout')
|
|
103
|
+
.description('clear local Bench auth and revoke the refresh token when possible')
|
|
104
|
+
.action(async () => {
|
|
105
|
+
await logout(context(program));
|
|
106
|
+
});
|
|
107
|
+
const auth = program.command('auth').description('inspect or manage Bench auth');
|
|
108
|
+
auth
|
|
109
|
+
.command('status')
|
|
110
|
+
.description('show local Bench auth status')
|
|
111
|
+
.action(async () => {
|
|
112
|
+
await authStatus(context(program));
|
|
113
|
+
});
|
|
114
|
+
const projects = program.command('projects').description('work with Wacht projects');
|
|
115
|
+
projects
|
|
116
|
+
.command('list')
|
|
117
|
+
.description('list projects visible to the current OAuth grant')
|
|
118
|
+
.action(async () => {
|
|
119
|
+
await listProjects(context(program));
|
|
120
|
+
});
|
|
121
|
+
projects
|
|
122
|
+
.command('create')
|
|
123
|
+
.description('create a Wacht project with its first staging deployment')
|
|
124
|
+
.option('--name <name>', 'project name')
|
|
125
|
+
.option('--method <method>', 'auth method; repeatable. Project/staging: email, phone, username, google_oauth, apple_oauth, facebook_oauth, github_oauth, discord_oauth, linkedin_oauth, gitlab_oauth, x_oauth', collect, [])
|
|
126
|
+
.option('--no-select', 'do not make the created staging deployment active')
|
|
127
|
+
.action(async (options) => {
|
|
128
|
+
await createProjectCommand(context(program), options);
|
|
129
|
+
});
|
|
130
|
+
const deployments = program.command('deployments').description('work with Wacht deployments');
|
|
131
|
+
deployments
|
|
132
|
+
.command('current')
|
|
133
|
+
.description('show the active deployment Bench will use')
|
|
134
|
+
.action(async () => {
|
|
135
|
+
await currentDeployment(context(program));
|
|
136
|
+
});
|
|
137
|
+
deployments
|
|
138
|
+
.command('select')
|
|
139
|
+
.description('select the active project/deployment for Bench')
|
|
140
|
+
.option('--project <id>', 'project id')
|
|
141
|
+
.option('--deployment <id>', 'deployment id')
|
|
142
|
+
.option('--mode <mode>', 'deployment mode: staging or production')
|
|
143
|
+
.action(async (options) => {
|
|
144
|
+
await selectDeployment(context(program), options);
|
|
145
|
+
});
|
|
146
|
+
deployments
|
|
147
|
+
.command('clear')
|
|
148
|
+
.description('clear the active deployment selection')
|
|
149
|
+
.action(async () => {
|
|
150
|
+
await clearDeployment(context(program));
|
|
151
|
+
});
|
|
152
|
+
deployments
|
|
153
|
+
.command('create')
|
|
154
|
+
.description('create a staging or production deployment')
|
|
155
|
+
.argument('[mode]', 'staging or production')
|
|
156
|
+
.option('--project <id>', 'project id; defaults to the active project')
|
|
157
|
+
.option('--domain <domain>', 'custom domain for production deployments')
|
|
158
|
+
.option('--method <method>', 'auth method; repeatable. Staging supports social providers; production supports email, phone, username', collect, [])
|
|
159
|
+
.option('--no-select', 'do not make the created deployment active')
|
|
160
|
+
.action(async (mode, options) => {
|
|
161
|
+
await createDeploymentCommand(context(program), { ...options, mode });
|
|
162
|
+
});
|
|
163
|
+
const skills = program.command('skills').description('install Wacht agent skills');
|
|
164
|
+
skills
|
|
165
|
+
.command('install')
|
|
166
|
+
.description('install the Wacht skills pack')
|
|
167
|
+
.option('--skill <name>', 'install one skill from the pack')
|
|
168
|
+
.action(async (options) => {
|
|
169
|
+
await installSkills(options.skill);
|
|
170
|
+
});
|
|
171
|
+
const mcp = program.command('mcp').description('print Wacht Docs MCP configuration');
|
|
172
|
+
mcp
|
|
173
|
+
.command('config')
|
|
174
|
+
.description('print MCP config JSON for an assistant client')
|
|
175
|
+
.option('--client <client>', 'cursor, claude, or codex', 'cursor')
|
|
176
|
+
.action((options) => {
|
|
177
|
+
printMcpConfig(options.client);
|
|
178
|
+
});
|
|
179
|
+
const config = program.command('config').description('manage Wacht deployment settings as code');
|
|
180
|
+
config
|
|
181
|
+
.command('pull')
|
|
182
|
+
.description('pull the active deployment settings into wacht.config.json')
|
|
183
|
+
.option('--file <path>', 'config file path', 'wacht.config.json')
|
|
184
|
+
.option('--deployment <id>', 'deployment id override; defaults to active deployment')
|
|
185
|
+
.action(async (options) => {
|
|
186
|
+
await configPull(context(program), options);
|
|
187
|
+
});
|
|
188
|
+
config
|
|
189
|
+
.command('schema')
|
|
190
|
+
.description('print the Wacht config JSON schema')
|
|
191
|
+
.option('--refresh', 'refresh the cached OpenAPI schema first')
|
|
192
|
+
.action(async (options) => {
|
|
193
|
+
await configSchemaCommand(context(program), options);
|
|
194
|
+
});
|
|
195
|
+
config
|
|
196
|
+
.command('template')
|
|
197
|
+
.description('print a minimal Wacht config template')
|
|
198
|
+
.action(() => {
|
|
199
|
+
printConfigTemplate(context(program));
|
|
200
|
+
});
|
|
201
|
+
config
|
|
202
|
+
.command('diff')
|
|
203
|
+
.description('compare a config file with the active deployment')
|
|
204
|
+
.option('--file <path>', 'config file path', 'wacht.config.json')
|
|
205
|
+
.option('--deployment <id>', 'deployment id override; defaults to config or active deployment')
|
|
206
|
+
.action(async (options) => {
|
|
207
|
+
await configDiff(context(program), options);
|
|
208
|
+
});
|
|
209
|
+
config
|
|
210
|
+
.command('apply')
|
|
211
|
+
.description('apply a config file to the active deployment')
|
|
212
|
+
.option('--file <path>', 'config file path', 'wacht.config.json')
|
|
213
|
+
.option('--deployment <id>', 'deployment id override; defaults to config or active deployment')
|
|
214
|
+
.option('--dry-run', 'preview changes without applying them')
|
|
215
|
+
.option('--yes', 'required to apply changes')
|
|
216
|
+
.option('--production', 'allow applying to a production deployment')
|
|
217
|
+
.option('--confirm <deployment_id>', 'required with --production; must match the deployment id')
|
|
218
|
+
.action(async (options) => {
|
|
219
|
+
await configApply(context(program), options);
|
|
220
|
+
});
|
|
221
|
+
const api = program
|
|
222
|
+
.command('api')
|
|
223
|
+
.description('call the Wacht Machine API')
|
|
224
|
+
.argument('[method]', 'HTTP method')
|
|
225
|
+
.argument('[path]', 'API path, for example /projects')
|
|
226
|
+
.option('--body <json>', 'JSON request body')
|
|
227
|
+
.option('--field <key=value>', 'URL-encoded form field; repeatable', collect, [])
|
|
228
|
+
.option('--form <key=value>', 'multipart form field; value @path is treated as a file; repeatable', collect, [])
|
|
229
|
+
.option('--file <key=path>', 'multipart file field; key=path or key=@path; repeatable', collect, [])
|
|
230
|
+
.option('--header <key=value>', 'request header; repeatable', collect, [])
|
|
231
|
+
.action(async (method, path, options) => {
|
|
232
|
+
await apiCommand(method, path, options, context(program));
|
|
233
|
+
});
|
|
234
|
+
api
|
|
235
|
+
.command('ls')
|
|
236
|
+
.description('list operations from the Wacht Platform OpenAPI schema')
|
|
237
|
+
.option('--tag <tag>', 'filter by OpenAPI tag')
|
|
238
|
+
.option('--search <text>', 'filter by operation id, summary, path, or tag')
|
|
239
|
+
.option('--refresh', 'refresh the cached OpenAPI schema first')
|
|
240
|
+
.action(async (options) => {
|
|
241
|
+
await openApiList(context(program), options);
|
|
242
|
+
});
|
|
243
|
+
api
|
|
244
|
+
.command('describe')
|
|
245
|
+
.description('describe an OpenAPI operation by operation id or METHOD path')
|
|
246
|
+
.argument('<operationOrMethod>', 'operation id, or HTTP method when path is also passed')
|
|
247
|
+
.argument('[path]', 'OpenAPI path, for example /users')
|
|
248
|
+
.option('--refresh', 'refresh the cached OpenAPI schema first')
|
|
249
|
+
.action(async (operationOrMethod, path, options) => {
|
|
250
|
+
await openApiDescribe(context(program), operationOrMethod, path, options);
|
|
251
|
+
});
|
|
252
|
+
api
|
|
253
|
+
.command('call')
|
|
254
|
+
.description('call an OpenAPI operation by operation id using the active deployment')
|
|
255
|
+
.argument('<operation>', 'OpenAPI operation id')
|
|
256
|
+
.option('--deployment <id>', 'deployment id override; defaults to active deployment')
|
|
257
|
+
.option('--param <key=value>', 'path or query parameter; repeatable', collect, [])
|
|
258
|
+
.option('--body <json>', 'JSON request body')
|
|
259
|
+
.option('--field <key=value>', 'URL-encoded form field; repeatable', collect, [])
|
|
260
|
+
.option('--form <key=value>', 'multipart form field; value @path is treated as a file; repeatable', collect, [])
|
|
261
|
+
.option('--file <key=path>', 'multipart file field; key=path or key=@path; repeatable', collect, [])
|
|
262
|
+
.option('--header <key=value>', 'request header; repeatable', collect, [])
|
|
263
|
+
.option('--refresh', 'refresh the cached OpenAPI schema first')
|
|
264
|
+
.action(async (operation, options) => {
|
|
265
|
+
await openApiCall(context(program), operation, options);
|
|
266
|
+
});
|
|
267
|
+
const schema = api.command('schema').description('manage cached OpenAPI schema');
|
|
268
|
+
schema
|
|
269
|
+
.command('refresh')
|
|
270
|
+
.description('download and cache the latest Wacht Platform OpenAPI schema')
|
|
271
|
+
.action(async () => {
|
|
272
|
+
await openApiRefresh(context(program));
|
|
273
|
+
});
|
|
274
|
+
// ─── Resources ────────────────────────────────────────────────
|
|
275
|
+
const users = program.command('users').description('list, get, or create users in the active deployment');
|
|
276
|
+
users
|
|
277
|
+
.command('list')
|
|
278
|
+
.description('list users in the active deployment')
|
|
279
|
+
.option('--deployment <id>', 'deployment id override')
|
|
280
|
+
.option('--limit <n>', 'page size')
|
|
281
|
+
.option('--offset <n>', 'page offset')
|
|
282
|
+
.option('--search <q>', 'search query')
|
|
283
|
+
.action(async (options) => {
|
|
284
|
+
await listUsers(context(program), options);
|
|
285
|
+
});
|
|
286
|
+
users
|
|
287
|
+
.command('get')
|
|
288
|
+
.description('get a user by id')
|
|
289
|
+
.argument('<userId>', 'user id')
|
|
290
|
+
.option('--deployment <id>', 'deployment id override')
|
|
291
|
+
.action(async (userId, options) => {
|
|
292
|
+
await getUser(context(program), userId, options);
|
|
293
|
+
});
|
|
294
|
+
users
|
|
295
|
+
.command('create')
|
|
296
|
+
.description('create a user via the Machine API')
|
|
297
|
+
.option('--deployment <id>', 'deployment id override')
|
|
298
|
+
.option('--body <json>', 'JSON request body')
|
|
299
|
+
.option('--field <key=value>', 'URL-encoded form field; repeatable', collect, [])
|
|
300
|
+
.option('--form <key=value>', 'multipart form field; repeatable', collect, [])
|
|
301
|
+
.option('--file <key=path>', 'multipart file field; repeatable', collect, [])
|
|
302
|
+
.option('--header <key=value>', 'request header; repeatable', collect, [])
|
|
303
|
+
.action(async (options) => {
|
|
304
|
+
await createUser(context(program), options);
|
|
305
|
+
});
|
|
306
|
+
const orgs = program.command('orgs').alias('organizations').description('list, get, or create organizations');
|
|
307
|
+
orgs
|
|
308
|
+
.command('list')
|
|
309
|
+
.description('list organizations in the active deployment')
|
|
310
|
+
.option('--deployment <id>', 'deployment id override')
|
|
311
|
+
.option('--limit <n>', 'page size')
|
|
312
|
+
.option('--offset <n>', 'page offset')
|
|
313
|
+
.option('--search <q>', 'search query')
|
|
314
|
+
.action(async (options) => {
|
|
315
|
+
await listOrgs(context(program), options);
|
|
316
|
+
});
|
|
317
|
+
orgs
|
|
318
|
+
.command('get')
|
|
319
|
+
.description('get an organization by id')
|
|
320
|
+
.argument('<orgId>', 'organization id')
|
|
321
|
+
.option('--deployment <id>', 'deployment id override')
|
|
322
|
+
.action(async (orgId, options) => {
|
|
323
|
+
await getOrg(context(program), orgId, options);
|
|
324
|
+
});
|
|
325
|
+
orgs
|
|
326
|
+
.command('create')
|
|
327
|
+
.description('create an organization')
|
|
328
|
+
.option('--deployment <id>', 'deployment id override')
|
|
329
|
+
.option('--body <json>', 'JSON request body')
|
|
330
|
+
.option('--field <key=value>', 'URL-encoded form field; repeatable', collect, [])
|
|
331
|
+
.option('--form <key=value>', 'multipart form field; repeatable', collect, [])
|
|
332
|
+
.option('--file <key=path>', 'multipart file field; repeatable', collect, [])
|
|
333
|
+
.option('--header <key=value>', 'request header; repeatable', collect, [])
|
|
334
|
+
.action(async (options) => {
|
|
335
|
+
await createOrg(context(program), options);
|
|
336
|
+
});
|
|
337
|
+
const workspaces = program.command('workspaces').description('list, get, or create workspaces');
|
|
338
|
+
workspaces
|
|
339
|
+
.command('list')
|
|
340
|
+
.description('list workspaces in the active deployment, optionally filtered by org')
|
|
341
|
+
.option('--deployment <id>', 'deployment id override')
|
|
342
|
+
.option('--org <id>', 'list workspaces under a specific organization')
|
|
343
|
+
.option('--limit <n>', 'page size')
|
|
344
|
+
.option('--offset <n>', 'page offset')
|
|
345
|
+
.option('--search <q>', 'search query')
|
|
346
|
+
.action(async (options) => {
|
|
347
|
+
await listWorkspaces(context(program), options);
|
|
348
|
+
});
|
|
349
|
+
workspaces
|
|
350
|
+
.command('get')
|
|
351
|
+
.description('get a workspace by id')
|
|
352
|
+
.argument('<workspaceId>', 'workspace id')
|
|
353
|
+
.option('--deployment <id>', 'deployment id override')
|
|
354
|
+
.action(async (workspaceId, options) => {
|
|
355
|
+
await getWorkspace(context(program), workspaceId, options);
|
|
356
|
+
});
|
|
357
|
+
workspaces
|
|
358
|
+
.command('create')
|
|
359
|
+
.description('create a workspace under an organization')
|
|
360
|
+
.option('--deployment <id>', 'deployment id override')
|
|
361
|
+
.requiredOption('--org <id>', 'organization id (workspaces are scoped to an org)')
|
|
362
|
+
.option('--body <json>', 'JSON request body')
|
|
363
|
+
.option('--field <key=value>', 'URL-encoded form field; repeatable', collect, [])
|
|
364
|
+
.option('--form <key=value>', 'multipart form field; repeatable', collect, [])
|
|
365
|
+
.option('--file <key=path>', 'multipart file field; repeatable', collect, [])
|
|
366
|
+
.option('--header <key=value>', 'request header; repeatable', collect, [])
|
|
367
|
+
.action(async (options) => {
|
|
368
|
+
await createWorkspace(context(program), options);
|
|
369
|
+
});
|
|
370
|
+
// ─── Shell completion ─────────────────────────────────────────
|
|
371
|
+
program
|
|
372
|
+
.command('completion')
|
|
373
|
+
.description('print shell completion script (bash, zsh, fish, powershell)')
|
|
374
|
+
.argument('[shell]', 'bash, zsh, fish, or powershell')
|
|
375
|
+
.action((shellArg) => {
|
|
376
|
+
const shell = (shellArg ?? '').toLowerCase();
|
|
377
|
+
if (!['bash', 'zsh', 'fish', 'powershell'].includes(shell)) {
|
|
378
|
+
console.error('Pass a shell: wacht completion bash | zsh | fish | powershell\n' +
|
|
379
|
+
' bash: wacht completion bash > /etc/bash_completion.d/wacht\n' +
|
|
380
|
+
' zsh: wacht completion zsh > "${fpath[1]}/_wacht"\n' +
|
|
381
|
+
' fish: wacht completion fish > ~/.config/fish/completions/wacht.fish\n' +
|
|
382
|
+
' powershell: wacht completion powershell | Out-String | Invoke-Expression');
|
|
383
|
+
process.exitCode = 1;
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
process.stdout.write(completionScript(shell));
|
|
387
|
+
});
|
|
388
|
+
await program.parseAsync(args, { from: 'user' });
|
|
389
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// Static shell completion scripts for the `wacht` CLI.
|
|
2
|
+
// Generated dynamically from the known top-level commands so adding a command
|
|
3
|
+
// only requires updating the COMMANDS list below.
|
|
4
|
+
const COMMANDS = [
|
|
5
|
+
'init',
|
|
6
|
+
'login',
|
|
7
|
+
'logout',
|
|
8
|
+
'auth',
|
|
9
|
+
'projects',
|
|
10
|
+
'deployments',
|
|
11
|
+
'users',
|
|
12
|
+
'orgs',
|
|
13
|
+
'workspaces',
|
|
14
|
+
'skills',
|
|
15
|
+
'mcp',
|
|
16
|
+
'config',
|
|
17
|
+
'api',
|
|
18
|
+
'completion',
|
|
19
|
+
];
|
|
20
|
+
const SUBCOMMANDS = {
|
|
21
|
+
auth: ['status'],
|
|
22
|
+
projects: ['list', 'create'],
|
|
23
|
+
deployments: ['current', 'select', 'clear', 'create'],
|
|
24
|
+
users: ['list', 'get', 'create'],
|
|
25
|
+
orgs: ['list', 'get', 'create'],
|
|
26
|
+
workspaces: ['list', 'get', 'create'],
|
|
27
|
+
skills: ['install'],
|
|
28
|
+
mcp: ['config'],
|
|
29
|
+
config: ['pull', 'schema', 'template', 'diff', 'apply'],
|
|
30
|
+
api: ['ls', 'describe', 'call', 'schema'],
|
|
31
|
+
completion: ['bash', 'zsh', 'fish', 'powershell'],
|
|
32
|
+
};
|
|
33
|
+
function bashScript() {
|
|
34
|
+
const tops = COMMANDS.join(' ');
|
|
35
|
+
const subs = Object.entries(SUBCOMMANDS)
|
|
36
|
+
.map(([cmd, list]) => ` ${cmd}) COMPREPLY=($(compgen -W "${list.join(' ')}" -- "$cur")); return 0;;`)
|
|
37
|
+
.join('\n');
|
|
38
|
+
return `# bash completion for wacht
|
|
39
|
+
_wacht_complete() {
|
|
40
|
+
local cur prev words cword
|
|
41
|
+
_init_completion -n = || return
|
|
42
|
+
|
|
43
|
+
if [[ $cword -eq 1 ]]; then
|
|
44
|
+
COMPREPLY=($(compgen -W "${tops}" -- "$cur"))
|
|
45
|
+
return 0
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
if [[ $cword -eq 2 ]]; then
|
|
49
|
+
case "\${words[1]}" in
|
|
50
|
+
${subs}
|
|
51
|
+
esac
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
COMPREPLY=()
|
|
55
|
+
}
|
|
56
|
+
complete -F _wacht_complete wacht
|
|
57
|
+
`;
|
|
58
|
+
}
|
|
59
|
+
function zshScript() {
|
|
60
|
+
const cases = COMMANDS.map((cmd) => {
|
|
61
|
+
const subs = SUBCOMMANDS[cmd];
|
|
62
|
+
if (!subs)
|
|
63
|
+
return ` ${cmd}) ;;`;
|
|
64
|
+
const subList = subs.map((s) => `'${s}'`).join(' ');
|
|
65
|
+
return ` ${cmd})\n _values 'subcommand' ${subList}\n ;;`;
|
|
66
|
+
}).join('\n');
|
|
67
|
+
return `#compdef wacht
|
|
68
|
+
# zsh completion for wacht
|
|
69
|
+
_wacht() {
|
|
70
|
+
local context state line
|
|
71
|
+
_arguments -C \\
|
|
72
|
+
'1: :->command' \\
|
|
73
|
+
'2: :->subcommand' \\
|
|
74
|
+
'*::arg:->args'
|
|
75
|
+
|
|
76
|
+
case $state in
|
|
77
|
+
command)
|
|
78
|
+
_values 'wacht command' ${COMMANDS.map((c) => `'${c}'`).join(' ')}
|
|
79
|
+
;;
|
|
80
|
+
subcommand)
|
|
81
|
+
case $words[2] in
|
|
82
|
+
${cases}
|
|
83
|
+
esac
|
|
84
|
+
;;
|
|
85
|
+
esac
|
|
86
|
+
}
|
|
87
|
+
_wacht "$@"
|
|
88
|
+
`;
|
|
89
|
+
}
|
|
90
|
+
function fishScript() {
|
|
91
|
+
const lines = [`# fish completion for wacht`];
|
|
92
|
+
for (const cmd of COMMANDS) {
|
|
93
|
+
lines.push(`complete -c wacht -n '__fish_use_subcommand' -a '${cmd}'`);
|
|
94
|
+
}
|
|
95
|
+
for (const [cmd, subs] of Object.entries(SUBCOMMANDS)) {
|
|
96
|
+
for (const sub of subs) {
|
|
97
|
+
lines.push(`complete -c wacht -n '__fish_seen_subcommand_from ${cmd}' -a '${sub}'`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return `${lines.join('\n')}\n`;
|
|
101
|
+
}
|
|
102
|
+
function powershellScript() {
|
|
103
|
+
const tops = COMMANDS.join("','");
|
|
104
|
+
const cases = Object.entries(SUBCOMMANDS)
|
|
105
|
+
.map(([cmd, subs]) => ` '${cmd}' { @('${subs.join("','")}') }`)
|
|
106
|
+
.join('\n');
|
|
107
|
+
return `# PowerShell completion for wacht
|
|
108
|
+
Register-ArgumentCompleter -Native -CommandName wacht -ScriptBlock {
|
|
109
|
+
param($wordToComplete, $commandAst, $cursorPosition)
|
|
110
|
+
$tokens = $commandAst.CommandElements | ForEach-Object { $_.Value }
|
|
111
|
+
if ($tokens.Length -le 2) {
|
|
112
|
+
@('${tops}') | Where-Object { $_ -like "$wordToComplete*" } |
|
|
113
|
+
ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }
|
|
114
|
+
return
|
|
115
|
+
}
|
|
116
|
+
$sub = switch ($tokens[1]) {
|
|
117
|
+
${cases}
|
|
118
|
+
default { @() }
|
|
119
|
+
}
|
|
120
|
+
$sub | Where-Object { $_ -like "$wordToComplete*" } |
|
|
121
|
+
ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }
|
|
122
|
+
}
|
|
123
|
+
`;
|
|
124
|
+
}
|
|
125
|
+
export function completionScript(shell) {
|
|
126
|
+
switch (shell) {
|
|
127
|
+
case 'bash': return bashScript();
|
|
128
|
+
case 'zsh': return zshScript();
|
|
129
|
+
case 'fish': return fishScript();
|
|
130
|
+
case 'powershell': return powershellScript();
|
|
131
|
+
}
|
|
132
|
+
}
|