@ptkl/toolkit 0.6.1 → 0.7.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/README.md ADDED
@@ -0,0 +1,139 @@
1
+ # Protokol Toolkit
2
+
3
+ A command-line toolkit for managing Protokol platform applications, profiles, functions, and components.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g @ptkl/toolkit
9
+ ```
10
+
11
+ ## Getting Started
12
+
13
+ ### 1. Initialize the Toolkit
14
+
15
+ Before using the toolkit, initialize it to create the configuration directory:
16
+
17
+ ```bash
18
+ ptkl init
19
+ ```
20
+
21
+ This creates a `~/.ptkl` directory where profiles and settings are stored.
22
+
23
+ ### 2. Create a Profile
24
+
25
+ A profile stores your authentication credentials and connection settings for a Protokol instance.
26
+
27
+ #### Secure Method (Recommended)
28
+
29
+ Omit the password or use `--password` flag without a value to be prompted securely:
30
+
31
+ ```bash
32
+ ptkl profile new \
33
+ -n production \
34
+ -u user@example.com \
35
+ -P my-project \
36
+ -h https://api.example.com \
37
+ --password
38
+ ```
39
+
40
+ You'll be prompted to enter your password securely (hidden input).
41
+
42
+ #### With Password in CLI (Not Recommended)
43
+
44
+ ```bash
45
+ ptkl profile new \
46
+ -n production \
47
+ -u user@example.com \
48
+ -P my-project \
49
+ -h https://api.example.com \
50
+ --password "myPassword"
51
+ ```
52
+
53
+ ⚠️ **Security Warning**: This will show a warning as the password may be visible in shell history.
54
+
55
+ #### Options
56
+
57
+ - `-n, --name <name>` - Profile name (e.g., "production", "staging")
58
+ - `-u, --username <username>` - Email or API username
59
+ - `-P, --project <project>` - Project identifier
60
+ - `-h, --host <host>` - API host URL
61
+ - `-p, --password [password]` - Password (optional value for secure prompt)
62
+
63
+ ## Profile Management
64
+
65
+ ### List All Profiles
66
+
67
+ ```bash
68
+ ptkl profile list
69
+ ```
70
+
71
+ Shows all available profiles. The currently active profile is marked with `*`.
72
+
73
+ ### View Current Profile
74
+
75
+ ```bash
76
+ ptkl profile
77
+ ```
78
+
79
+ ### Switch to a Different Profile
80
+
81
+ ```bash
82
+ ptkl profile use staging
83
+ ```
84
+
85
+ Sets `staging` as the active profile for all subsequent commands.
86
+
87
+ ### Inspect Profile Details
88
+
89
+ ```bash
90
+ ptkl profile inspect production
91
+ ```
92
+
93
+ ### Re-authenticate a Profile
94
+
95
+ If your token expires or you need to update credentials:
96
+
97
+ ```bash
98
+ ptkl profile auth --password
99
+ ```
100
+
101
+ Or for a specific profile:
102
+
103
+ ```bash
104
+ ptkl profile auth --profile production --password
105
+ ```
106
+
107
+ #### Options
108
+
109
+ - `-p, --password [password]` - Password (omit value for secure prompt)
110
+ - `-t, --token <token>` - Directly provide a token
111
+ - `-P, --project <project>` - Update the project
112
+
113
+ ### Delete a Profile
114
+
115
+ ```bash
116
+ ptkl profile delete staging
117
+ ```
118
+
119
+ ## Using the --profile Flag
120
+
121
+ You can override the active profile for any command using the global `--profile` flag:
122
+
123
+ ```bash
124
+ ptkl --profile production apps upload -a app -d ./my-app
125
+ ```
126
+
127
+ ## Version
128
+
129
+ Current version: 0.7.0
130
+
131
+ ## License
132
+
133
+ GNU General Public License v3.0 (GPL-3.0)
134
+
135
+ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
136
+
137
+ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
138
+
139
+ Any modifications or derivative works must also be licensed under GPL-3.0 and the source code must be made available.
@@ -2,8 +2,27 @@
2
2
  import { Command } from "commander";
3
3
  import { commands } from "../commands/index.js";
4
4
  const program = new Command();
5
+ // Add global --profile option
6
+ program.option('--profile <profileName>', 'Use a specific profile for this command');
7
+ // Add a preAction hook to handle the global --profile flag
8
+ program.hook('preAction', (thisCommand, actionCommand) => {
9
+ const opts = thisCommand.opts();
10
+ if (opts.profile) {
11
+ // Store the profile name in a global variable or process.env so commands can access it
12
+ process.env.PTKL_ACTIVE_PROFILE = opts.profile;
13
+ }
14
+ });
5
15
  commands.forEach(c => program.addCommand(c));
6
- program.parseAsync(process.argv);
16
+ program.parseAsync(process.argv).catch((err) => {
17
+ const { response } = err;
18
+ if (response && response.data) {
19
+ console.error(response.data.message);
20
+ }
21
+ else {
22
+ console.error(err.message || err);
23
+ }
24
+ process.exit(1);
25
+ });
7
26
  process.on('uncaughtException', (err) => {
8
27
  const { response } = err;
9
28
  if (response && response.data) {
@@ -78,10 +78,10 @@ class AppsCommand {
78
78
  const profile = util.getCurrentProfile();
79
79
  const client = new Api({ token: profile.token, host: profile.host }).app(apptype);
80
80
  try {
81
- let buffer = Buffer.alloc(0);
81
+ const chunks = [];
82
82
  const bufferStream = new Writable({
83
83
  write(chunk, encoding, callback) {
84
- buffer = Buffer.concat([buffer, chunk]);
84
+ chunks.push(Buffer.from(chunk));
85
85
  callback();
86
86
  },
87
87
  });
@@ -93,14 +93,35 @@ class AppsCommand {
93
93
  .on('error', reject);
94
94
  });
95
95
  console.log('Archive created successfully');
96
- // Create FormData with buffer
96
+ // Convert chunks to a single ArrayBuffer to avoid Node Buffer issues
97
+ const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
98
+ const arrayBuffer = new ArrayBuffer(totalLength);
99
+ const uint8View = new Uint8Array(arrayBuffer);
100
+ let offset = 0;
101
+ for (const chunk of chunks) {
102
+ uint8View.set(new Uint8Array(chunk), offset);
103
+ offset += chunk.length;
104
+ }
105
+ console.log('ArrayBuffer size:', arrayBuffer.byteLength);
106
+ // Create FormData with ArrayBuffer
97
107
  const formData = new FormData();
98
- const blob = new Blob([buffer], { type: 'application/gzip' });
108
+ const blob = new Blob([arrayBuffer], { type: 'application/gzip' });
109
+ console.log('Blob size:', blob.size);
99
110
  formData.append('app', blob, 'app.tar.gz');
100
111
  if (build) {
101
112
  formData.append('build', 'true');
102
113
  }
103
- return await client.upload(formData);
114
+ console.log('About to call client.upload...');
115
+ try {
116
+ const result = await client.upload(formData);
117
+ console.log('Upload successful');
118
+ return result;
119
+ }
120
+ catch (error) {
121
+ console.log('Upload failed with error:', error.message);
122
+ console.log('Error details:', error);
123
+ throw error;
124
+ }
104
125
  }
105
126
  catch (error) {
106
127
  throw error;
@@ -164,14 +185,15 @@ class AppsCommand {
164
185
  const manifestPath = `${distPath}/manifest.json`;
165
186
  writeFileSync(manifestPath, JSON.stringify(manifest, null, 4));
166
187
  if (upload) {
167
- let buffer = Buffer.alloc(0);
188
+ const chunks = [];
168
189
  const bufferStream = new Writable({
169
190
  write(chunk, encoding, callback) {
170
- buffer = Buffer.concat([buffer, chunk]);
191
+ chunks.push(chunk);
171
192
  callback();
172
193
  },
173
194
  });
174
195
  c({ gzip: true, cwd: distPath }, ['.']).pipe(bufferStream).on('finish', () => {
196
+ const buffer = Buffer.concat(chunks);
175
197
  client.app().bundleUpload(buffer).then(() => {
176
198
  console.log('Bundle uploaded successfully');
177
199
  })
@@ -2,6 +2,7 @@ import { Command } from "commander";
2
2
  import util from "../lib/util.js";
3
3
  import cli from "../lib/cli.js";
4
4
  import { APIUser, User } from "@ptkl/sdk";
5
+ import password from '@inquirer/password';
5
6
  class ProfileCommand {
6
7
  register() {
7
8
  return new Command('profile')
@@ -12,14 +13,15 @@ class ProfileCommand {
12
13
  .description('Create new profile')
13
14
  .option("-n, --name <name>")
14
15
  .option("-u, --username <username>")
15
- .option("-p, --password <password>")
16
+ .option("-p, --password [password]", "Password (optional value - if omitted, you will be prompted securely)")
16
17
  .option("-P, --project <project>")
17
18
  .option("-h, --host <host>")
18
19
  .action(this.new))
19
20
  .addCommand(new Command('auth')
20
21
  .description('Profile auth')
21
- .option("-p, --password <password>")
22
+ .option("-p, --password [password]", "Password (optional value - if omitted, you will be prompted securely)")
22
23
  .option("-t, --token <token>")
24
+ .option("-P, --project <project>")
23
25
  .action(this.auth))
24
26
  .addCommand(new Command('list')
25
27
  .description('List all available profiles')
@@ -43,23 +45,39 @@ class ProfileCommand {
43
45
  }
44
46
  async new(options) {
45
47
  // try to find profile with the name
46
- const { name, username, password, project, host } = options;
48
+ const { name, username, project, host } = options;
49
+ let userPassword = options.password;
50
+ // Handle password input
51
+ if (typeof userPassword === 'string' && userPassword.length > 0) {
52
+ // Password was provided as CLI argument - show security warning
53
+ console.warn('⚠️ WARNING: Providing password via CLI argument is insecure and may be visible in shell history.');
54
+ console.warn('⚠️ Consider using --password flag without a value to be prompted securely.\n');
55
+ }
56
+ else {
57
+ // Prompt for password securely (either --password flag was used without value, or not provided at all)
58
+ userPassword = await password({
59
+ message: 'Enter password:',
60
+ });
61
+ }
47
62
  let token = "";
48
63
  // if username is email then login to platform as user
49
64
  if (username && username.includes('@')) {
50
65
  const user = new User({
51
- username,
52
- password,
53
66
  host,
54
67
  });
55
- const { data } = await user.auth(username, password, project);
56
- token = "SESSION:" + data.token;
68
+ try {
69
+ const { data } = await user.auth(username, userPassword, project);
70
+ token = "SESSION:" + data.token;
71
+ }
72
+ catch (error) {
73
+ throw error;
74
+ }
57
75
  }
58
76
  else {
59
77
  const user = new APIUser({
60
78
  host,
61
79
  });
62
- const { data } = await user.auth(username, password, project);
80
+ const { data } = await user.auth(username, userPassword, project);
63
81
  token = data.Token;
64
82
  }
65
83
  const profiles = util.getProfiles() ?? [];
@@ -72,25 +90,51 @@ class ProfileCommand {
72
90
  name,
73
91
  username,
74
92
  token,
75
- host
93
+ host,
94
+ project,
76
95
  });
77
96
  util.updateProfiles(profiles);
78
97
  }
79
98
  async auth(options) {
80
99
  // try to find profile with the name
81
- const { password, token } = options;
100
+ let { token, project } = options;
101
+ let userPassword = options.password;
82
102
  const profile = util.getCurrentProfile();
83
103
  if (!token) {
84
- const user = new APIUser({
85
- host: profile.host,
86
- });
87
- // authenticate to protokol
88
- const { data } = await user.auth(profile.username, password, profile.project);
89
- profile.token = data.Token;
90
- }
91
- else {
92
- profile.token = token;
104
+ // Handle password input
105
+ if (typeof userPassword === 'string' && userPassword.length > 0) {
106
+ // Password was provided as CLI argument - show security warning
107
+ console.warn('⚠️ WARNING: Providing password via CLI argument is insecure and may be visible in shell history.');
108
+ console.warn('⚠️ Consider using --password flag without a value to be prompted securely.\n');
109
+ }
110
+ else {
111
+ // Prompt for password securely
112
+ userPassword = await password({
113
+ message: 'Enter password:',
114
+ });
115
+ }
116
+ if (profile.username && profile.username.includes('@')) {
117
+ const user = new User({
118
+ host: profile.host,
119
+ });
120
+ try {
121
+ const { data } = await user.auth(profile.username, userPassword, project ?? profile.project);
122
+ token = "SESSION:" + data.token;
123
+ }
124
+ catch (error) {
125
+ throw error;
126
+ }
127
+ }
128
+ else {
129
+ const user = new APIUser({
130
+ host: profile.host,
131
+ });
132
+ const { data } = await user.auth(profile.username, userPassword, project ?? profile.project);
133
+ token = data.Token;
134
+ }
93
135
  }
136
+ profile.project = project ?? profile.project;
137
+ profile.token = token;
94
138
  util.updateProfile(profile);
95
139
  }
96
140
  list() {
package/dist/lib/util.js CHANGED
@@ -48,9 +48,22 @@ export default class Util {
48
48
  }
49
49
  static getCurrentProfile() {
50
50
  const profiles = Util.getProfiles();
51
- const profile = profiles?.find(p => p.current);
52
- if (!profile) {
53
- throw new Error("Current profile is not set. Run command `profile use {profile-name}`");
51
+ // Check if --profile flag was used globally
52
+ const profileNameFromFlag = process.env.PTKL_ACTIVE_PROFILE;
53
+ let profile;
54
+ if (profileNameFromFlag) {
55
+ // Use the profile specified via --profile flag
56
+ profile = profiles?.find(p => p.name === profileNameFromFlag);
57
+ if (!profile) {
58
+ throw new Error(`Profile '${profileNameFromFlag}' not found. Available profiles: ${profiles?.map(p => p.name).join(', ')}`);
59
+ }
60
+ }
61
+ else {
62
+ // Fall back to current profile
63
+ profile = profiles?.find(p => p.current);
64
+ if (!profile) {
65
+ throw new Error("Current profile is not set. Run command `profile use {profile-name}` or use --profile flag");
66
+ }
54
67
  }
55
68
  return profile;
56
69
  }
package/package.json CHANGED
@@ -1,19 +1,36 @@
1
1
  {
2
2
  "name": "@ptkl/toolkit",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
4
+ "description": "A command-line toolkit for managing Protokol platform applications, profiles, functions, and components",
5
+ "keywords": [
6
+ "protokol",
7
+ "cli",
8
+ "toolkit",
9
+ "deployment",
10
+ "serverless",
11
+ "functions"
12
+ ],
13
+ "author": "Protokol",
14
+ "license": "GPL-3.0",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://your-gitea-instance.com/your-org/protokol-toolkit.git"
18
+ },
19
+ "bugs": {
20
+ "url": "https://your-gitea-instance.com/your-org/protokol-toolkit/issues"
21
+ },
22
+ "homepage": "https://your-gitea-instance.com/your-org/protokol-toolkit#readme",
23
+ "type": "module",
4
24
  "scripts": {
5
25
  "test": "echo \"Error: no test specified\" && exit 1",
6
26
  "build": "npx tsc",
7
- "toolkit": "tsc --build && node dist/bin/toolkit"
27
+ "toolkit": "tsc --build && node dist/bin/toolkit",
28
+ "prepublishOnly": "npm run build"
8
29
  },
9
- "keywords": [],
10
- "author": "",
11
- "license": "MIT",
12
- "description": "",
13
- "type": "module",
14
30
  "dependencies": {
15
31
  "@babel/standalone": "^7.26.10",
16
- "@ptkl/sdk": "file:../protokol-sdk",
32
+ "@inquirer/password": "^4.0.21",
33
+ "@ptkl/sdk": "^0.9.12",
17
34
  "@types/axios": "^0.14.0",
18
35
  "@types/commander": "^2.12.2",
19
36
  "@types/js-yaml": "^4.0.9",