pinokiod 6.0.28 → 6.0.40
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/kernel/environment.js +49 -17
- package/kernel/shell.js +38 -1
- package/package.json +8 -3
- package/prototype/system/SKILL_PINOKIO.md +134 -0
- package/server/index.js +654 -3179
- package/server/lib/app_logs.js +138 -0
- package/server/lib/app_preferences.js +313 -0
- package/server/lib/app_registry.js +325 -0
- package/server/lib/app_search.js +736 -0
- package/server/lib/terminal_session_helpers.js +2550 -0
- package/server/lib/terminal_session_registry.js +191 -0
- package/server/public/common.js +57 -0
- package/server/routes/apps.js +176 -0
- package/server/socket.js +117 -4
- package/server/views/app.ejs +126 -6
- package/server/views/app_search_test.ejs +185 -0
- package/server/views/index.ejs +142 -6
- package/server/views/settings.ejs +5 -0
- package/server/views/shell.ejs +116 -10
- package/server/views/terminals.ejs +466 -221
package/kernel/environment.js
CHANGED
|
@@ -525,7 +525,35 @@ const init = async (options, kernel) => {
|
|
|
525
525
|
} else {
|
|
526
526
|
root = kernel.homedir
|
|
527
527
|
}
|
|
528
|
-
const
|
|
528
|
+
const writeSkillIfChanged = async (skillPath, content) => {
|
|
529
|
+
let shouldWrite = true
|
|
530
|
+
try {
|
|
531
|
+
const existingSkillContent = await fs.promises.readFile(skillPath, "utf8")
|
|
532
|
+
shouldWrite = existingSkillContent !== content
|
|
533
|
+
} catch (error) {
|
|
534
|
+
if (!(error && error.code === "ENOENT")) {
|
|
535
|
+
throw error
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if (shouldWrite) {
|
|
540
|
+
await fs.promises.writeFile(skillPath, content, "utf8")
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
const syncSkillToAgentRoots = async (skillName, content) => {
|
|
544
|
+
const home = os.homedir()
|
|
545
|
+
const targetDirs = [
|
|
546
|
+
path.resolve(home, ".agents", "skills", skillName),
|
|
547
|
+
path.resolve(home, ".claude", "skills", skillName)
|
|
548
|
+
]
|
|
549
|
+
for (let i = 0; i < targetDirs.length; i++) {
|
|
550
|
+
const skillDir = targetDirs[i]
|
|
551
|
+
const skillPath = path.resolve(skillDir, "SKILL.md")
|
|
552
|
+
await fs.promises.mkdir(skillDir, { recursive: true })
|
|
553
|
+
await writeSkillIfChanged(skillPath, content)
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
const syncGepetoSkillFromAgents = async () => {
|
|
529
557
|
const homeRoot = path.resolve(kernel.homedir)
|
|
530
558
|
if (path.resolve(root) !== homeRoot) {
|
|
531
559
|
return
|
|
@@ -537,12 +565,7 @@ const init = async (options, kernel) => {
|
|
|
537
565
|
return
|
|
538
566
|
}
|
|
539
567
|
|
|
540
|
-
const skillDir = path.resolve(os.homedir(), ".agents", "skills", "pinokio")
|
|
541
|
-
const skillPath = path.resolve(skillDir, "SKILL.md")
|
|
542
|
-
await fs.promises.mkdir(skillDir, { recursive: true })
|
|
543
|
-
|
|
544
568
|
let agentsContent = ""
|
|
545
|
-
let shouldWrite = true
|
|
546
569
|
try {
|
|
547
570
|
agentsContent = await fs.promises.readFile(agentsPath, "utf8")
|
|
548
571
|
} catch (error) {
|
|
@@ -553,25 +576,33 @@ const init = async (options, kernel) => {
|
|
|
553
576
|
}
|
|
554
577
|
const skillFrontmatter = [
|
|
555
578
|
"---",
|
|
556
|
-
"name:
|
|
557
|
-
"description: Guide for building 1-click launchers
|
|
579
|
+
"name: gepeto",
|
|
580
|
+
"description: Guide for building 1-click launchers and building apps with launchers built-in using Pinokio",
|
|
558
581
|
"---"
|
|
559
582
|
].join("\n")
|
|
560
583
|
const desiredSkillContent = `${skillFrontmatter}\n\n${agentsContent}`
|
|
584
|
+
await syncSkillToAgentRoots("gepeto", desiredSkillContent)
|
|
585
|
+
}
|
|
586
|
+
const syncPinokioSkillFromTemplate = async () => {
|
|
587
|
+
const homeRoot = path.resolve(kernel.homedir)
|
|
588
|
+
if (path.resolve(root) !== homeRoot) {
|
|
589
|
+
return
|
|
590
|
+
}
|
|
561
591
|
|
|
592
|
+
const templatePath = path.resolve(__dirname, "../prototype/system/SKILL_PINOKIO.md")
|
|
593
|
+
let templateContent
|
|
562
594
|
try {
|
|
563
|
-
|
|
564
|
-
shouldWrite = existingSkillContent !== desiredSkillContent
|
|
595
|
+
templateContent = await fs.promises.readFile(templatePath, "utf8")
|
|
565
596
|
} catch (error) {
|
|
566
|
-
if (
|
|
567
|
-
|
|
597
|
+
if (error && error.code === "ENOENT") {
|
|
598
|
+
return
|
|
568
599
|
}
|
|
600
|
+
throw error
|
|
569
601
|
}
|
|
570
602
|
|
|
571
|
-
|
|
572
|
-
await fs.promises.writeFile(skillPath, desiredSkillContent, "utf8")
|
|
573
|
-
}
|
|
603
|
+
await syncSkillToAgentRoots("pinokio", templateContent)
|
|
574
604
|
}
|
|
605
|
+
|
|
575
606
|
let current = path.resolve(root, "ENVIRONMENT")
|
|
576
607
|
let exists = await kernel.exists(current)
|
|
577
608
|
if (exists) {
|
|
@@ -663,8 +694,9 @@ const init = async (options, kernel) => {
|
|
|
663
694
|
}
|
|
664
695
|
}
|
|
665
696
|
|
|
666
|
-
// Keep
|
|
667
|
-
await
|
|
697
|
+
// Keep agent skills in sync for the Pinokio home root
|
|
698
|
+
await syncGepetoSkillFromAgents()
|
|
699
|
+
await syncPinokioSkillFromTemplate()
|
|
668
700
|
|
|
669
701
|
const gitDir = path.resolve(root, ".git")
|
|
670
702
|
const gitDirExists = await kernel.exists(gitDir)
|
package/kernel/shell.js
CHANGED
|
@@ -152,7 +152,7 @@ class Shell {
|
|
|
152
152
|
|
|
153
153
|
// if the shell is running from a script file, the params.$parent will include the path to the parent script
|
|
154
154
|
// this means we need to apply app environment as well
|
|
155
|
-
if (params.$parent) {
|
|
155
|
+
if (params.$parent && params.$parent.id) {
|
|
156
156
|
let api_path
|
|
157
157
|
if (params.$parent.cwd) {
|
|
158
158
|
api_path = Util.api_path(params.$parent.cwd, this.kernel)
|
|
@@ -309,8 +309,31 @@ class Shell {
|
|
|
309
309
|
this.vts = new SerializeAddon()
|
|
310
310
|
this.vt.loadAddon(this.vts)
|
|
311
311
|
|
|
312
|
+
const extractTerminalIdFromShellId = (shellId) => {
|
|
313
|
+
if (typeof shellId !== "string" || shellId.length === 0) {
|
|
314
|
+
return ""
|
|
315
|
+
}
|
|
316
|
+
const separatorIndex = shellId.indexOf("?")
|
|
317
|
+
if (separatorIndex < 0) {
|
|
318
|
+
return ""
|
|
319
|
+
}
|
|
320
|
+
const queryString = shellId.slice(separatorIndex + 1).replace(/&/g, "&")
|
|
321
|
+
try {
|
|
322
|
+
const parsed = new URLSearchParams(queryString)
|
|
323
|
+
return typeof parsed.get("terminal_id") === "string" ? parsed.get("terminal_id").trim() : ""
|
|
324
|
+
} catch (error) {
|
|
325
|
+
return ""
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
312
329
|
// 1. id
|
|
313
330
|
this.id = (params.id ? params.id : uuidv4())
|
|
331
|
+
const explicitTerminalId = typeof params.terminal_id === "string" ? params.terminal_id.trim() : ""
|
|
332
|
+
const resolvedTerminalId = explicitTerminalId || extractTerminalIdFromShellId(this.id)
|
|
333
|
+
this.terminal_id = resolvedTerminalId || null
|
|
334
|
+
if (resolvedTerminalId) {
|
|
335
|
+
params.terminal_id = resolvedTerminalId
|
|
336
|
+
}
|
|
314
337
|
|
|
315
338
|
// 2. group id
|
|
316
339
|
this.group = params.group
|
|
@@ -323,6 +346,20 @@ class Shell {
|
|
|
323
346
|
this.ondata({ raw: `\r\n████\r\n██ Starting Shell ${this.id}\r\n` })
|
|
324
347
|
|
|
325
348
|
this.start_time = Date.now()
|
|
349
|
+
const cloneSourceMessage = (value) => {
|
|
350
|
+
if (Array.isArray(value)) {
|
|
351
|
+
return value.slice()
|
|
352
|
+
}
|
|
353
|
+
if (value && typeof value === "object") {
|
|
354
|
+
try {
|
|
355
|
+
return JSON.parse(JSON.stringify(value))
|
|
356
|
+
} catch (error) {
|
|
357
|
+
return value
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
return value
|
|
361
|
+
}
|
|
362
|
+
this.source_message = cloneSourceMessage(params.message)
|
|
326
363
|
this.params = params
|
|
327
364
|
this.EOL = os.EOL
|
|
328
365
|
if (this.params.shell) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pinokiod",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.40",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@homebridge/node-pty-prebuilt-multiarch": "0.12.0-beta.5",
|
|
14
|
+
"@parcel/watcher": "2.5.1",
|
|
14
15
|
"@xterm/headless": "^5.5.0",
|
|
15
16
|
"7zip-min-win-asar-support": "^1.4.4",
|
|
16
17
|
"accepts": "^1.3.8",
|
|
@@ -57,6 +58,7 @@
|
|
|
57
58
|
"lodash": "^4.17.21",
|
|
58
59
|
"marked": "^5.0.1",
|
|
59
60
|
"mime-types": "^2.1.35",
|
|
61
|
+
"minisearch": "^7.2.0",
|
|
60
62
|
"multer": "^1.4.5-lts.1",
|
|
61
63
|
"node-downloader-helper": "^2.1.9",
|
|
62
64
|
"node-gradio-client": "^0.14.6",
|
|
@@ -86,7 +88,10 @@
|
|
|
86
88
|
"xterm-addon-serialize": "^0.9.0",
|
|
87
89
|
"xterm-headless": "^5.1.0",
|
|
88
90
|
"yaml": "^2.3.4",
|
|
89
|
-
"yargs-unparser-custom-flag": "^2.0.0"
|
|
90
|
-
|
|
91
|
+
"yargs-unparser-custom-flag": "^2.0.0"
|
|
92
|
+
},
|
|
93
|
+
"optionalDependencies": {
|
|
94
|
+
"@parcel/watcher-linux-arm64-glibc": "2.5.1",
|
|
95
|
+
"@parcel/watcher-linux-arm64-musl": "2.5.1"
|
|
91
96
|
}
|
|
92
97
|
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Pinokio
|
|
3
|
+
description: Use pterm to discover apps, ensure they are running, and execute API requests with generated-once reusable clients.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Pinokio Runtime Skill (pterm-first)
|
|
7
|
+
|
|
8
|
+
Use this skill for runtime control of Pinokio apps.
|
|
9
|
+
Do not ask users to manually install, launch, or call APIs when `pterm` can do it.
|
|
10
|
+
|
|
11
|
+
## Control Plane
|
|
12
|
+
|
|
13
|
+
Assume `pterm` is preinstalled and up to date.
|
|
14
|
+
Use direct `pterm` commands for control-plane operations:
|
|
15
|
+
|
|
16
|
+
1. `pterm search "<query>"`
|
|
17
|
+
2. `pterm status <app_id>`
|
|
18
|
+
3. `pterm run <app_path>`
|
|
19
|
+
4. `pterm logs <app_id> --tail 200`
|
|
20
|
+
5. `pterm stars` (optional: inspect user-pinned favorites)
|
|
21
|
+
6. `pterm star <app_id>` / `pterm unstar <app_id>` (only when user explicitly asks to change preference)
|
|
22
|
+
|
|
23
|
+
Do not run install/update commands from this skill.
|
|
24
|
+
|
|
25
|
+
Permission handling:
|
|
26
|
+
- If a `pterm` command fails with loopback permission errors (`EPERM`/`EACCES` to `127.0.0.1:42000`), rerun the same command with required escalation/approval.
|
|
27
|
+
- Continue normal flow if rerun succeeds.
|
|
28
|
+
- Fail only if escalation is denied or rerun still cannot reach Pinokio.
|
|
29
|
+
|
|
30
|
+
## Workflow
|
|
31
|
+
|
|
32
|
+
1. Resolve app target.
|
|
33
|
+
- Resolve by `pterm search`.
|
|
34
|
+
- Build one primary query from user intent:
|
|
35
|
+
- explicit app name/vendor if user provided one
|
|
36
|
+
- otherwise 2-4 high-signal capability tokens (example: `tts speech synthesis`)
|
|
37
|
+
- Query hygiene:
|
|
38
|
+
- remove duplicate/filler words (`to`, `for`, `use`, `app`, `tool`, `service`)
|
|
39
|
+
- do not send full sentences
|
|
40
|
+
- Run primary lookup:
|
|
41
|
+
- if query has 3+ terms: `pterm search "<query>" --mode balanced --min-match 2 --limit 8`
|
|
42
|
+
- if query has 1-2 terms: `pterm search "<query>" --mode balanced --min-match 1 --limit 8`
|
|
43
|
+
- If user provided a git URL, extract owner/repo tokens and run `pterm search` with those tokens first.
|
|
44
|
+
- Useful-hit threshold:
|
|
45
|
+
- for 3+ term queries: candidate has `matched_terms_count >= 2` (if available)
|
|
46
|
+
- for 1-2 term queries: candidate has `matched_terms_count >= 1` or clear top score
|
|
47
|
+
- If no useful hits, run one fallback:
|
|
48
|
+
- `pterm search "<query>" --mode broad --limit 8`
|
|
49
|
+
- Deterministic ranking:
|
|
50
|
+
- exact `app_id`/title match (for explicit app requests)
|
|
51
|
+
- `starred=true` (user preference)
|
|
52
|
+
- `ready=true`
|
|
53
|
+
- higher `matched_terms_count` (if available)
|
|
54
|
+
- higher `launch_count_total` (if available)
|
|
55
|
+
- more recent `last_launch_at` (if available)
|
|
56
|
+
- higher `score`
|
|
57
|
+
- `running=true`
|
|
58
|
+
- If the top candidate is not clearly better than alternatives, ask user once with top 3 candidates.
|
|
59
|
+
|
|
60
|
+
2. Check runtime state with `pterm status`.
|
|
61
|
+
- Poll every 2s.
|
|
62
|
+
- Use status fields from pterm output:
|
|
63
|
+
- `path`: absolute app path to use with `pterm run`
|
|
64
|
+
- `running`: script is running
|
|
65
|
+
- `ready`: app is reachable/ready
|
|
66
|
+
- `ready_url`: base URL for API calls when available
|
|
67
|
+
- `state`: `offline | starting | online`
|
|
68
|
+
- Use `--probe` only for readiness confirmation before first API call (or when status is uncertain).
|
|
69
|
+
- Use `--timeout=<ms>` only when you need a non-default probe timeout.
|
|
70
|
+
- Treat `offline` as expected before first run.
|
|
71
|
+
|
|
72
|
+
3. If app is offline or not ready, run it.
|
|
73
|
+
- Run `pterm run <app_path>`.
|
|
74
|
+
- Continue polling with `pterm status <app_id>`.
|
|
75
|
+
- Default startup timeout: 180s.
|
|
76
|
+
- Do not keep searching indefinitely once an app is selected; start it.
|
|
77
|
+
|
|
78
|
+
4. Success criteria.
|
|
79
|
+
- `state=online` and `ready=true`.
|
|
80
|
+
- If `ready_url` exists, use it as API base URL.
|
|
81
|
+
|
|
82
|
+
5. Failure criteria.
|
|
83
|
+
- Timeout before success.
|
|
84
|
+
- App drops back to `offline` during startup after a run attempt.
|
|
85
|
+
- `pterm run` terminates and status never reaches ready.
|
|
86
|
+
- On failure, fetch `pterm logs <app_id> --tail 200` and return:
|
|
87
|
+
- raw log tail
|
|
88
|
+
- short diagnosis
|
|
89
|
+
|
|
90
|
+
6. API call strategy (generated once, reused).
|
|
91
|
+
- Cache location:
|
|
92
|
+
- `~/pinokio/agents/clients/<app_id>/<operation>.<ext>`
|
|
93
|
+
- First run for `<app_id>/<operation>`:
|
|
94
|
+
- inspect docs/code to infer endpoint + payload
|
|
95
|
+
- generate minimal HTTP client file (`js`/`py`/`sh`)
|
|
96
|
+
- Later runs:
|
|
97
|
+
- reuse existing generated client file directly
|
|
98
|
+
- Regenerate only if request indicates contract mismatch:
|
|
99
|
+
- 404/405 endpoint mismatch
|
|
100
|
+
- 400/422 payload/schema mismatch
|
|
101
|
+
- auth/header mismatch
|
|
102
|
+
|
|
103
|
+
## Behavior Rules
|
|
104
|
+
|
|
105
|
+
- Do not add app-specific hardcoding when user gave only capability (for example "tts").
|
|
106
|
+
- Do not guess hidden endpoints when docs/code are unclear; ask one targeted question.
|
|
107
|
+
- Do not rewrite launcher files unless user explicitly asked.
|
|
108
|
+
- Prefer returning full logs over brittle deterministic error parsing.
|
|
109
|
+
- REST endpoints may be used for diagnostics only when pterm is unavailable; do not claim full install/launch lifecycle completion without compatible pterm commands.
|
|
110
|
+
- Do not keep searching after app selection; move to status/run.
|
|
111
|
+
|
|
112
|
+
## Example A (Capability Only)
|
|
113
|
+
|
|
114
|
+
User: "Generate TTS from this text: hello world"
|
|
115
|
+
|
|
116
|
+
1. `pterm search "tts speech synthesis" --mode balanced --min-match 2 --limit 8`
|
|
117
|
+
2. Pick best match using deterministic ranking (or ask once if ambiguous).
|
|
118
|
+
3. `pterm status <app_id>`
|
|
119
|
+
4. If not ready: `pterm run <app_path>`, keep polling status.
|
|
120
|
+
5. Before first API call: `pterm status <app_id> --probe`
|
|
121
|
+
6. When ready: generate/reuse `text_to_speech` client and execute.
|
|
122
|
+
7. Return output (audio path/bytes) or failure with `pterm logs`.
|
|
123
|
+
|
|
124
|
+
## Example B (Explicit App)
|
|
125
|
+
|
|
126
|
+
User: "Use Qwen-TTS to generate speech from this text: hello world"
|
|
127
|
+
|
|
128
|
+
1. `pterm search "qwen tts" --mode balanced --min-match 1 --limit 8`.
|
|
129
|
+
2. If exact app_id/title match exists, pick it.
|
|
130
|
+
3. `pterm status <app_id>`
|
|
131
|
+
4. Before first API call: `pterm status <app_id> --probe`
|
|
132
|
+
5. If not ready: `pterm run <app_path>`, keep polling.
|
|
133
|
+
6. When ready: generate/reuse `text_to_speech` client and execute.
|
|
134
|
+
7. Return output or failure with `pterm logs`.
|