@mcowger/plexus-cli 0.0.1 → 0.0.3

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 ADDED
@@ -0,0 +1,46 @@
1
+ # Plexus CLI
2
+
3
+ `@mcowger/plexus-cli` is a Bun-powered command-line client for the Plexus
4
+ management API. It discovers the target server's OpenAPI document at runtime,
5
+ so documented management operations are available without a CLI update.
6
+
7
+ ## Install or run
8
+
9
+ Run once with Bun:
10
+
11
+ ```bash
12
+ bunx @mcowger/plexus-cli api list
13
+ ```
14
+
15
+ Or install the `plexuscli` command globally:
16
+
17
+ ```bash
18
+ bun install -g @mcowger/plexus-cli
19
+ plexuscli --help
20
+ ```
21
+
22
+ ## Connect to Plexus
23
+
24
+ Set the target URL and management key. The local Plexus dev stack defaults to
25
+ `password`; use a real admin key for non-local instances.
26
+
27
+ ```bash
28
+ export PLEXUS_URL=http://localhost:4000
29
+ export PLEXUS_ADMIN_KEY=password
30
+
31
+ plexuscli api call getV0ManagementAuthVerify
32
+ plexuscli api list
33
+ ```
34
+
35
+ Use `--url` and `--admin-key` to override the environment for one invocation.
36
+
37
+ ## Agent guidance
38
+
39
+ Print the packaged skill for complete operational and safety guidance:
40
+
41
+ ```bash
42
+ plexuscli skill > SKILL.md
43
+ ```
44
+
45
+ The skill covers discovery, JSON request bodies, paginated reads, debug traces,
46
+ errors, system logs, and runtime logging/debug configuration.
package/package.json CHANGED
@@ -1,13 +1,25 @@
1
1
  {
2
2
  "name": "@mcowger/plexus-cli",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "Dynamic command line client for Plexus management APIs",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/mcowger/plexus"
8
+ },
5
9
  "type": "module",
6
10
  "bin": {
7
- "plexuscli": "./src/index.ts"
11
+ "plexuscli": "src/index.ts"
8
12
  },
13
+ "files": [
14
+ "README.md",
15
+ "skills",
16
+ "src/cli.ts",
17
+ "src/index.ts",
18
+ "src/skill.ts"
19
+ ],
9
20
  "scripts": {
10
21
  "start": "bun run src/index.ts",
22
+ "prepack": "bun run --cwd ../.. sync:cli-skill",
11
23
  "typecheck": "bun x tsc --noEmit",
12
24
  "test": "bunx --bun vitest run"
13
25
  },
@@ -0,0 +1,192 @@
1
+ ---
2
+ name: plexus-cli
3
+ description: >-
4
+ Use this skill to inspect or administer a running Plexus instance through
5
+ plexuscli. Use it before plexus-rest-api whenever `bun run plexuscli` or
6
+ `plexuscli` is available; it is the preferred interface for live
7
+ request/debug-trace/error/system-log review and runtime configuration.
8
+ ---
9
+
10
+ # Plexus Management CLI
11
+
12
+ Use `plexuscli` to call the Management API. The CLI discovers the running
13
+ server's public OpenAPI document on each invocation, so use `api list` and
14
+ `api describe` when an operation ID is uncertain.
15
+
16
+ ## Preference
17
+
18
+ Use this skill whenever either `bun run plexuscli --help` or
19
+ `plexuscli --help` succeeds. Use `plexus-rest-api` only when neither command
20
+ is available or when this CLI intentionally does not support the required API
21
+ operation, such as SSE streaming.
22
+
23
+ ## Local Dev Setup
24
+
25
+ Get the active worktree server port; do not derive it manually:
26
+
27
+ ```bash
28
+ PORT=$(bun run dev:get:port)
29
+ export PLEXUS_URL="http://localhost:$PORT"
30
+ ```
31
+
32
+ The standard local dev stack uses `password` as its admin key. This is for
33
+ local development only; never assume it for staging or production.
34
+
35
+ ```bash
36
+ export PLEXUS_ADMIN_KEY=password
37
+ ```
38
+
39
+ Run the checkout-local CLI:
40
+
41
+ ```bash
42
+ PLEXUSCLI='bun run plexuscli'
43
+ ```
44
+
45
+ After installation, use `PLEXUSCLI=plexuscli` instead. Verify access before
46
+ making a change:
47
+
48
+ ```bash
49
+ $PLEXUSCLI api call getV0ManagementAuthVerify
50
+ ```
51
+
52
+ For a remote instance, require `PLEXUS_URL` and `PLEXUS_ADMIN_KEY` from the
53
+ user. Never print a supplied key. The CLI does not redact API responses, so
54
+ avoid broad key/config reads unless the user requested them.
55
+
56
+ ## CLI Conventions
57
+
58
+ - `api list` prints the available supported management operations.
59
+ - `api describe <operationId>` shows parameters and body requirements.
60
+ - `api call <operationId>` invokes an operation. An operation ID alone is a
61
+ shorthand for `api call`.
62
+ - Use `--param name=value` for path/query parameters. JSON literals are
63
+ coerced, so `--param limit=50` is numeric and
64
+ `--param fields='["requestId"]'` is an array.
65
+ - Use `--body '{...}'` or `--body-file payload.json` for writes.
66
+ - Use `--output json|yaml|table`. Non-interactive output defaults to JSON;
67
+ interactive output defaults to a deterministic table.
68
+ - Use `--all` only for standardized paginated list operations.
69
+ - `DELETE` and operations named delete, restore, restart, reset, clear,
70
+ rotate, or disable prompt for confirmation. Use `--yes` only when the user
71
+ explicitly requested that action.
72
+ - The CLI deliberately hides SSE operations, including live system-log
73
+ streaming. Use the recent-log endpoint instead.
74
+
75
+ ## Common Read Workflows
76
+
77
+ ### Inspect Available Operations
78
+
79
+ ```bash
80
+ $PLEXUSCLI api list --output table
81
+ $PLEXUSCLI api describe getV0ManagementDebugLogs --output yaml
82
+ ```
83
+
84
+ ### Review Debug Trace Configuration
85
+
86
+ Debug capture configuration is in memory and resets when Plexus restarts.
87
+ Capture is inclusive: global capture, key targets, alias targets, and provider
88
+ targets each independently cause a trace to be captured.
89
+
90
+ ```bash
91
+ $PLEXUSCLI api call getV0ManagementDebug
92
+ ```
93
+
94
+ ### Review Recent Debug Traces
95
+
96
+ List newest traces, then request the full trace by ID:
97
+
98
+ ```bash
99
+ $PLEXUSCLI api call getV0ManagementDebugLogs --param limit=50
100
+
101
+ $PLEXUSCLI api call getV0ManagementDebugLogsByrequestId \
102
+ --param requestId='<request-id>' \
103
+ --output json
104
+ ```
105
+
106
+ Trace payloads and headers can contain customer data or secrets. Report only
107
+ the specific redacted fields needed to diagnose the issue.
108
+
109
+ ### Review Inference Errors
110
+
111
+ ```bash
112
+ $PLEXUSCLI api call getV0ManagementErrors --param limit=50
113
+
114
+ # Retrieve every documented error page only when the user needs the full set.
115
+ $PLEXUSCLI api call getV0ManagementErrors --all --param limit=100
116
+ ```
117
+
118
+ ### Review Recent System Logs
119
+
120
+ ```bash
121
+ $PLEXUSCLI api call getV0SystemLogsRecent --param limit=100
122
+
123
+ $PLEXUSCLI api call getV0SystemLogsRecent --all --param limit=100
124
+ ```
125
+
126
+ The recent-log endpoint is bounded in memory. It is not a persistent audit
127
+ log, and `--all` can only retrieve entries the server still retains.
128
+
129
+ ## Runtime Configuration Workflows
130
+
131
+ Inspect the current state before changing it. State the intended change to the
132
+ user, then send the smallest PATCH/PUT body that satisfies the API.
133
+
134
+ ### Adjust Debug Trace Capture
135
+
136
+ Enable temporary global capture:
137
+
138
+ ```bash
139
+ $PLEXUSCLI api call patchV0ManagementDebug --body '{"enabled":true}'
140
+ ```
141
+
142
+ Capture only selected request dimensions while global capture stays off:
143
+
144
+ ```bash
145
+ $PLEXUSCLI api call patchV0ManagementDebug \
146
+ --body '{"enabled":false,"keys":["mobile-app"],"aliases":["support"],"providers":["openai"]}'
147
+ ```
148
+
149
+ Clear provider targeting and disable global capture after the investigation:
150
+
151
+ ```bash
152
+ $PLEXUSCLI api call patchV0ManagementDebug \
153
+ --body '{"enabled":false,"providers":null}'
154
+ ```
155
+
156
+ `keys`, `aliases`, and `providers` replace their corresponding target lists;
157
+ send `null` or `[]` to clear one. Do not leave broad debug capture enabled
158
+ longer than required.
159
+
160
+ ### Adjust Runtime Log Verbosity
161
+
162
+ Read the current and startup log levels:
163
+
164
+ ```bash
165
+ $PLEXUSCLI api call getV0ManagementLoggingLevel
166
+ ```
167
+
168
+ Increase temporary detail:
169
+
170
+ ```bash
171
+ $PLEXUSCLI api call putV0ManagementLoggingLevel --body '{"level":"debug"}'
172
+ ```
173
+
174
+ Valid levels, least to most verbose: `error`, `warn`, `info`, `debug`,
175
+ `verbose`, `silly`. The setting is runtime-only and reverts on restart.
176
+
177
+ Reset it to the startup default only when explicitly requested:
178
+
179
+ ```bash
180
+ $PLEXUSCLI api call deleteV0ManagementLoggingLevel --yes
181
+ ```
182
+
183
+ ## Failure Handling
184
+
185
+ - If spec discovery fails, confirm the URL with `bun run dev:get:port` locally
186
+ or ask for the remote base URL.
187
+ - If an operation ID is unavailable, run `api list`; do not guess a path or
188
+ substitute raw HTTP requests.
189
+ - On HTTP validation errors, report the exact response and stop rather than
190
+ guessing a new payload.
191
+ - For a 401/403, verify the correct `PLEXUS_ADMIN_KEY` was supplied and do not
192
+ retry with unrelated credentials.
package/src/cli.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { plexusCliSkill } from './skill';
2
+
1
3
  export const DEFAULT_URL = 'http://localhost:4000';
2
4
  const OPENAPI_PATH = '/.well-known/plexus/openapi.json';
3
5
  const ALLOWED_PATH = /^\/v0\/(management\/|system\/logs\/)/;
@@ -115,15 +117,24 @@ export function discoverOperations(document: {
115
117
  const candidates: Operation[] = [];
116
118
  for (const [path, pathItem] of Object.entries(document.paths ?? {})) {
117
119
  if (!ALLOWED_PATH.test(path)) continue;
120
+ const pathParameters = Array.isArray(pathItem.parameters)
121
+ ? (pathItem.parameters as OpenApiParameter[])
122
+ : [];
118
123
  for (const [method, value] of Object.entries(pathItem)) {
119
124
  if (!HTTP_METHODS.has(method) || !value || typeof value !== 'object') continue;
120
125
  const operation = value as OpenApiOperation;
121
126
  if (isStreamOperation(operation)) continue;
127
+ const parameters = new Map(
128
+ pathParameters.map((parameter) => [`${parameter.in}:${parameter.name}`, parameter])
129
+ );
130
+ for (const parameter of operation.parameters ?? []) {
131
+ parameters.set(`${parameter.in}:${parameter.name}`, parameter);
132
+ }
122
133
  candidates.push({
123
134
  id: operation.operationId ?? fallbackId(method, path, operation.tags),
124
135
  method,
125
136
  path,
126
- operation,
137
+ operation: { ...operation, parameters: [...parameters.values()] },
127
138
  });
128
139
  }
129
140
  }
@@ -278,7 +289,7 @@ async function fetchAllPages(
278
289
  limit ??= page.data.length;
279
290
  if (!limit || page.data.length === 0) break;
280
291
  allData.push(...page.data);
281
- offset += limit;
292
+ offset += page.data.length;
282
293
  }
283
294
 
284
295
  return { data: allData, total: total ?? allData.length, limit: limit ?? 0, offset: 0 };
@@ -290,7 +301,15 @@ export function formatOutput(value: unknown, output: 'json' | 'yaml' | 'table'):
290
301
  return `${JSON.stringify(value, null, 2)}\n`;
291
302
  }
292
303
 
293
- const HELP = `Usage: plexuscli [options] api <list|describe|call> [operation]
304
+ const HELP = `Usage: plexuscli [options] <skill|api> [operation]
305
+
306
+ AGENTS: Run \`plexuscli skill\` to print the \`plexus-cli\` skill and understand how to use this tool.
307
+
308
+ Commands:
309
+ skill Print the bundled plexus-cli SKILL.md
310
+ api list List discovered management operations
311
+ api describe OPERATION Describe a discovered operation
312
+ api call OPERATION Call a discovered operation
294
313
 
295
314
  Options:
296
315
  --url URL Plexus URL (default: PLEXUS_URL or ${DEFAULT_URL})
@@ -325,6 +344,10 @@ export async function run(
325
344
  runtime.stdout(HELP);
326
345
  return 0;
327
346
  }
347
+ if (args.positionals.length === 1 && args.positionals[0] === 'skill') {
348
+ runtime.stdout(plexusCliSkill);
349
+ return 0;
350
+ }
328
351
  const baseUrl = args.url.replace(/\/$/, '');
329
352
  const specResponse = await runtime.fetch(`${baseUrl}${OPENAPI_PATH}`, { cache: 'no-store' });
330
353
  if (!specResponse.ok)
@@ -391,6 +414,8 @@ export async function run(
391
414
  if (args.adminKey) request.headers.set('x-admin-key', args.adminKey);
392
415
  if (body) request.headers.set('content-type', 'application/json');
393
416
  if (args.all) {
417
+ if (operation.method !== 'get')
418
+ throw new CliError('--all can only be used with GET operations');
394
419
  if (body) throw new CliError('--all cannot be used with a request body');
395
420
  runtime.stdout(
396
421
  formatOutput(
package/src/skill.ts ADDED
@@ -0,0 +1 @@
1
+ export const plexusCliSkill = "---\nname: plexus-cli\ndescription: >-\n Use this skill to inspect or administer a running Plexus instance through\n plexuscli. Use it before plexus-rest-api whenever `bun run plexuscli` or\n `plexuscli` is available; it is the preferred interface for live\n request/debug-trace/error/system-log review and runtime configuration.\n---\n\n# Plexus Management CLI\n\nUse `plexuscli` to call the Management API. The CLI discovers the running\nserver's public OpenAPI document on each invocation, so use `api list` and\n`api describe` when an operation ID is uncertain.\n\n## Preference\n\nUse this skill whenever either `bun run plexuscli --help` or\n`plexuscli --help` succeeds. Use `plexus-rest-api` only when neither command\nis available or when this CLI intentionally does not support the required API\noperation, such as SSE streaming.\n\n## Local Dev Setup\n\nGet the active worktree server port; do not derive it manually:\n\n```bash\nPORT=$(bun run dev:get:port)\nexport PLEXUS_URL=\"http://localhost:$PORT\"\n```\n\nThe standard local dev stack uses `password` as its admin key. This is for\nlocal development only; never assume it for staging or production.\n\n```bash\nexport PLEXUS_ADMIN_KEY=password\n```\n\nRun the checkout-local CLI:\n\n```bash\nPLEXUSCLI='bun run plexuscli'\n```\n\nAfter installation, use `PLEXUSCLI=plexuscli` instead. Verify access before\nmaking a change:\n\n```bash\n$PLEXUSCLI api call getV0ManagementAuthVerify\n```\n\nFor a remote instance, require `PLEXUS_URL` and `PLEXUS_ADMIN_KEY` from the\nuser. Never print a supplied key. The CLI does not redact API responses, so\navoid broad key/config reads unless the user requested them.\n\n## CLI Conventions\n\n- `api list` prints the available supported management operations.\n- `api describe <operationId>` shows parameters and body requirements.\n- `api call <operationId>` invokes an operation. An operation ID alone is a\n shorthand for `api call`.\n- Use `--param name=value` for path/query parameters. JSON literals are\n coerced, so `--param limit=50` is numeric and\n `--param fields='[\"requestId\"]'` is an array.\n- Use `--body '{...}'` or `--body-file payload.json` for writes.\n- Use `--output json|yaml|table`. Non-interactive output defaults to JSON;\n interactive output defaults to a deterministic table.\n- Use `--all` only for standardized paginated list operations.\n- `DELETE` and operations named delete, restore, restart, reset, clear,\n rotate, or disable prompt for confirmation. Use `--yes` only when the user\n explicitly requested that action.\n- The CLI deliberately hides SSE operations, including live system-log\n streaming. Use the recent-log endpoint instead.\n\n## Common Read Workflows\n\n### Inspect Available Operations\n\n```bash\n$PLEXUSCLI api list --output table\n$PLEXUSCLI api describe getV0ManagementDebugLogs --output yaml\n```\n\n### Review Debug Trace Configuration\n\nDebug capture configuration is in memory and resets when Plexus restarts.\nCapture is inclusive: global capture, key targets, alias targets, and provider\ntargets each independently cause a trace to be captured.\n\n```bash\n$PLEXUSCLI api call getV0ManagementDebug\n```\n\n### Review Recent Debug Traces\n\nList newest traces, then request the full trace by ID:\n\n```bash\n$PLEXUSCLI api call getV0ManagementDebugLogs --param limit=50\n\n$PLEXUSCLI api call getV0ManagementDebugLogsByrequestId \\\n --param requestId='<request-id>' \\\n --output json\n```\n\nTrace payloads and headers can contain customer data or secrets. Report only\nthe specific redacted fields needed to diagnose the issue.\n\n### Review Inference Errors\n\n```bash\n$PLEXUSCLI api call getV0ManagementErrors --param limit=50\n\n# Retrieve every documented error page only when the user needs the full set.\n$PLEXUSCLI api call getV0ManagementErrors --all --param limit=100\n```\n\n### Review Recent System Logs\n\n```bash\n$PLEXUSCLI api call getV0SystemLogsRecent --param limit=100\n\n$PLEXUSCLI api call getV0SystemLogsRecent --all --param limit=100\n```\n\nThe recent-log endpoint is bounded in memory. It is not a persistent audit\nlog, and `--all` can only retrieve entries the server still retains.\n\n## Runtime Configuration Workflows\n\nInspect the current state before changing it. State the intended change to the\nuser, then send the smallest PATCH/PUT body that satisfies the API.\n\n### Adjust Debug Trace Capture\n\nEnable temporary global capture:\n\n```bash\n$PLEXUSCLI api call patchV0ManagementDebug --body '{\"enabled\":true}'\n```\n\nCapture only selected request dimensions while global capture stays off:\n\n```bash\n$PLEXUSCLI api call patchV0ManagementDebug \\\n --body '{\"enabled\":false,\"keys\":[\"mobile-app\"],\"aliases\":[\"support\"],\"providers\":[\"openai\"]}'\n```\n\nClear provider targeting and disable global capture after the investigation:\n\n```bash\n$PLEXUSCLI api call patchV0ManagementDebug \\\n --body '{\"enabled\":false,\"providers\":null}'\n```\n\n`keys`, `aliases`, and `providers` replace their corresponding target lists;\nsend `null` or `[]` to clear one. Do not leave broad debug capture enabled\nlonger than required.\n\n### Adjust Runtime Log Verbosity\n\nRead the current and startup log levels:\n\n```bash\n$PLEXUSCLI api call getV0ManagementLoggingLevel\n```\n\nIncrease temporary detail:\n\n```bash\n$PLEXUSCLI api call putV0ManagementLoggingLevel --body '{\"level\":\"debug\"}'\n```\n\nValid levels, least to most verbose: `error`, `warn`, `info`, `debug`,\n`verbose`, `silly`. The setting is runtime-only and reverts on restart.\n\nReset it to the startup default only when explicitly requested:\n\n```bash\n$PLEXUSCLI api call deleteV0ManagementLoggingLevel --yes\n```\n\n## Failure Handling\n\n- If spec discovery fails, confirm the URL with `bun run dev:get:port` locally\n or ask for the remote base URL.\n- If an operation ID is unavailable, run `api list`; do not guess a path or\n substitute raw HTTP requests.\n- On HTTP validation errors, report the exact response and stop rather than\n guessing a new payload.\n- For a 401/403, verify the correct `PLEXUS_ADMIN_KEY` was supplied and do not\n retry with unrelated credentials.\n";
@@ -1,177 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import {
3
- buildRequest,
4
- discoverOperations,
5
- formatOutput,
6
- isRisky,
7
- parseArgs,
8
- run,
9
- type Json,
10
- type Operation,
11
- } from '../cli';
12
-
13
- const document = {
14
- paths: {
15
- '/v0/management/keys/{id}': {
16
- delete: {
17
- operationId: 'deleteKey',
18
- parameters: [{ name: 'id', in: 'path', required: true }],
19
- },
20
- get: { operationId: 'getKey', tags: ['Keys'] },
21
- },
22
- '/v0/system/logs/stream': {
23
- get: {
24
- operationId: 'streamLogs',
25
- responses: { 200: { content: { 'text/event-stream': {} } } },
26
- },
27
- },
28
- '/v1/models': { get: { operationId: 'listModels' } },
29
- },
30
- };
31
-
32
- describe('dynamic OpenAPI routing', () => {
33
- it('limits discovery to supported non-stream operations', () => {
34
- expect(discoverOperations(document).map((operation) => operation.id)).toEqual([
35
- 'deleteKey',
36
- 'getKey',
37
- ]);
38
- });
39
-
40
- it('builds encoded path and query parameters', () => {
41
- const operation: Operation = {
42
- id: 'get',
43
- method: 'get',
44
- path: '/v0/management/items/{id}',
45
- operation: {
46
- parameters: [
47
- { name: 'id', in: 'path', required: true },
48
- { name: 'limit', in: 'query' },
49
- ],
50
- },
51
- };
52
- const request = buildRequest(
53
- operation,
54
- new Map([
55
- ['id', 'a/b' as Json],
56
- ['limit', 10 as Json],
57
- ])
58
- );
59
- expect(request.url).toBe('/v0/management/items/a%2Fb?limit=10');
60
- });
61
-
62
- it('recognizes destructive operations', () => {
63
- expect(isRisky(discoverOperations(document)[0]!)).toBe(true);
64
- });
65
- });
66
-
67
- describe('arguments and output', () => {
68
- it('uses environment defaults and coerces JSON literal parameters', () => {
69
- const args = parseArgs(['api', 'call', 'getKey', '--param', 'limit=10'], {
70
- PLEXUS_URL: 'http://plexus',
71
- });
72
- expect(args.url).toBe('http://plexus');
73
- expect(args.params.get('limit')).toBe(10);
74
- });
75
-
76
- it('parses the all-pages flag', () => {
77
- expect(parseArgs(['api', 'call', 'getKey', '--all'], {}).all).toBe(true);
78
- });
79
-
80
- it('formats tables with deterministic columns', () => {
81
- expect(formatOutput([{ b: 2, a: 'one' }], 'table')).toBe('a b\n--- -\none 2\n');
82
- });
83
- });
84
-
85
- describe('execution', () => {
86
- it('fetches the schema without caching and uses JSON for non-TTY output', async () => {
87
- const requests: Array<{ url: string; init?: RequestInit }> = [];
88
- let stdout = '';
89
- const exitCode = await run(
90
- ['api', 'call', 'getKey'],
91
- {},
92
- {
93
- fetch: async (url, init) => {
94
- requests.push({ url: String(url), init });
95
- return requests.length === 1
96
- ? new Response(JSON.stringify(document))
97
- : new Response(JSON.stringify({ value: true }));
98
- },
99
- stdin: async () => '',
100
- stdout: (text) => {
101
- stdout += text;
102
- },
103
- stderr: () => {},
104
- isTTY: false,
105
- confirm: async () => false,
106
- }
107
- );
108
- expect(exitCode).toBe(0);
109
- expect(requests[0]).toMatchObject({
110
- url: 'http://localhost:4000/.well-known/plexus/openapi.json',
111
- init: { cache: 'no-store' },
112
- });
113
- expect(stdout).toBe('{\n "value": true\n}\n');
114
- });
115
-
116
- it('sends the management credential with x-admin-key', async () => {
117
- const requests: Array<{ url: string; init?: RequestInit }> = [];
118
- const exitCode = await run(
119
- ['api', 'call', 'getKey', '--admin-key', 'secret'],
120
- {},
121
- {
122
- fetch: async (url, init) => {
123
- requests.push({ url: String(url), init });
124
- return requests.length === 1
125
- ? new Response(JSON.stringify(document))
126
- : new Response(JSON.stringify({ value: true }));
127
- },
128
- stdin: async () => '',
129
- stdout: () => {},
130
- stderr: () => {},
131
- isTTY: false,
132
- confirm: async () => false,
133
- }
134
- );
135
- expect(exitCode).toBe(0);
136
- expect(new Headers(requests[1]?.init?.headers).get('x-admin-key')).toBe('secret');
137
- });
138
-
139
- it('retrieves all standardized pages', async () => {
140
- const paginatedDocument = {
141
- paths: {
142
- '/v0/management/items': {
143
- get: {
144
- operationId: 'listItems',
145
- parameters: [
146
- { name: 'limit', in: 'query' },
147
- { name: 'offset', in: 'query' },
148
- ],
149
- },
150
- },
151
- },
152
- };
153
- let stdout = '';
154
- const exitCode = await run(
155
- ['api', 'call', 'listItems', '--all', '--param', 'limit=2'],
156
- {},
157
- {
158
- fetch: async (url) => {
159
- if (String(url).includes('openapi.json'))
160
- return new Response(JSON.stringify(paginatedDocument));
161
- return String(url).includes('offset=2')
162
- ? new Response(JSON.stringify({ data: [3], total: 3, limit: 2, offset: 2 }))
163
- : new Response(JSON.stringify({ data: [1, 2], total: 3, limit: 2, offset: 0 }));
164
- },
165
- stdin: async () => '',
166
- stdout: (text) => {
167
- stdout += text;
168
- },
169
- stderr: () => {},
170
- isTTY: false,
171
- confirm: async () => true,
172
- }
173
- );
174
- expect(exitCode).toBe(0);
175
- expect(JSON.parse(stdout)).toMatchObject({ data: [1, 2, 3], total: 3 });
176
- });
177
- });
@@ -1,112 +0,0 @@
1
- import { rm } from 'node:fs/promises';
2
- import { tmpdir } from 'node:os';
3
- import { join } from 'node:path';
4
- import { afterAll, afterEach, beforeAll, describe, expect, test } from 'vitest';
5
-
6
- const openApiDocument = {
7
- openapi: '3.1.0',
8
- paths: {
9
- '/v0/management/usage': {
10
- get: {
11
- operationId: 'getV0ManagementUsage',
12
- parameters: [
13
- { name: 'limit', in: 'query' },
14
- { name: 'offset', in: 'query' },
15
- ],
16
- responses: {
17
- 200: {
18
- content: {
19
- 'application/json': {},
20
- },
21
- },
22
- },
23
- },
24
- },
25
- },
26
- };
27
-
28
- describe('plexuscli end-to-end', () => {
29
- let server: ReturnType<typeof Bun.serve> | undefined;
30
- const buildDir = join(tmpdir(), `plexuscli-e2e-${crypto.randomUUID()}`);
31
- let cliPath: string;
32
-
33
- beforeAll(async () => {
34
- const result = await Bun.build({
35
- entrypoints: [join(process.cwd(), 'src', 'index.ts')],
36
- outdir: buildDir,
37
- compile: true,
38
- target: 'bun',
39
- });
40
- if (!result.success) throw new Error(result.logs.map((log) => log.message).join('\n'));
41
- cliPath = result.outputs[0]!.path;
42
- });
43
-
44
- afterEach(() => {
45
- server?.stop(true);
46
- server = undefined;
47
- });
48
-
49
- afterAll(async () => {
50
- await rm(buildDir, { force: true, recursive: true });
51
- });
52
-
53
- test('discovers and calls a paginated management operation', async () => {
54
- const adminKeys: string[] = [];
55
- server = Bun.serve({
56
- port: 0,
57
- fetch(request) {
58
- const url = new URL(request.url);
59
- if (url.pathname === '/.well-known/plexus/openapi.json') {
60
- return Response.json(openApiDocument);
61
- }
62
-
63
- if (url.pathname === '/v0/management/usage') {
64
- adminKeys.push(request.headers.get('x-admin-key') ?? '');
65
- const offset = Number(url.searchParams.get('offset') ?? '0');
66
- return Response.json(
67
- offset === 0
68
- ? { data: [{ requestId: 'one' }], total: 2, limit: 1, offset }
69
- : { data: [{ requestId: 'two' }], total: 2, limit: 1, offset }
70
- );
71
- }
72
-
73
- return new Response('Not Found', { status: 404 });
74
- },
75
- });
76
-
77
- const child = Bun.spawn(
78
- [
79
- cliPath,
80
- '--url',
81
- `http://localhost:${server.port}`,
82
- '--admin-key',
83
- 'e2e-admin-key',
84
- 'api',
85
- 'call',
86
- 'getV0ManagementUsage',
87
- '--all',
88
- '--param',
89
- 'limit=1',
90
- '--output',
91
- 'json',
92
- ],
93
- {
94
- stdout: 'pipe',
95
- stderr: 'pipe',
96
- }
97
- );
98
- const [stdout, stderr, exitCode] = await Promise.all([
99
- new Response(child.stdout).text(),
100
- new Response(child.stderr).text(),
101
- child.exited,
102
- ]);
103
-
104
- expect(exitCode).toBe(0);
105
- expect(stderr).toBe('');
106
- expect(JSON.parse(stdout)).toMatchObject({
107
- data: [{ requestId: 'one' }, { requestId: 'two' }],
108
- total: 2,
109
- });
110
- expect(adminKeys).toEqual(['e2e-admin-key', 'e2e-admin-key']);
111
- });
112
- });
package/tsconfig.json DELETED
@@ -1,7 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.json",
3
- "compilerOptions": {
4
- "types": ["bun-types"]
5
- },
6
- "include": ["src", "test"]
7
- }