@quantum-hub/qhubctl 1.1.5 → 1.3.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.
Files changed (77) hide show
  1. package/README.md +136 -20
  2. package/dist/client/kqh/models/UpdateWorkflowServiceRequest.js +0 -11
  3. package/dist/commands/compress.d.ts +2 -0
  4. package/dist/commands/compress.js +23 -0
  5. package/dist/commands/datapool-upload.d.ts +2 -0
  6. package/dist/commands/datapool-upload.js +184 -0
  7. package/dist/commands/get-context.d.ts +2 -0
  8. package/dist/commands/get-context.js +12 -0
  9. package/dist/commands/init.d.ts +2 -0
  10. package/dist/commands/init.js +66 -0
  11. package/dist/commands/list-contexts.d.ts +2 -0
  12. package/dist/commands/list-contexts.js +28 -0
  13. package/dist/commands/login.d.ts +2 -0
  14. package/dist/commands/login.js +59 -0
  15. package/dist/commands/logout.d.ts +2 -0
  16. package/dist/commands/logout.js +20 -0
  17. package/dist/commands/openapi.d.ts +2 -0
  18. package/dist/commands/openapi.js +244 -0
  19. package/dist/commands/run.d.ts +2 -0
  20. package/dist/commands/run.js +126 -0
  21. package/dist/commands/serve.d.ts +2 -0
  22. package/dist/commands/serve.js +35 -0
  23. package/dist/commands/services.d.ts +2 -0
  24. package/dist/commands/services.js +48 -0
  25. package/dist/commands/set-context.d.ts +2 -0
  26. package/dist/commands/set-context.js +52 -0
  27. package/dist/commands/up.d.ts +2 -0
  28. package/dist/commands/up.js +161 -0
  29. package/dist/index.d.ts +1 -1
  30. package/dist/index.js +1 -1
  31. package/dist/lib/config-dir.d.ts +6 -0
  32. package/dist/lib/config-dir.js +8 -0
  33. package/dist/lib/entrypoint-resolver.js +2 -2
  34. package/dist/lib/output.d.ts +14 -0
  35. package/dist/lib/output.js +22 -0
  36. package/dist/main.d.ts +2 -0
  37. package/dist/main.js +8 -0
  38. package/dist/model/command.d.ts +14 -11
  39. package/dist/model/command.js +44 -40
  40. package/dist/program.d.ts +2 -0
  41. package/dist/program.js +47 -0
  42. package/dist/service/command-service.d.ts +1 -5
  43. package/dist/service/command-service.js +1 -5
  44. package/dist/service/quantum-hub-service.d.ts +1 -3
  45. package/dist/service/quantum-hub-service.js +2 -2
  46. package/package.json +30 -61
  47. package/bin/dev.cmd +0 -3
  48. package/bin/dev.js +0 -17
  49. package/bin/run.cmd +0 -3
  50. package/bin/run.js +0 -12
  51. package/dist/commands/compress/index.d.ts +0 -6
  52. package/dist/commands/compress/index.js +0 -25
  53. package/dist/commands/datapool/upload.d.ts +0 -23
  54. package/dist/commands/datapool/upload.js +0 -260
  55. package/dist/commands/get-context/index.d.ts +0 -6
  56. package/dist/commands/get-context/index.js +0 -10
  57. package/dist/commands/init/index.d.ts +0 -10
  58. package/dist/commands/init/index.js +0 -75
  59. package/dist/commands/list-contexts/index.d.ts +0 -9
  60. package/dist/commands/list-contexts/index.js +0 -30
  61. package/dist/commands/login/index.d.ts +0 -10
  62. package/dist/commands/login/index.js +0 -62
  63. package/dist/commands/logout/index.d.ts +0 -6
  64. package/dist/commands/logout/index.js +0 -18
  65. package/dist/commands/openapi/index.d.ts +0 -26
  66. package/dist/commands/openapi/index.js +0 -307
  67. package/dist/commands/run/index.d.ts +0 -18
  68. package/dist/commands/run/index.js +0 -150
  69. package/dist/commands/serve/index.d.ts +0 -13
  70. package/dist/commands/serve/index.js +0 -87
  71. package/dist/commands/services/index.d.ts +0 -14
  72. package/dist/commands/services/index.js +0 -65
  73. package/dist/commands/set-context/index.d.ts +0 -17
  74. package/dist/commands/set-context/index.js +0 -59
  75. package/dist/commands/up/index.d.ts +0 -19
  76. package/dist/commands/up/index.js +0 -190
  77. package/oclif.manifest.json +0 -530
package/README.md CHANGED
@@ -1,40 +1,156 @@
1
- # Quantum Hub Control
1
+ # qhubctl
2
2
 
3
- The Quantum Hub Control, qhubctl, is a Command Line Interface (CLI) which lets you interact with the Kipu Quantum Platform from your terminal.
4
- We have installation instructions to guide you through the initial setup in our
5
- [quickstart](https://docs.hub.kipu-quantum.com/quickstart.html) guide.
3
+ `qhubctl`, is a Command Line Interface (CLI) which lets you interact with the Kipu Quantum Hub from your terminal.
6
4
 
7
- Detailed information about the supported commands can be found in our
8
- [CLI reference](https://docs.hub.kipu-quantum.com/cli-reference.html).
5
+ Detailed information about the supported commands can be found in our [CLI reference](https://docs.hub.kipu-quantum.com/cli-reference.html).
9
6
 
10
7
  ## Installation
11
8
 
12
- To install qhubctl, you must install Node.js and the npm command line interface using either a
13
- [Node version manager](https://github.com/nvm-sh/nvm) or a [Node installer](https://nodejs.org/en/download).
14
-
15
- **Make sure you install Node.js version 18 or higher.**
16
-
17
- Then install the qhubctl globally using npm:
9
+ Node.js 18 or higher is required.
10
+ Install it via a [Node version manager](https://github.com/nvm-sh/nvm) or a [Node installer](https://nodejs.org/en/download).
18
11
 
19
12
  ```bash
20
13
  npm install -g @quantum-hub/qhubctl
21
14
  ```
22
15
 
23
- You can use the `--help` flag to get information about the supported commands:
16
+ > **Troubleshooting:** If you see `EACCES permissions` errors, use [nvm](https://github.com/nvm-sh/nvm) or see the [npm docs](https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally).
17
+
18
+ ## Usage
24
19
 
25
- ``` bash
20
+ ```bash
26
21
  qhubctl --help
22
+ qhubctl <command> --help
27
23
  ```
28
24
 
29
- You may also get information about a specific command:
25
+ ## Development
30
26
 
31
- ``` bash
32
- qhubctl <command> --help
27
+ ### Build
28
+
29
+ ```bash
30
+ npm install
31
+ npm run build
33
32
  ```
34
33
 
35
- > **Troubleshooting:**
36
- > If you experience `EACCES permissions` errors during installation, we recommend using [Node version manager](https://github.com/nvm-sh/nvm) (nvm).
37
- > Further, you may refer to the official [npm documentation](https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally) how to resolve such issues.
34
+ Compiles TypeScript from `src/` to `dist/`.
35
+ The CLI entry point is `dist/main.js`.
36
+
37
+ ### Run locally
38
+
39
+ ```bash
40
+ node dist/main.js <command>
41
+ ```
42
+
43
+ ### Watch mode
44
+
45
+ For active development, run the TypeScript compiler in watch mode so every file save triggers an incremental rebuild:
46
+
47
+ ```bash
48
+ npm run dev
49
+ ```
50
+
51
+ `qhubctl` will reflect your latest changes on the next invocation without any manual rebuild step.
52
+
53
+ ### Linking globally with npm link
54
+
55
+ `npm link` registers a global `qhubctl` symlink that points permanently to `dist/main.js` in your local checkout. Run it once after the initial setup:
56
+
57
+ ```bash
58
+ npm run build
59
+ npm link
60
+ qhubctl <command> # now resolves to your local build
61
+ ```
62
+
63
+ The symlink persists across rebuilds and watch-mode recompiles — you never need to run `npm link` again unless you delete and re-clone the repo or change the `"bin"` entry in `package.json`.
64
+
65
+ > **Note:** `npm link` and a globally installed published version of qhubctl are mutually exclusive — they occupy the same path. The last one to run wins. Use `which qhubctl` or `qhubctl --version` to confirm which is currently active.
66
+
67
+ To switch back to the published version from npm:
68
+
69
+ ```bash
70
+ npm unlink -g @quantum-hub/qhubctl # removes the local symlink
71
+ npm install -g @quantum-hub/qhubctl # installs the published version
72
+ ```
73
+
74
+ To go back to your local dev version:
75
+
76
+ ```bash
77
+ npm link
78
+ ```
79
+
80
+ ### Run against staging
81
+
82
+ ```bash
83
+ node dist/main.js login --base-path https://api.hub.34.90.225.20.nip.io -t <personal access token>
84
+ ```
85
+
86
+ ### Debug output
87
+
88
+ Set `DEBUG=*` to print all debug output:
89
+
90
+ ```bash
91
+ DEBUG=* node dist/main.js <command>
92
+ ```
93
+
94
+ On Windows, set the variable separately:
95
+
96
+ ```cmd
97
+ set DEBUG=*
98
+ node dist\main.js <command>
99
+ ```
100
+
101
+ ### Tests
102
+
103
+ ```bash
104
+ npm test # run tests + lint
105
+ npm run test:coverage # run tests with lcov coverage report
106
+ ```
107
+
108
+ ### Lint
109
+
110
+ ```bash
111
+ npm run lint
112
+ ```
113
+
114
+ ### Generate docs
115
+
116
+ Produces a Markdown CLI reference at `docs/cli-reference.md` from the compiled program:
117
+
118
+ ```bash
119
+ npm run build
120
+ npm run docs:generate
121
+ ```
122
+
123
+ ## Regenerating the API client
124
+
125
+ The generated clients in `src/client/` are produced from the platform OpenAPI specs using [openapi-generator-cli](https://github.com/OpenAPITools/openapi-generator-cli).
126
+
127
+ ```bash
128
+ npm install -g @openapitools/openapi-generator-cli
129
+ ```
130
+
131
+ Against production:
132
+
133
+ ```bash
134
+ openapi-generator-cli generate -g typescript-fetch \
135
+ -i https://api.hub.kipu-quantum.com/qc-catalog/docs \
136
+ -o src/client/kqh
137
+
138
+ openapi-generator-cli generate -g typescript-fetch \
139
+ -i https://api.hub.kipu-quantum.com/user-service/docs \
140
+ -o src/client/users
141
+ ```
142
+
143
+ Against staging:
144
+
145
+ ```bash
146
+ openapi-generator-cli generate -g typescript-fetch \
147
+ -i https://api.hub.34.90.225.20.nip.io/qc-catalog/docs \
148
+ -o src/client/kqh
149
+
150
+ openapi-generator-cli generate -g typescript-fetch \
151
+ -i https://api.hub.34.90.225.20.nip.io/user-service/docs \
152
+ -o src/client/users
153
+ ```
38
154
 
39
155
  ## License
40
156
 
@@ -1,16 +1,5 @@
1
1
  /* tslint:disable */
2
2
  /* eslint-disable */
3
- /**
4
- * Quantum Hub Platform API
5
- * Part of the OpenAPI specification for the Quantum Hub Platform.
6
- *
7
- * The version of the OpenAPI document: v1
8
- * Contact: info@anaqor.io
9
- *
10
- * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
11
- * https://openapi-generator.tech
12
- * Do not edit the class manually.
13
- */
14
3
  /**
15
4
  * Check if a given object implements the UpdateWorkflowServiceRequest interface.
16
5
  */
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const compressCommand: Command;
@@ -0,0 +1,23 @@
1
+ import { Command } from 'commander';
2
+ import fs from 'fs-extra';
3
+ import path from 'node:path';
4
+ import { zipProjectFolder } from '../lib/up/zip.js';
5
+ import { spinner, stdout } from '../lib/output.js';
6
+ const EXAMPLES = `
7
+ Examples:
8
+ $ qhubctl compress`;
9
+ export const compressCommand = new Command('compress')
10
+ .description("Compresses the current project and creates a ZIP file. Use the '.dockerignore' file to exclude files and directories from the ZIP file.")
11
+ .addHelpText('after', EXAMPLES)
12
+ .action(async () => {
13
+ const spin = spinner('Compressing project');
14
+ const targetDirectory = process.cwd();
15
+ const targetFilename = 'service.zip';
16
+ const targetFilePath = path.join(targetDirectory, '/', targetFilename);
17
+ if (fs.existsSync(targetFilePath)) {
18
+ fs.unlinkSync(targetFilePath);
19
+ }
20
+ await zipProjectFolder('./', targetDirectory, targetFilename);
21
+ spin.succeed('Project compressed');
22
+ stdout(`\nFile created at: ${targetFilePath}`);
23
+ });
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const datapoolUploadCommand: Command;
@@ -0,0 +1,184 @@
1
+ import { input, select } from '@inquirer/prompts';
2
+ import { Command } from 'commander';
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { spinner, stdout } from '../lib/output.js';
6
+ import { requireAuth } from '../model/command.js';
7
+ import { HubPlatformClient, HubPlatformError } from '@quantum-hub/qhub-api/platform';
8
+ function extractError(err) {
9
+ const bodyObj = err.body;
10
+ if (bodyObj.errorMessage)
11
+ return bodyObj.errorMessage;
12
+ if (bodyObj.detail)
13
+ return bodyObj.detail;
14
+ if (bodyObj.message)
15
+ return bodyObj.message;
16
+ return JSON.stringify(err.body);
17
+ }
18
+ function printHttpSpecificError(datapoolId, statusCode, errorMessage) {
19
+ if (statusCode === 404)
20
+ throw new Error(`Data pool not found: ${datapoolId}`);
21
+ if (statusCode === 403)
22
+ throw new Error(`Access denied. You don't have permission to upload to data pool: ${datapoolId}`);
23
+ if (statusCode === 400)
24
+ throw new Error(`Bad request. Please check the data pool ID and file format. Details: ${errorMessage}`);
25
+ if (statusCode === 401)
26
+ throw new Error(`Authentication failed. Please login again using 'qhubctl login'`);
27
+ if (statusCode === 413)
28
+ throw new Error(`File too large. The file exceeds the maximum allowed size for data pool uploads`);
29
+ if (statusCode === 415)
30
+ throw new Error(`Unsupported file type. Please check if the file format is supported`);
31
+ throw new Error(`Failed to upload file (HTTP ${statusCode}): ${errorMessage}`);
32
+ }
33
+ function handleUploadError(err, datapoolId) {
34
+ if (err instanceof HubPlatformError) {
35
+ const statusCode = err.statusCode ?? 500;
36
+ let errorMessage = 'Unknown API error occurred';
37
+ if (err.body) {
38
+ if (typeof err.body === 'string')
39
+ errorMessage = err.body;
40
+ else if (typeof err.body === 'object')
41
+ errorMessage = extractError(err);
42
+ else if (err.message)
43
+ errorMessage = err.message;
44
+ }
45
+ else if (err.message) {
46
+ errorMessage = err.message;
47
+ }
48
+ printHttpSpecificError(datapoolId || 'unknown', statusCode, errorMessage);
49
+ }
50
+ const msg = err instanceof Error ? err.message : String(err);
51
+ throw new Error(`Unexpected Error: ${msg}`);
52
+ }
53
+ async function promptForDatapoolAction(client) {
54
+ try {
55
+ const dataPools = await client.dataPools.getDataPools();
56
+ if (dataPools.length === 0) {
57
+ stdout('No data pools found. Creating a new one...');
58
+ return 'create';
59
+ }
60
+ return await select({
61
+ message: 'What would you like to do?',
62
+ choices: [
63
+ { name: 'Create a new data pool', value: 'create' },
64
+ { name: 'Select an existing data pool', value: 'select' },
65
+ ],
66
+ default: 'create',
67
+ });
68
+ }
69
+ catch {
70
+ return 'create';
71
+ }
72
+ }
73
+ async function createDatapool(client, suggestedName) {
74
+ try {
75
+ const name = await input({
76
+ message: 'Enter a name for the new data pool:',
77
+ default: `Data Pool for ${suggestedName}`,
78
+ });
79
+ const datapool = await client.dataPools.createDataPool({ name });
80
+ stdout(`\u2713 Created new data pool: ${datapool.name} (${datapool.id})`);
81
+ return datapool.id;
82
+ }
83
+ catch (err) {
84
+ if (err instanceof HubPlatformError) {
85
+ const errorMessage = err.body || err.message || 'Unknown API error occurred';
86
+ throw new Error(`Failed to create data pool (HTTP ${err.statusCode}): ${JSON.stringify(errorMessage)}`);
87
+ }
88
+ const msg = err instanceof Error ? err.message : String(err);
89
+ throw new Error(`Failed to create data pool: ${msg}`);
90
+ }
91
+ }
92
+ async function selectExistingDatapool(client) {
93
+ try {
94
+ const dataPools = await client.dataPools.getDataPools();
95
+ if (dataPools.length === 0) {
96
+ stdout('No existing data pools found. Creating a new one...');
97
+ return createDatapool(client, 'upload');
98
+ }
99
+ stdout('\nAvailable data pools:');
100
+ dataPools.forEach((pool, index) => {
101
+ stdout(`${index + 1}. ${pool.name} (${pool.id})`);
102
+ });
103
+ const selection = await input({
104
+ message: `Select a data pool (1-${dataPools.length}) or enter the ID directly:`,
105
+ });
106
+ const index = parseInt(selection, 10);
107
+ if (!isNaN(index) && index >= 1 && index <= dataPools.length) {
108
+ return dataPools[index - 1].id;
109
+ }
110
+ return selection;
111
+ }
112
+ catch {
113
+ throw new Error('Failed to retrieve data pools');
114
+ }
115
+ }
116
+ async function ensureDatapoolExists(client, datapoolId, filePaths) {
117
+ if (!datapoolId) {
118
+ stdout('No data pool ID provided. You can create a new data pool or select an existing one.');
119
+ const action = await promptForDatapoolAction(client);
120
+ return action === 'create'
121
+ ? createDatapool(client, filePaths[0])
122
+ : selectExistingDatapool(client);
123
+ }
124
+ try {
125
+ await client.dataPools.getDataPool({ id: datapoolId });
126
+ return datapoolId;
127
+ }
128
+ catch (err) {
129
+ if (err instanceof HubPlatformError && err.statusCode === 404) {
130
+ stdout(`Data pool ${datapoolId} not found. Would you like to create a new data pool instead?`);
131
+ const action = await promptForDatapoolAction(client);
132
+ return action === 'create'
133
+ ? createDatapool(client, filePaths[0])
134
+ : selectExistingDatapool(client);
135
+ }
136
+ handleUploadError(err, datapoolId);
137
+ }
138
+ }
139
+ const EXAMPLES = `
140
+ Examples:
141
+ $ qhubctl datapool upload --file ./data/dataset.csv
142
+ $ qhubctl datapool upload --file ./data/dataset.csv --file ./data/metadata.json
143
+ $ qhubctl datapool upload -f ./data/dataset.csv -f ./data/metadata.json --datapool-id 550e8400-e29b-41d4-a716-446655440000`;
144
+ export const datapoolUploadCommand = new Command('upload')
145
+ .description('Upload files to a data pool')
146
+ .requiredOption('-f, --file <value...>', 'Path to a file to upload (can be specified multiple times)')
147
+ .option('-d, --datapool-id <value>', 'ID of the data pool to upload the files to (optional - will prompt to create a new one if not provided)')
148
+ .addHelpText('after', EXAMPLES)
149
+ .action(async (opts) => {
150
+ const userConfig = await requireAuth();
151
+ const baseUrl = userConfig.endpoint?.basePath
152
+ ? `${userConfig.endpoint.basePath}/qc-catalog`
153
+ : undefined;
154
+ const apiKey = userConfig.auth?.value;
155
+ const organizationId = userConfig.context?.isOrganization ? userConfig.context.id : undefined;
156
+ const client = new HubPlatformClient({ baseUrl, apiKey, organizationId });
157
+ const filePaths = opts.file;
158
+ for (const filePath of filePaths) {
159
+ if (!fs.existsSync(filePath))
160
+ throw new Error(`File not found: ${filePath}`);
161
+ try {
162
+ fs.accessSync(filePath, fs.constants.R_OK);
163
+ }
164
+ catch {
165
+ throw new Error(`Cannot read file: ${filePath}`);
166
+ }
167
+ }
168
+ const datapoolId = await ensureDatapoolExists(client, opts.datapoolId, filePaths);
169
+ for (const filePath of filePaths) {
170
+ const fileName = path.basename(filePath);
171
+ const spin = spinner(`Uploading ${fileName} to data pool ${datapoolId}`);
172
+ try {
173
+ const fileStream = fs.createReadStream(filePath);
174
+ const result = await client.dataPools.addDataPoolFile({ id: datapoolId, file: fileStream });
175
+ spin.succeed('\u2713');
176
+ stdout(`Successfully uploaded ${fileName} to data pool ${datapoolId}`);
177
+ stdout(`File ID: ${result.id}`);
178
+ }
179
+ catch (err) {
180
+ spin.fail('\u2717');
181
+ handleUploadError(err, datapoolId);
182
+ }
183
+ }
184
+ });
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const getContextCommand: Command;
@@ -0,0 +1,12 @@
1
+ import { Command } from 'commander';
2
+ import { requireAuth } from '../model/command.js';
3
+ const EXAMPLES = `
4
+ Examples:
5
+ $ qhubctl get-context`;
6
+ export const getContextCommand = new Command('get-context')
7
+ .description('Get the current context, i.e., the personal or organization account you are currently working with.')
8
+ .addHelpText('after', EXAMPLES)
9
+ .action(async () => {
10
+ // requireAuth prints "Current context: ..." as a side-effect
11
+ await requireAuth();
12
+ });
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const initCommand: Command;
@@ -0,0 +1,66 @@
1
+ import { Command } from 'commander';
2
+ import fs from 'fs-extra';
3
+ import path from 'node:path';
4
+ import { DEFAULT_STARTER, promptUserInput } from '../lib/init-user-input.js';
5
+ import { randomName } from '../lib/random-name.js';
6
+ import { downloadRepository, extractStarter } from '../lib/repository.js';
7
+ import { error, stdout } from '../lib/output.js';
8
+ import { Runtime } from '../model/managed-service-config.js';
9
+ import { writeServiceConfig } from '../service/service-config-service.js';
10
+ const EXAMPLES = `
11
+ Examples:
12
+ $ qhubctl init`;
13
+ export const initCommand = new Command('init')
14
+ .description('Bootstrap project to create a service.')
15
+ .option('--name <value>', 'The name of the service')
16
+ .option('--non-interactive', 'Run it in non-interactive mode', false)
17
+ .addHelpText('after', EXAMPLES)
18
+ .action(async (opts) => {
19
+ const nonInteractive = opts.nonInteractive;
20
+ let name = opts.name ?? randomName();
21
+ const input = nonInteractive ? {
22
+ cpu: 1,
23
+ descriptionFile: 'README.md',
24
+ gpu: undefined,
25
+ memory: 1,
26
+ name: name,
27
+ starter: DEFAULT_STARTER,
28
+ } : await promptUserInput(name);
29
+ name = input.name || name;
30
+ const targetDirectory = input.starter ? path.join(process.cwd(), name) : path.join(process.cwd());
31
+ if (input.starter && fs.existsSync(targetDirectory)) {
32
+ error(`Destination ${targetDirectory} already exists. Please choose another name.`);
33
+ }
34
+ const serviceConfig = {
35
+ name,
36
+ descriptionFile: input.descriptionFile ?? 'README.md',
37
+ resources: {
38
+ cpu: input.cpu,
39
+ memory: input.memory,
40
+ },
41
+ runtime: input.starter ? input.starter.runtime : Runtime.PYTHON_TEMPLATE,
42
+ };
43
+ if (input.gpu) {
44
+ serviceConfig.resources.gpu = {
45
+ count: 1,
46
+ type: input.gpu,
47
+ };
48
+ }
49
+ if (input.starter) {
50
+ fs.mkdirSync(targetDirectory);
51
+ }
52
+ writeServiceConfig(targetDirectory, serviceConfig);
53
+ if (input.starter) {
54
+ const zip = await downloadRepository(input.starter.url);
55
+ const starterName = path.basename(input.starter.url, '.zip');
56
+ extractStarter(zip, starterName, targetDirectory);
57
+ }
58
+ stdout('\u{1F389} Initialized project. Happy hacking!');
59
+ if (input.starter) {
60
+ stdout('\n Next steps:');
61
+ stdout(` \u{2022} cd ${name}`);
62
+ stdout(' \u{2022} qhubctl up (deploys your code as a service to Kipu Quantum Hub)');
63
+ stdout(' \u{2022} qhubctl run (executes your service using the data from the input directory)');
64
+ }
65
+ stdout('');
66
+ });
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const listContextsCommand: Command;
@@ -0,0 +1,28 @@
1
+ import { Command } from 'commander';
2
+ import Table from 'tty-table';
3
+ import { spinner, stdout } from '../lib/output.js';
4
+ import { requireAuth } from '../model/command.js';
5
+ import QuantumHubService from '../service/quantum-hub-service.js';
6
+ const EXAMPLES = `
7
+ Examples:
8
+ $ qhubctl list-contexts`;
9
+ export const listContextsCommand = new Command('list-contexts')
10
+ .description('Retrieves the available contexts, i.e., the personal or organizational accounts available to you to work with.')
11
+ .addHelpText('after', EXAMPLES)
12
+ .action(async () => {
13
+ const userConfig = await requireAuth();
14
+ const quantumHubService = new QuantumHubService(userConfig);
15
+ const spin = spinner('Loading available contexts');
16
+ const accounts = await quantumHubService.getAccounts();
17
+ spin.succeed('Contexts loaded');
18
+ const tableData = accounts
19
+ .map(account => ({ displayName: account.displayName, id: account.id, isOrganization: account.isOrganization }));
20
+ const header = [
21
+ { value: "displayName", alias: "Name", align: "left" },
22
+ { value: "id", alias: "ID", align: "left" },
23
+ { value: "isOrganization", alias: "Account", width: 14, formatter: (value) => value ? "Organization" : "Personal" },
24
+ ];
25
+ const t = Table(header, tableData);
26
+ stdout(t.render());
27
+ stdout();
28
+ });
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const loginCommand: Command;
@@ -0,0 +1,59 @@
1
+ import { password } from '@inquirer/prompts';
2
+ import { Command } from 'commander';
3
+ import { spinner } from '../lib/output.js';
4
+ import { configDir, getUserConfig, showCustomBasePathWarning } from '../model/command.js';
5
+ import { AuthType, defaultBasePath } from '../model/user-config.js';
6
+ import AuthService from '../service/auth-service.js';
7
+ import { writeUserConfig } from '../service/user-config-service.js';
8
+ const EXAMPLES = `
9
+ Examples:
10
+ $ qhubctl login -t <personal access token>
11
+ $ qhubctl login -t <personal access token> --base-path ${defaultBasePath}`;
12
+ export const loginCommand = new Command('login')
13
+ .description('Login with your credentials')
14
+ .option('-t, --token <value>', 'Your personal access token')
15
+ .option('--base-path <value>', 'Custom base path for authentication (development)')
16
+ .addHelpText('after', EXAMPLES)
17
+ .action(async (opts) => {
18
+ let { token } = opts;
19
+ const { basePath } = opts;
20
+ if (!token) {
21
+ token = await password({ message: 'Enter or paste your personal access token?', mask: true });
22
+ }
23
+ if (!token) {
24
+ throw new Error('Invalid credentials. Please try again.');
25
+ }
26
+ if (basePath) {
27
+ showCustomBasePathWarning();
28
+ }
29
+ const spin = spinner('Login...');
30
+ const userConfig = getUserConfig();
31
+ const tempUserConfig = basePath ? {
32
+ ...userConfig,
33
+ endpoint: {
34
+ basePath,
35
+ defaultHeaders: userConfig.endpoint?.defaultHeaders || {}
36
+ }
37
+ } : userConfig;
38
+ const user = await new AuthService(tempUserConfig).authenticate(token);
39
+ const newUserConfig = {
40
+ ...userConfig,
41
+ auth: {
42
+ type: AuthType.API_KEY,
43
+ value: token,
44
+ },
45
+ context: {
46
+ id: user.id,
47
+ displayName: `${user.firstname} ${user.lastname}`,
48
+ isOrganization: false,
49
+ },
50
+ ...(basePath && {
51
+ endpoint: {
52
+ basePath,
53
+ defaultHeaders: userConfig.endpoint?.defaultHeaders || {}
54
+ }
55
+ })
56
+ };
57
+ writeUserConfig(configDir, newUserConfig);
58
+ spin.succeed('Login successful');
59
+ });
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const logoutCommand: Command;
@@ -0,0 +1,20 @@
1
+ import { Command } from 'commander';
2
+ import { spinner } from '../lib/output.js';
3
+ import { configDir } from '../model/command.js';
4
+ import { writeUserConfig } from '../service/user-config-service.js';
5
+ import { getUserConfig } from '../model/command.js';
6
+ const EXAMPLES = `
7
+ Examples:
8
+ $ qhubctl logout`;
9
+ export const logoutCommand = new Command('logout')
10
+ .description('Logout')
11
+ .addHelpText('after', EXAMPLES)
12
+ .action(async () => {
13
+ const spin = spinner('Logout');
14
+ writeUserConfig(configDir, {
15
+ ...getUserConfig(),
16
+ auth: undefined,
17
+ context: undefined,
18
+ });
19
+ spin.succeed('Logout successful');
20
+ });
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const openapiCommand: Command;