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.
@@ -525,7 +525,35 @@ const init = async (options, kernel) => {
525
525
  } else {
526
526
  root = kernel.homedir
527
527
  }
528
- const syncHomeSkillFromAgents = async () => {
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: Pinokio",
557
- "description: Guide for building 1-click launchers, building apps with 1-click launchers built-in, and controlling any localhost application via Pinokio",
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
- const existingSkillContent = await fs.promises.readFile(skillPath, "utf8")
564
- shouldWrite = existingSkillContent !== desiredSkillContent
595
+ templateContent = await fs.promises.readFile(templatePath, "utf8")
565
596
  } catch (error) {
566
- if (!(error && error.code === "ENOENT")) {
567
- throw error
597
+ if (error && error.code === "ENOENT") {
598
+ return
568
599
  }
600
+ throw error
569
601
  }
570
602
 
571
- if (shouldWrite) {
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 ~/.agents/skills/pinokio/SKILL.md in sync with ~/pinokio/AGENTS.md
667
- await syncHomeSkillFromAgents()
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(/&amp;/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.28",
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
- "@parcel/watcher": "^2.4.1"
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`.