pterm 0.0.1 → 0.0.5

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,220 @@
1
+ # Pinokio Terminal
2
+
3
+ Interact with Pinokio through terminal commands
4
+
5
+ # Install
6
+
7
+ ```
8
+ npm install -g pterm
9
+ ```
10
+
11
+ # Usage
12
+
13
+ ## version
14
+
15
+ prints the current version
16
+
17
+ ```
18
+ pinokio version
19
+ ```
20
+
21
+ ## start
22
+
23
+ Start a pinokio script. Arguments can be passed into the script
24
+
25
+ ### syntax
26
+
27
+ ```
28
+ pinokio start <script_path> [<arg1>, <arg2>, ...]
29
+ ```
30
+
31
+ ### examples
32
+
33
+ Starting a script named `install.js`:
34
+
35
+ ```
36
+ pinokio start install.js
37
+ ```
38
+
39
+ Starting a script named `start.js` with parameters:
40
+
41
+ ```
42
+ pinokio start start.js --port=3000 --model=google/gemma-3n-E4B-it
43
+ ```
44
+
45
+ Above command starts the script `start.js` with the following args:
46
+
47
+ ```
48
+ {
49
+ port: 3000,
50
+ model: "google/gemma-3n-E4B-it"
51
+ }
52
+ ```
53
+
54
+ Which can be accessed in the `start.js` script, for example:
55
+
56
+ ```json
57
+ {
58
+ "daemon": true,
59
+ "run": [{
60
+ "method": "shell.run",
61
+ "params": {
62
+ "env": {
63
+ "PORT": "{{args.port}}"
64
+ },
65
+ "message": "python app.py --checkpoint={{args.model}}"
66
+ }
67
+ }]
68
+ }
69
+ ```
70
+
71
+ ## stop
72
+
73
+ Stops a script if running:
74
+
75
+ ### syntax
76
+
77
+ ```
78
+ pinokio stop <script_path>
79
+ ```
80
+
81
+
82
+ ### example
83
+
84
+ Stop the `start.js` script if it's running:
85
+
86
+ ```
87
+ pinokio stop start.js
88
+ ```
89
+
90
+ ## run
91
+
92
+ Run a launcher. Equivalent to the user visiting a launcher page. Will run whichever script is the current default script.
93
+
94
+ ### syntax
95
+
96
+ ```
97
+ pinokio run <launcher_path>
98
+ ```
99
+
100
+ ### examples
101
+
102
+ Launch the launcher in the current path
103
+
104
+ ```
105
+ pinokio run .
106
+ ```
107
+
108
+ Launch from absolute path
109
+
110
+ ```
111
+ pinokio run /pinokio/api/test
112
+ ```
113
+
114
+ ## filepicker
115
+
116
+ Display a file picker dialog, which lets the user select one or more file or folder paths, powered by tkinter.
117
+
118
+ This API is NOT for uploading the actual files but for submitting file paths.
119
+
120
+ ### syntax
121
+
122
+ ```
123
+ pinokio filepicker [<arg>, <arg>, ...]
124
+ ```
125
+
126
+ Where args can be one of the following:
127
+
128
+ - `<arg>`: script flags
129
+ - `--title`: (optional) file dialog title.
130
+ - `--type`: (optional) which type to select. Either "folder" or "file". If not specified, the value is "file".
131
+ - `--path`: (optional) specify path to open the file dialog from. If not specified, just use the default path.
132
+ - `--multiple`: (optional) whether to allow multiple path selection (`true` or `false`). Default is `false`.
133
+ - `--filetype`: (optional) file types to accept. you can specify multiple `--filetype` flags. The format must follow `type/extension,extension,extension,...` (Example: `--filetype='image/*.png,*.jpg`)
134
+
135
+ ### examples
136
+
137
+ #### Select a folder path
138
+
139
+ ```
140
+ pinokio filepicker --type=folder
141
+ ```
142
+
143
+ #### Select a file path
144
+
145
+ The most basic command lets users select a single file:
146
+
147
+ ```
148
+ pinokio filepicker
149
+ ```
150
+
151
+ which is equivalent to:
152
+
153
+ ```
154
+ pinokio filepicker --type=file
155
+ ```
156
+
157
+ #### Select multiple files
158
+
159
+ ```
160
+ pinokio filepicker --multiple
161
+ ```
162
+
163
+ #### Open the filepicker from the current path
164
+
165
+ ```
166
+ pinokio filepicker --path=.
167
+ ```
168
+
169
+ #### Open an image filepicker
170
+
171
+ ```
172
+ pinokio filepicker --filetype='images/*.png,*.jpg,*.jpeg'
173
+ ```
174
+
175
+ #### Open a filepicker with multiple file types
176
+
177
+ ```
178
+ pinokio filepicker --filetype='images/*.png,*.jpg,*.jpeg' --filetype='docs/*.pdf'
179
+ ```
180
+
181
+
182
+ ## push
183
+
184
+ Send a desktop notification
185
+
186
+ ### syntax
187
+
188
+ ```
189
+ pinokio push <message> [<arg>, <arg>, ...]
190
+ ```
191
+
192
+ - `message`: a message to send
193
+ - `<arg>`: script flags
194
+ - `--title`: (optional) push notification title
195
+ - `--subtitle`: (optional) push notification subtitle
196
+ - `--image`: (optional) custom image path (can be both relative and absolute paths)
197
+ - `--sound`: (optional) true|false (default is false)
198
+ - `--wait`: (optional) wait for 5 seconds
199
+ - `--timeout`: (optional) wait for N seconds
200
+
201
+ ### examples
202
+
203
+ #### send a simple notification
204
+
205
+ ```
206
+ pinokio push 'hello world'
207
+ ```
208
+
209
+ #### notification with sound
210
+
211
+ ```
212
+ pinokio push 'this is a notification' --sound
213
+ ```
214
+
215
+ #### notification with an image
216
+
217
+ ```
218
+ pinokio push 'this is an image notification' --image=icon.png
219
+ ```
220
+
package/icon.png ADDED
Binary file
package/index.js ADDED
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env node
2
+ const path = require('path')
3
+ const yargs = require('yargs/yargs')
4
+ const { hideBin } = require('yargs/helpers')
5
+ const Script = require('./script')
6
+ const Util = require('./util')
7
+ const argv = yargs(hideBin(process.argv)).parse();
8
+ const script = new Script();
9
+ const util = new Util();
10
+ (async () => {
11
+ if (argv._.length > 0) {
12
+ let cmd = argv._[0].toLowerCase()
13
+ if (cmd === "push") {
14
+ let response = await util.push(argv)
15
+ } else if (cmd === "version") {
16
+ console.log("v" + require('./package.json').version)
17
+ } else if (cmd === "filepicker") {
18
+ await util.filepicker(argv)
19
+ } else if (cmd === "download") {
20
+ await util.download(argv)
21
+ } else if (cmd === "stop") {
22
+ await script.stop(argv)
23
+ } else if (cmd === "start") {
24
+ if (argv._.length > 1) {
25
+ let uri = argv._[1]
26
+ await script.start(uri, true)
27
+ } else {
28
+ console.error("required argument: <uri>")
29
+ }
30
+ } else if (cmd === "run") {
31
+ if (argv._.length > 1) {
32
+ let _uri = argv._[1]
33
+ const uri = path.resolve(process.cwd(), _uri)
34
+ // try downloading first
35
+ if (path.isAbsolute(uri)) {
36
+ } else {
37
+ // url
38
+ await util.download(argv)
39
+ }
40
+ while(true) {
41
+ let default_uri = await script.default_script(uri)
42
+ if (default_uri) {
43
+ if (path.isAbsolute(default_uri)) {
44
+ await new Promise((resolve, reject) => {
45
+ script.start(default_uri, false, (packet) => {
46
+ if (packet.type === "result") {
47
+ resolve()
48
+ }
49
+ })
50
+ })
51
+ if (script.killed) {
52
+ break
53
+ }
54
+ } else {
55
+ // open in browser
56
+ let response = await util.open_url(default_uri)
57
+ console.log({ response })
58
+ break
59
+ }
60
+ } else {
61
+ break
62
+ }
63
+ }
64
+ if (script.killed) {
65
+ process.exit()
66
+ }
67
+ } else {
68
+ console.error("required argument: <uri>")
69
+ }
70
+ }
71
+ }
72
+ })();
package/package.json CHANGED
@@ -1,11 +1,19 @@
1
1
  {
2
2
  "name": "pterm",
3
- "version": "0.0.1",
3
+ "version": "0.0.5",
4
4
  "description": "",
5
5
  "main": "index.js",
6
+ "bin": {
7
+ "pinokio": "./index.js"
8
+ },
6
9
  "scripts": {
7
10
  "test": "echo \"Error: no test specified\" && exit 1"
8
11
  },
9
12
  "author": "",
10
- "license": "ISC"
13
+ "license": "ISC",
14
+ "dependencies": {
15
+ "axios": "^1.10.0",
16
+ "unws": "^0.3.0",
17
+ "yargs": "^17.7.2"
18
+ }
11
19
  }
package/rpc.js ADDED
@@ -0,0 +1,71 @@
1
+ const { WebSocket } = require('unws')
2
+ class RPC {
3
+ constructor(url) {
4
+ this.url = url
5
+ }
6
+ async status(rpc) {
7
+ let res = await new Promise((resolve, reject) => {
8
+ this.run({
9
+ status: true,
10
+ ...rpc
11
+ }, (stream) => {
12
+ resolve(stream.data)
13
+ this.ws.close()
14
+ })
15
+ })
16
+ return res
17
+ }
18
+ close() {
19
+ this.ws.close()
20
+ }
21
+ stop(rpc) {
22
+ this.run({
23
+ stop: true,
24
+ ...rpc
25
+ })
26
+ }
27
+ run (rpc, ondata) {
28
+ // use fetch
29
+ /*
30
+ FormData := [
31
+ types_array: ['number', 'boolean']
32
+ ]
33
+ body := {
34
+
35
+ }
36
+ */
37
+ return new Promise((resolve, reject) => {
38
+ if (this.ws) {
39
+ this.ws.send(JSON.stringify(rpc))
40
+ } else {
41
+ this.ws = new WebSocket(this.url)
42
+ this.ws.addEventListener('open', () => {
43
+ this.ws.send(JSON.stringify(rpc))
44
+ });
45
+ this.ws.addEventListener('message', (message) => {
46
+ /******************************************************************************
47
+
48
+
49
+ ******************************************************************************/
50
+ if (ondata) {
51
+ const packet = JSON.parse(message.data);
52
+ ondata(packet)
53
+ }
54
+ });
55
+ this.ws.addEventListener('close', () => {
56
+ // console.log('Disconnected from WebSocket endpoint', { error: this.error, result: this.result });
57
+ delete this.ws
58
+ resolve()
59
+ });
60
+ }
61
+ })
62
+ }
63
+ respond(response) {
64
+ if (this.ws) {
65
+ this.ws.send(JSON.stringify(response))
66
+ } else {
67
+ throw new Error("socket not connected")
68
+ }
69
+ }
70
+ }
71
+ module.exports = RPC
package/script.js ADDED
@@ -0,0 +1,163 @@
1
+ const path = require('path')
2
+ const RPC = require('./rpc')
3
+ class Script {
4
+ listen(onKey) {
5
+ process.stdin.setRawMode(true);
6
+ process.stdin.resume();
7
+ process.stdin.setEncoding('utf8');
8
+
9
+ const handler = (key) => {
10
+ onKey(key);
11
+
12
+ // Optional: handle Ctrl+C (SIGINT)
13
+ if (key === '\u0003') { // Ctrl+C
14
+ cleanup();
15
+ process.exit();
16
+ }
17
+ };
18
+
19
+ const cleanup = () => {
20
+ process.stdin.setRawMode(false);
21
+ process.stdin.pause();
22
+ process.stdin.off('data', handler);
23
+ };
24
+
25
+ process.stdin.on('data', handler);
26
+
27
+ return cleanup; // call this to stop listening
28
+ }
29
+ async default_script (uri) {
30
+ const rpc = new RPC("ws://localhost:42000")
31
+ const stop = () => {
32
+ rpc.run({
33
+ method: "kernel.api.stop",
34
+ params: { uri }
35
+ }, (packet) => {
36
+ })
37
+ }
38
+ process.on("SIGINT", () => {
39
+ stop()
40
+ });
41
+ process.on("SIGTERM", () => {
42
+ stop()
43
+ });
44
+ process.on("beforeExit", (code) => {
45
+ stop()
46
+ });
47
+ process.on("exit", (code) => {
48
+ stop()
49
+ });
50
+ let default_uri = await new Promise((resolve, reject) => {
51
+ rpc.run({ uri, mode: "open" }, (packet) => {
52
+ if (packet.data && packet.data.uri) {
53
+ // start
54
+ //rpc.stop({ uri })
55
+ stop()
56
+ resolve(packet.data.uri)
57
+ }
58
+ })
59
+ })
60
+ return default_uri
61
+ }
62
+ async stop(argv) {
63
+ if (argv._.length > 1) {
64
+ let _uri = argv._[1]
65
+ const uri = path.resolve(process.cwd(), _uri)
66
+ const rpc = new RPC("ws://localhost:42000")
67
+ rpc.run({
68
+ method: "kernel.api.stop",
69
+ params: { uri }
70
+ }, (packet) => {
71
+ process.exit()
72
+ })
73
+ } else {
74
+ console.error("required argument: <uri>")
75
+ }
76
+ }
77
+ async start(_uri, kill, ondata) {
78
+ const cols = process.stdout.columns;
79
+ const rows = process.stdout.rows;
80
+ const rpc = new RPC("ws://localhost:42000")
81
+
82
+ const uri = path.resolve(process.cwd(), _uri)
83
+
84
+ const stop = () => {
85
+ this.killed = true
86
+ rpc.run({
87
+ method: "kernel.api.stop",
88
+ params: { uri },
89
+ }, (packet) => {
90
+ rpc.stop({ uri })
91
+ })
92
+ }
93
+ process.on("SIGINT", () => {
94
+ stop()
95
+ });
96
+ process.on("SIGTERM", () => {
97
+ stop()
98
+ });
99
+ process.on("beforeExit", (code) => {
100
+ stop()
101
+ });
102
+ process.on("exit", (code) => {
103
+ stop()
104
+ });
105
+ this.key_stop = this.listen((key) => {
106
+ if (key.length >= 256) {
107
+ rpc.run({
108
+ id: this.shell_id,
109
+ paste: true,
110
+ key: key
111
+ })
112
+ } else {
113
+ rpc.run({
114
+ id: this.shell_id,
115
+ key: key
116
+ })
117
+ }
118
+ });
119
+ process.stdout.on('resize', () => {
120
+ const cols = process.stdout.columns;
121
+ const rows = process.stdout.rows;
122
+ rpc.run({
123
+ id: this.shell_id,
124
+ resize: {
125
+ cols,
126
+ rows,
127
+ }
128
+ }, (packet) => {
129
+ })
130
+ });
131
+ await rpc.run({
132
+ uri,
133
+ client: {
134
+ cols,
135
+ rows,
136
+ }
137
+ }, (packet) => {
138
+ if (packet.type === "stop") {
139
+ rpc.stop({ uri })
140
+ if (kill) {
141
+ this.key_stop()
142
+ process.exit()
143
+ }
144
+ } else if (packet.type === "disconnect") {
145
+ rpc.close()
146
+ if (kill) {
147
+ this.key_stop()
148
+ process.exit()
149
+ }
150
+ } else if (packet.type === "stream") {
151
+ if (packet.data.id) {
152
+ this.shell_id = packet.data.id
153
+ }
154
+ process.stdout.write(packet.data.raw)
155
+ }
156
+ if (ondata) {
157
+ ondata(packet)
158
+ }
159
+ })
160
+ };
161
+
162
+ }
163
+ module.exports = Script
package/util.js ADDED
@@ -0,0 +1,99 @@
1
+ const os = require('os')
2
+ const axios = require('axios')
3
+ const path = require('path')
4
+ const RPC = require('./rpc')
5
+ class Util {
6
+ async filepicker(argv) {
7
+ const rpc = new RPC("ws://localhost:42000")
8
+ if (argv.path) {
9
+ argv.path = path.resolve(process.cwd(), argv.path)
10
+ }
11
+ await rpc.run({
12
+ method: "kernel.bin.filepicker",
13
+ params: argv
14
+ }, (packet) => {
15
+ if (packet.type === "result") {
16
+ rpc.close()
17
+ if (packet.data.paths.length > 0) {
18
+ for(let p of packet.data.paths) {
19
+ process.stdout.write(p)
20
+ process.stdout.write("\n")
21
+ }
22
+ }
23
+ } else if (packet.type === "stream") {
24
+ // process.stdout.write(packet.data.raw)
25
+ }
26
+ })
27
+ }
28
+ async push(argv) {
29
+ if (argv._ && argv._.length > 1 && !argv.message) {
30
+ argv.message = argv._[1]
31
+ }
32
+ if (argv.image && !path.isAbsolute(argv.image)) {
33
+ argv.image = path.resolve(process.cwd(), argv.image)
34
+ }
35
+ let response = await axios.post("http://localhost:42000/push", argv)
36
+ return response
37
+ }
38
+ async open_url(url) {
39
+ let response = await axios.post("http://localhost:42000/go", { url })
40
+ return response
41
+ }
42
+ async download(argv) {
43
+ if (argv._.length > 1) {
44
+ let uri = argv._[1]
45
+ const rpc = new RPC("ws://localhost:42000")
46
+ await rpc.run({
47
+ method: "kernel.bin.install2",
48
+ params: {}
49
+ }, (packet) => {
50
+ if (packet.type === "result") {
51
+ rpc.close()
52
+ } else if (packet.type === "stream") {
53
+ process.stdout.write(packet.data.raw)
54
+ }
55
+ })
56
+ if (!uri.endsWith(".git")) {
57
+ uri = uri + ".git"
58
+ }
59
+
60
+ let exists;
61
+ await rpc.run({
62
+ method: "kernel.bin.path_exists",
63
+ params: { uri }
64
+ }, (packet) => {
65
+ if (packet.type === "result") {
66
+ exists = packet.data
67
+ rpc.close()
68
+ }
69
+ })
70
+
71
+ if (!exists) {
72
+ let message = `git clone ${uri}`
73
+ let name = new URL(uri).pathname.split("/").pop()
74
+ if (argv._.length > 2) {
75
+ name = argv._[2]
76
+ message = `git clone ${uri} ${name}`
77
+ }
78
+ if (argv.b) {
79
+ let branch = argv.b
80
+ message += ` -b ${branch}`
81
+ }
82
+ await rpc.run({
83
+ method: "shell.run",
84
+ params: { message, path: "~/api" }
85
+ }, (packet) => {
86
+ if (packet.type === "result") {
87
+ rpc.close()
88
+ } else if (packet.type === "stream") {
89
+ process.stdout.write(packet.data.raw)
90
+ }
91
+ })
92
+ }
93
+ process.exit()
94
+ } else {
95
+ console.error("required argument: <uri>")
96
+ }
97
+ }
98
+ }
99
+ module.exports = Util