pterm 0.0.1 → 0.0.6

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,233 @@
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
+ ### syntax
18
+
19
+ ```
20
+ pinokio version <type>
21
+ ```
22
+
23
+ - `type`: may be `terminal`, `pinokiod`, or `pinokio`
24
+ - `terminal`: returns the pterm version
25
+ - `pinokiod`: returns the pinokiod version
26
+ - `pinokio`: returns the pinokio version
27
+
28
+ ### example
29
+
30
+ ```
31
+ pinokio version terminal
32
+ ```
33
+
34
+ ## start
35
+
36
+ Start a pinokio script. Arguments can be passed into the script
37
+
38
+ ### syntax
39
+
40
+ ```
41
+ pinokio start <script_path> [<arg1>, <arg2>, ...]
42
+ ```
43
+
44
+ ### examples
45
+
46
+ Starting a script named `install.js`:
47
+
48
+ ```
49
+ pinokio start install.js
50
+ ```
51
+
52
+ Starting a script named `start.js` with parameters:
53
+
54
+ ```
55
+ pinokio start start.js --port=3000 --model=google/gemma-3n-E4B-it
56
+ ```
57
+
58
+ Above command starts the script `start.js` with the following args:
59
+
60
+ ```
61
+ {
62
+ port: 3000,
63
+ model: "google/gemma-3n-E4B-it"
64
+ }
65
+ ```
66
+
67
+ Which can be accessed in the `start.js` script, for example:
68
+
69
+ ```json
70
+ {
71
+ "daemon": true,
72
+ "run": [{
73
+ "method": "shell.run",
74
+ "params": {
75
+ "env": {
76
+ "PORT": "{{args.port}}"
77
+ },
78
+ "message": "python app.py --checkpoint={{args.model}}"
79
+ }
80
+ }]
81
+ }
82
+ ```
83
+
84
+ ## stop
85
+
86
+ Stops a script if running:
87
+
88
+ ### syntax
89
+
90
+ ```
91
+ pinokio stop <script_path>
92
+ ```
93
+
94
+
95
+ ### example
96
+
97
+ Stop the `start.js` script if it's running:
98
+
99
+ ```
100
+ pinokio stop start.js
101
+ ```
102
+
103
+ ## run
104
+
105
+ Run a launcher. Equivalent to the user visiting a launcher page. Will run whichever script is the current default script.
106
+
107
+ ### syntax
108
+
109
+ ```
110
+ pinokio run <launcher_path>
111
+ ```
112
+
113
+ ### examples
114
+
115
+ Launch the launcher in the current path
116
+
117
+ ```
118
+ pinokio run .
119
+ ```
120
+
121
+ Launch from absolute path
122
+
123
+ ```
124
+ pinokio run /pinokio/api/test
125
+ ```
126
+
127
+ ## filepicker
128
+
129
+ Display a file picker dialog, which lets the user select one or more file or folder paths, powered by tkinter.
130
+
131
+ This API is NOT for uploading the actual files but for submitting file paths.
132
+
133
+ ### syntax
134
+
135
+ ```
136
+ pinokio filepicker [<arg>, <arg>, ...]
137
+ ```
138
+
139
+ Where args can be one of the following:
140
+
141
+ - `<arg>`: script flags
142
+ - `--title`: (optional) file dialog title.
143
+ - `--type`: (optional) which type to select. Either "folder" or "file". If not specified, the value is "file".
144
+ - `--path`: (optional) specify path to open the file dialog from. If not specified, just use the default path.
145
+ - `--multiple`: (optional) whether to allow multiple path selection (`true` or `false`). Default is `false`.
146
+ - `--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`)
147
+
148
+ ### examples
149
+
150
+ #### Select a folder path
151
+
152
+ ```
153
+ pinokio filepicker --type=folder
154
+ ```
155
+
156
+ #### Select a file path
157
+
158
+ The most basic command lets users select a single file:
159
+
160
+ ```
161
+ pinokio filepicker
162
+ ```
163
+
164
+ which is equivalent to:
165
+
166
+ ```
167
+ pinokio filepicker --type=file
168
+ ```
169
+
170
+ #### Select multiple files
171
+
172
+ ```
173
+ pinokio filepicker --multiple
174
+ ```
175
+
176
+ #### Open the filepicker from the current path
177
+
178
+ ```
179
+ pinokio filepicker --path=.
180
+ ```
181
+
182
+ #### Open an image filepicker
183
+
184
+ ```
185
+ pinokio filepicker --filetype='images/*.png,*.jpg,*.jpeg'
186
+ ```
187
+
188
+ #### Open a filepicker with multiple file types
189
+
190
+ ```
191
+ pinokio filepicker --filetype='images/*.png,*.jpg,*.jpeg' --filetype='docs/*.pdf'
192
+ ```
193
+
194
+
195
+ ## push
196
+
197
+ Send a desktop notification
198
+
199
+ ### syntax
200
+
201
+ ```
202
+ pinokio push <message> [<arg>, <arg>, ...]
203
+ ```
204
+
205
+ - `message`: a message to send
206
+ - `<arg>`: script flags
207
+ - `--title`: (optional) push notification title
208
+ - `--subtitle`: (optional) push notification subtitle
209
+ - `--image`: (optional) custom image path (can be both relative and absolute paths)
210
+ - `--sound`: (optional) true|false (default is false)
211
+ - `--wait`: (optional) wait for 5 seconds
212
+ - `--timeout`: (optional) wait for N seconds
213
+
214
+ ### examples
215
+
216
+ #### send a simple notification
217
+
218
+ ```
219
+ pinokio push 'hello world'
220
+ ```
221
+
222
+ #### notification with sound
223
+
224
+ ```
225
+ pinokio push 'this is a notification' --sound
226
+ ```
227
+
228
+ #### notification with an image
229
+
230
+ ```
231
+ pinokio push 'this is an image notification' --image=icon.png
232
+ ```
233
+
package/icon.png ADDED
Binary file
package/index.js ADDED
@@ -0,0 +1,95 @@
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
+ if (argv._.length > 1) {
17
+ let app = argv._[1]
18
+ if (app === "terminal") {
19
+ console.log("pterm@" + require('./package.json').version)
20
+ } else if (app === "pinokiod") {
21
+ try {
22
+ let r = await fetch("http://localhost:42000/pinokio/version").then((res) => {
23
+ return res.json()
24
+ })
25
+ console.log(`pinokiod@${r.pinokiod}`)
26
+ } catch (e) {
27
+ }
28
+ } else if (app === "pinokio") {
29
+ try {
30
+ let r = await fetch("http://localhost:42000/pinokio/version").then((res) => {
31
+ return res.json()
32
+ })
33
+ if (r.pinokio) {
34
+ console.log(`pinokiod@${r.pinokio}`)
35
+ }
36
+ } catch (e) {
37
+ }
38
+ }
39
+ }
40
+ } else if (cmd === "filepicker") {
41
+ await util.filepicker(argv)
42
+ } else if (cmd === "download") {
43
+ await util.download(argv)
44
+ } else if (cmd === "stop") {
45
+ await script.stop(argv)
46
+ } else if (cmd === "start") {
47
+ if (argv._.length > 1) {
48
+ let uri = argv._[1]
49
+ await script.start(uri, true)
50
+ } else {
51
+ console.error("required argument: <uri>")
52
+ }
53
+ } else if (cmd === "run") {
54
+ if (argv._.length > 1) {
55
+ let _uri = argv._[1]
56
+ const uri = path.resolve(process.cwd(), _uri)
57
+ // try downloading first
58
+ if (path.isAbsolute(uri)) {
59
+ } else {
60
+ // url
61
+ await util.download(argv)
62
+ }
63
+ while(true) {
64
+ let default_uri = await script.default_script(uri)
65
+ if (default_uri) {
66
+ if (path.isAbsolute(default_uri)) {
67
+ await new Promise((resolve, reject) => {
68
+ script.start(default_uri, false, (packet) => {
69
+ if (packet.type === "result") {
70
+ resolve()
71
+ }
72
+ })
73
+ })
74
+ if (script.killed) {
75
+ break
76
+ }
77
+ } else {
78
+ // open in browser
79
+ let response = await util.open_url(default_uri)
80
+ console.log({ response })
81
+ break
82
+ }
83
+ } else {
84
+ break
85
+ }
86
+ }
87
+ if (script.killed) {
88
+ process.exit()
89
+ }
90
+ } else {
91
+ console.error("required argument: <uri>")
92
+ }
93
+ }
94
+ }
95
+ })();
package/package.json CHANGED
@@ -1,11 +1,19 @@
1
1
  {
2
2
  "name": "pterm",
3
- "version": "0.0.1",
3
+ "version": "0.0.6",
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