pinokiod 7.3.1 → 7.3.3

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.
Files changed (122) hide show
  1. package/kernel/api/github/index.js +444 -0
  2. package/kernel/api/index.js +199 -11
  3. package/kernel/api/process/index.js +124 -44
  4. package/kernel/api/shell_run_template.js +273 -0
  5. package/kernel/api/uri/index.js +51 -0
  6. package/kernel/bin/git.js +9 -10
  7. package/kernel/bin/huggingface.js +1 -1
  8. package/kernel/bin/zip.js +9 -1
  9. package/kernel/connect/providers/github/README.md +5 -4
  10. package/kernel/environment.js +195 -92
  11. package/kernel/git.js +98 -19
  12. package/kernel/gitconfig_template +7 -0
  13. package/kernel/gpu/amd.js +72 -0
  14. package/kernel/gpu/apple.js +8 -0
  15. package/kernel/gpu/common.js +12 -0
  16. package/kernel/gpu/intel.js +47 -0
  17. package/kernel/gpu/nvidia.js +8 -0
  18. package/kernel/index.js +11 -1
  19. package/kernel/managed_skills.js +871 -0
  20. package/kernel/plugin.js +6 -58
  21. package/kernel/plugin_sources.js +316 -0
  22. package/kernel/resource_usage/gpu.js +349 -0
  23. package/kernel/resource_usage/index.js +322 -0
  24. package/kernel/resource_usage/macos_footprint.js +197 -0
  25. package/kernel/resource_usage/preferences.js +92 -0
  26. package/kernel/resource_usage/process_tree.js +303 -0
  27. package/kernel/scripts/git/create +4 -4
  28. package/kernel/scripts/git/fork +7 -8
  29. package/kernel/shell.js +23 -2
  30. package/kernel/shells.js +41 -0
  31. package/kernel/sysinfo.js +62 -9
  32. package/kernel/util.js +60 -0
  33. package/package.json +1 -1
  34. package/server/index.js +984 -156
  35. package/server/lib/app_log_report.js +543 -0
  36. package/server/lib/content_validation.js +55 -33
  37. package/server/lib/launcher_instruction_bootstrap.js +4 -96
  38. package/server/lib/terminal_session_helpers.js +0 -3
  39. package/server/public/common.js +77 -31
  40. package/server/public/create-launcher.js +4 -32
  41. package/server/public/logs.js +1428 -0
  42. package/server/public/nav.js +7 -0
  43. package/server/public/plugin-detail.js +93 -10
  44. package/server/public/privacy_filter_worker.js +391 -0
  45. package/server/public/style.css +1104 -154
  46. package/server/public/task-launcher.js +8 -29
  47. package/server/public/universal-launcher.css +8 -6
  48. package/server/public/universal-launcher.js +3 -27
  49. package/server/routes/apps.js +195 -1
  50. package/server/views/app.ejs +3041 -717
  51. package/server/views/autolaunch.ejs +917 -0
  52. package/server/views/bootstrap.ejs +7 -1
  53. package/server/views/d.ejs +408 -65
  54. package/server/views/editor.ejs +85 -19
  55. package/server/views/index.ejs +661 -111
  56. package/server/views/init/index.ejs +1 -1
  57. package/server/views/install.ejs +1 -1
  58. package/server/views/logs.ejs +164 -86
  59. package/server/views/net.ejs +7 -1
  60. package/server/views/partials/d_terminal_column.ejs +2 -2
  61. package/server/views/partials/d_terminal_options.ejs +0 -8
  62. package/server/views/partials/fs_status.ejs +47 -0
  63. package/server/views/partials/home_action_modal.ejs +86 -0
  64. package/server/views/partials/home_run_menu.ejs +87 -0
  65. package/server/views/partials/main_sidebar.ejs +2 -0
  66. package/server/views/partials/menu.ejs +1 -1
  67. package/server/views/plugin_detail.ejs +19 -4
  68. package/server/views/plugins.ejs +201 -3
  69. package/server/views/pre.ejs +1 -1
  70. package/server/views/pro.ejs +1 -1
  71. package/server/views/shell.ejs +40 -18
  72. package/server/views/skills.ejs +506 -0
  73. package/server/views/terminal.ejs +45 -19
  74. package/spec/INSTRUCTION_SYNC.md +20 -10
  75. package/system/plugin/antigravity-cli/antigravity.png +0 -0
  76. package/system/plugin/antigravity-cli/common.js +155 -0
  77. package/system/plugin/antigravity-cli/install.js +272 -0
  78. package/system/plugin/antigravity-cli/pinokio.js +13 -0
  79. package/system/plugin/antigravity-cli-auto/antigravity.png +0 -0
  80. package/system/plugin/antigravity-cli-auto/pinokio.js +13 -0
  81. package/system/plugin/claude/claude.png +0 -0
  82. package/system/plugin/claude/pinokio.js +47 -0
  83. package/system/plugin/claude-auto/claude.png +0 -0
  84. package/system/plugin/claude-auto/pinokio.js +58 -0
  85. package/system/plugin/claude-desktop/icon.jpeg +0 -0
  86. package/system/plugin/claude-desktop/pinokio.js +23 -0
  87. package/system/plugin/codex/openai.webp +0 -0
  88. package/system/plugin/codex/pinokio.js +42 -0
  89. package/system/plugin/codex-auto/openai.webp +0 -0
  90. package/system/plugin/codex-auto/pinokio.js +49 -0
  91. package/system/plugin/codex-desktop/icon.png +0 -0
  92. package/system/plugin/codex-desktop/pinokio.js +23 -0
  93. package/system/plugin/crush/crush.png +0 -0
  94. package/system/plugin/crush/pinokio.js +15 -0
  95. package/system/plugin/cursor/cursor.jpeg +0 -0
  96. package/system/plugin/cursor/pinokio.js +23 -0
  97. package/system/plugin/qwen/pinokio.js +34 -0
  98. package/system/plugin/qwen/qwen.png +0 -0
  99. package/system/plugin/vscode/pinokio.js +20 -0
  100. package/system/plugin/vscode/vscode.png +0 -0
  101. package/system/plugin/windsurf/pinokio.js +23 -0
  102. package/system/plugin/windsurf/windsurf.png +0 -0
  103. package/test/antigravity-cli-plugin.test.js +185 -0
  104. package/test/app-api.test.js +239 -0
  105. package/test/app-log-report.test.js +67 -0
  106. package/test/environment-cache-preflight.test.js +98 -0
  107. package/test/git-bin.test.js +59 -0
  108. package/test/git-defaults.test.js +97 -0
  109. package/test/github-api.test.js +158 -0
  110. package/test/github-connection.test.js +117 -0
  111. package/test/huggingface-bin.test.js +25 -0
  112. package/test/managed-skills.test.js +351 -0
  113. package/test/plugin-action-functions.test.js +337 -0
  114. package/test/plugin-dev-iframe.test.js +17 -0
  115. package/test/plugin-sources.test.js +203 -0
  116. package/test/privacy-filter-worker-heuristics.test.js +69 -0
  117. package/test/process-wait.test.js +169 -0
  118. package/test/script-api.test.js +97 -0
  119. package/test/shell-api.test.js +134 -0
  120. package/test/shell-run-template.test.js +209 -0
  121. package/test/storage-api.test.js +137 -0
  122. package/test/uri-api.test.js +100 -0
@@ -0,0 +1,197 @@
1
+ "use strict"
2
+
3
+ const os = require("os")
4
+ const { execFileText, normalizePid } = require("./process_tree")
5
+
6
+ const DEFAULT_TIMEOUT_MS = 2000
7
+ const DEFAULT_TTL_MS = 15000
8
+ const DEFAULT_FAILURE_BACKOFF_MS = 30000
9
+ const FOOTPRINT_COMMAND = "/usr/bin/footprint"
10
+
11
+ function parseFootprintBytes(stdout) {
12
+ const text = String(stdout || "")
13
+ const summaryMatch = /Summary Footprint:\s+(\d+)\s+B/i.exec(text)
14
+ if (summaryMatch) {
15
+ const bytes = Number.parseInt(summaryMatch[1], 10)
16
+ return Number.isFinite(bytes) && bytes > 0 ? bytes : 0
17
+ }
18
+ const physicalMatch = /phys_footprint:\s+(\d+)\s+B/i.exec(text)
19
+ if (physicalMatch) {
20
+ const bytes = Number.parseInt(physicalMatch[1], 10)
21
+ return Number.isFinite(bytes) && bytes > 0 ? bytes : 0
22
+ }
23
+ const footprintMatch = /Footprint:\s+(\d+)\s+B/i.exec(text)
24
+ if (footprintMatch) {
25
+ const bytes = Number.parseInt(footprintMatch[1], 10)
26
+ return Number.isFinite(bytes) && bytes > 0 ? bytes : 0
27
+ }
28
+ return 0
29
+ }
30
+
31
+ function parseFootprintPidBytes(stdout) {
32
+ const processes = new Map()
33
+ const text = String(stdout || "")
34
+ const pattern = /\[(\d+)\]:[^\n]*?\bFootprint:\s+(\d+)\s+B/gi
35
+ let match = pattern.exec(text)
36
+ while (match) {
37
+ const pid = normalizePid(match[1])
38
+ const bytes = Number.parseInt(match[2], 10)
39
+ if (pid && Number.isFinite(bytes) && bytes > 0) {
40
+ processes.set(pid, bytes)
41
+ }
42
+ match = pattern.exec(text)
43
+ }
44
+ return processes
45
+ }
46
+
47
+ function normalizePidList(pids) {
48
+ const normalized = []
49
+ const seen = new Set()
50
+ for (const value of pids || []) {
51
+ const pid = normalizePid(value)
52
+ if (!pid || seen.has(pid)) continue
53
+ seen.add(pid)
54
+ normalized.push(pid)
55
+ }
56
+ return normalized
57
+ }
58
+
59
+ function errorMessage(error) {
60
+ return error && error.message ? error.message : String(error)
61
+ }
62
+
63
+ class MacFootprintSampler {
64
+ constructor(options = {}) {
65
+ this.platform = options.platform || os.platform()
66
+ this.timeoutMs = options.timeoutMs || DEFAULT_TIMEOUT_MS
67
+ this.ttlMs = options.ttlMs || DEFAULT_TTL_MS
68
+ this.failureBackoffMs = options.failureBackoffMs || DEFAULT_FAILURE_BACKOFF_MS
69
+ this.cache = new Map()
70
+ this.inFlight = new Map()
71
+ this.failures = new Map()
72
+ }
73
+
74
+ async getFootprintByPid(pids) {
75
+ if (this.platform !== "darwin") {
76
+ return {
77
+ available: false,
78
+ stale: false,
79
+ bytes: 0,
80
+ perPid: new Map(),
81
+ collectedAt: Date.now(),
82
+ pids: [],
83
+ error: null
84
+ }
85
+ }
86
+
87
+ const selectedPids = normalizePidList(pids)
88
+ if (selectedPids.length === 0) {
89
+ return {
90
+ available: false,
91
+ stale: false,
92
+ bytes: 0,
93
+ perPid: new Map(),
94
+ collectedAt: Date.now(),
95
+ pids: [],
96
+ error: null
97
+ }
98
+ }
99
+
100
+ const key = selectedPids.join(",")
101
+ const now = Date.now()
102
+ const cached = this.cache.get(key)
103
+ if (cached && now - cached.collectedAt < this.ttlMs) {
104
+ return cached
105
+ }
106
+ const failure = this.failures.get(key)
107
+ if (failure && now < failure.until) {
108
+ if (cached) {
109
+ return {
110
+ ...cached,
111
+ stale: true,
112
+ error: failure.error
113
+ }
114
+ }
115
+ return {
116
+ available: false,
117
+ stale: true,
118
+ bytes: 0,
119
+ perPid: new Map(),
120
+ collectedAt: now,
121
+ pids: selectedPids,
122
+ error: failure.error
123
+ }
124
+ }
125
+ if (this.inFlight.has(key)) {
126
+ return this.inFlight.get(key)
127
+ }
128
+
129
+ const args = []
130
+ for (const pid of selectedPids) {
131
+ args.push("-pid", String(pid))
132
+ }
133
+ args.push("-f", "bytes", "--noCategories")
134
+
135
+ const run = execFileText(FOOTPRINT_COMMAND, args, {
136
+ timeoutMs: this.timeoutMs,
137
+ maxBuffer: 512 * 1024
138
+ }).then(({ stdout }) => {
139
+ const perPid = parseFootprintPidBytes(stdout)
140
+ let bytes = 0
141
+ for (const value of perPid.values()) {
142
+ bytes += value
143
+ }
144
+ if (bytes <= 0) {
145
+ bytes = parseFootprintBytes(stdout)
146
+ }
147
+ const snapshot = {
148
+ available: bytes > 0,
149
+ stale: false,
150
+ bytes,
151
+ perPid,
152
+ collectedAt: Date.now(),
153
+ pids: selectedPids,
154
+ error: bytes > 0 ? null : "Unable to parse footprint output"
155
+ }
156
+ if (snapshot.available) {
157
+ this.cache.set(key, snapshot)
158
+ this.failures.delete(key)
159
+ }
160
+ return snapshot
161
+ }).catch((error) => {
162
+ const message = errorMessage(error)
163
+ this.failures.set(key, {
164
+ until: Date.now() + this.failureBackoffMs,
165
+ error: message
166
+ })
167
+ if (cached) {
168
+ return {
169
+ ...cached,
170
+ stale: true,
171
+ error: message
172
+ }
173
+ }
174
+ return {
175
+ available: false,
176
+ stale: false,
177
+ bytes: 0,
178
+ perPid: new Map(),
179
+ collectedAt: Date.now(),
180
+ pids: selectedPids,
181
+ error: message
182
+ }
183
+ }).finally(() => {
184
+ this.inFlight.delete(key)
185
+ })
186
+
187
+ this.inFlight.set(key, run)
188
+ return run
189
+ }
190
+
191
+ }
192
+
193
+ module.exports = {
194
+ MacFootprintSampler,
195
+ parseFootprintBytes,
196
+ parseFootprintPidBytes
197
+ }
@@ -0,0 +1,92 @@
1
+ "use strict"
2
+
3
+ const fs = require("fs")
4
+ const path = require("path")
5
+ const os = require("os")
6
+
7
+ const DEFAULT_PREFERENCES = Object.freeze({
8
+ show_ram: true,
9
+ show_vram: true,
10
+ show_cpu: false
11
+ })
12
+
13
+ class ResourceUsagePreferences {
14
+ constructor(options = {}) {
15
+ this.kernel = options.kernel || null
16
+ this.writeQueue = Promise.resolve()
17
+ this.cache = null
18
+ this.cacheLoadedAt = 0
19
+ }
20
+
21
+ getPreferencesPath() {
22
+ if (this.kernel && this.kernel.homedir && typeof this.kernel.path === "function") {
23
+ return this.kernel.path("cache", "resource_usage", "preferences.json")
24
+ }
25
+ const configuredHome = (
26
+ (this.kernel && this.kernel.store && typeof this.kernel.store.get === "function" ? this.kernel.store.get("home") : null)
27
+ || process.env.PINOKIO_HOME
28
+ )
29
+ if (configuredHome) {
30
+ return path.resolve(configuredHome, "cache", "resource_usage", "preferences.json")
31
+ }
32
+ return path.resolve(os.homedir(), "pinokio", "cache", "resource_usage", "preferences.json")
33
+ }
34
+
35
+ coerce(value = {}) {
36
+ const source = value && typeof value === "object" ? value : {}
37
+ return {
38
+ show_ram: Object.prototype.hasOwnProperty.call(source, "show_ram") ? Boolean(source.show_ram) : DEFAULT_PREFERENCES.show_ram,
39
+ show_vram: Object.prototype.hasOwnProperty.call(source, "show_vram") ? Boolean(source.show_vram) : DEFAULT_PREFERENCES.show_vram,
40
+ show_cpu: Object.prototype.hasOwnProperty.call(source, "show_cpu") ? Boolean(source.show_cpu) : DEFAULT_PREFERENCES.show_cpu
41
+ }
42
+ }
43
+
44
+ async read() {
45
+ if (this.cache && Date.now() - this.cacheLoadedAt < 5000) {
46
+ return this.cache
47
+ }
48
+ try {
49
+ const raw = await fs.promises.readFile(this.getPreferencesPath(), "utf8")
50
+ const parsed = JSON.parse(raw)
51
+ this.cache = this.coerce(parsed && parsed.preferences ? parsed.preferences : parsed)
52
+ } catch (_) {
53
+ this.cache = this.coerce({})
54
+ }
55
+ this.cacheLoadedAt = Date.now()
56
+ return this.cache
57
+ }
58
+
59
+ withWriteLock(task) {
60
+ const run = this.writeQueue.then(() => task())
61
+ this.writeQueue = run.catch(() => {})
62
+ return run
63
+ }
64
+
65
+ async update(updates = {}) {
66
+ return this.withWriteLock(async () => {
67
+ const current = await this.read()
68
+ const next = this.coerce({ ...current, ...(updates && typeof updates === "object" ? updates : {}) })
69
+ const preferencesPath = this.getPreferencesPath()
70
+ await fs.promises.mkdir(path.dirname(preferencesPath), { recursive: true })
71
+ const payload = {
72
+ updated_at: new Date().toISOString(),
73
+ preferences: next
74
+ }
75
+ const tmpPath = `${preferencesPath}.${process.pid}-${Date.now()}.tmp`
76
+ try {
77
+ await fs.promises.writeFile(tmpPath, JSON.stringify(payload, null, 2), "utf8")
78
+ await fs.promises.rename(tmpPath, preferencesPath)
79
+ } finally {
80
+ await fs.promises.rm(tmpPath, { force: true }).catch(() => {})
81
+ }
82
+ this.cache = next
83
+ this.cacheLoadedAt = Date.now()
84
+ return next
85
+ })
86
+ }
87
+ }
88
+
89
+ module.exports = {
90
+ DEFAULT_PREFERENCES,
91
+ ResourceUsagePreferences
92
+ }
@@ -0,0 +1,303 @@
1
+ "use strict"
2
+
3
+ const os = require("os")
4
+ const { execFile } = require("child_process")
5
+
6
+ const DEFAULT_TIMEOUT_MS = 2500
7
+ const DEFAULT_TTL_MS = 4000
8
+ const MAX_BUFFER = 8 * 1024 * 1024
9
+
10
+ function execFileText(command, args, options = {}) {
11
+ const timeoutMs = Math.max(250, Number(options.timeoutMs || DEFAULT_TIMEOUT_MS))
12
+ return new Promise((resolve, reject) => {
13
+ execFile(command, args, {
14
+ encoding: "utf8",
15
+ maxBuffer: options.maxBuffer || MAX_BUFFER,
16
+ timeout: timeoutMs,
17
+ windowsHide: true
18
+ }, (error, stdout, stderr) => {
19
+ if (error) {
20
+ error.stdout = stdout
21
+ error.stderr = stderr
22
+ reject(error)
23
+ return
24
+ }
25
+ resolve({ stdout: stdout || "", stderr: stderr || "" })
26
+ })
27
+ })
28
+ }
29
+
30
+ function normalizePid(value) {
31
+ const pid = Number.parseInt(String(value == null ? "" : value).trim(), 10)
32
+ return Number.isFinite(pid) && pid > 0 ? pid : null
33
+ }
34
+
35
+ function parseCpuTimeSeconds(value) {
36
+ const raw = String(value == null ? "" : value).trim()
37
+ if (!raw) return 0
38
+ const daySplit = raw.split("-")
39
+ let days = 0
40
+ let timePart = raw
41
+ if (daySplit.length === 2) {
42
+ days = Number.parseInt(daySplit[0], 10) || 0
43
+ timePart = daySplit[1]
44
+ }
45
+ const parts = timePart.split(":").map((part) => Number.parseFloat(part))
46
+ if (parts.some((part) => !Number.isFinite(part))) {
47
+ return 0
48
+ }
49
+ let seconds = 0
50
+ if (parts.length === 3) {
51
+ seconds = (parts[0] * 3600) + (parts[1] * 60) + parts[2]
52
+ } else if (parts.length === 2) {
53
+ seconds = (parts[0] * 60) + parts[1]
54
+ } else if (parts.length === 1) {
55
+ seconds = parts[0]
56
+ }
57
+ return (days * 86400) + seconds
58
+ }
59
+
60
+ function parsePsOutput(stdout) {
61
+ const processes = new Map()
62
+ for (const line of String(stdout || "").split(/\r?\n/)) {
63
+ const match = /^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s*(.*)$/.exec(line)
64
+ if (!match) continue
65
+ const pid = normalizePid(match[1])
66
+ const ppid = normalizePid(match[2])
67
+ if (!pid) continue
68
+ const rssKb = Number.parseInt(match[3], 10)
69
+ processes.set(pid, {
70
+ pid,
71
+ ppid,
72
+ rssBytes: Number.isFinite(rssKb) && rssKb > 0 ? rssKb * 1024 : 0,
73
+ cpuSeconds: parseCpuTimeSeconds(match[4]),
74
+ name: match[5] || ""
75
+ })
76
+ }
77
+ return processes
78
+ }
79
+
80
+ function parseWindowsProcessJson(stdout) {
81
+ const parsed = JSON.parse(stdout || "[]")
82
+ const rows = Array.isArray(parsed) ? parsed : [parsed]
83
+ const processes = new Map()
84
+ for (const row of rows) {
85
+ if (!row || typeof row !== "object") continue
86
+ const pid = normalizePid(row.ProcessId)
87
+ if (!pid) continue
88
+ const ppid = normalizePid(row.ParentProcessId)
89
+ const workingSet = Number(row.WorkingSetSize)
90
+ const kernelTime = Number(row.KernelModeTime)
91
+ const userTime = Number(row.UserModeTime)
92
+ const cpuTicks = (Number.isFinite(kernelTime) ? kernelTime : 0) + (Number.isFinite(userTime) ? userTime : 0)
93
+ processes.set(pid, {
94
+ pid,
95
+ ppid,
96
+ rssBytes: Number.isFinite(workingSet) && workingSet > 0 ? workingSet : 0,
97
+ cpuSeconds: cpuTicks / 10000000,
98
+ name: typeof row.Name === "string" ? row.Name : ""
99
+ })
100
+ }
101
+ return processes
102
+ }
103
+
104
+ async function collectPosixProcesses(timeoutMs) {
105
+ const { stdout } = await execFileText("ps", ["-axo", "pid=,ppid=,rss=,time=,comm="], { timeoutMs })
106
+ return parsePsOutput(stdout)
107
+ }
108
+
109
+ async function collectWindowsProcesses(timeoutMs) {
110
+ const script = [
111
+ "$ProgressPreference = 'SilentlyContinue';",
112
+ "Get-CimInstance Win32_Process |",
113
+ "Select-Object ProcessId,ParentProcessId,WorkingSetSize,KernelModeTime,UserModeTime,Name |",
114
+ "ConvertTo-Json -Compress"
115
+ ].join(" ")
116
+ const candidates = ["powershell.exe", "powershell", "pwsh"]
117
+ let lastError = null
118
+ for (const command of candidates) {
119
+ try {
120
+ const { stdout } = await execFileText(command, [
121
+ "-NoProfile",
122
+ "-NonInteractive",
123
+ "-ExecutionPolicy",
124
+ "Bypass",
125
+ "-Command",
126
+ script
127
+ ], { timeoutMs: Math.max(timeoutMs, 3000) })
128
+ return parseWindowsProcessJson(stdout)
129
+ } catch (error) {
130
+ lastError = error
131
+ if (error && error.code !== "ENOENT") {
132
+ break
133
+ }
134
+ }
135
+ }
136
+ throw lastError || new Error("Unable to collect Windows process list")
137
+ }
138
+
139
+ async function collectProcessSnapshot(options = {}) {
140
+ const platform = options.platform || os.platform()
141
+ const timeoutMs = options.timeoutMs || DEFAULT_TIMEOUT_MS
142
+ const processes = platform === "win32"
143
+ ? await collectWindowsProcesses(timeoutMs)
144
+ : await collectPosixProcesses(timeoutMs)
145
+ return {
146
+ available: true,
147
+ stale: false,
148
+ collectedAt: Date.now(),
149
+ processes,
150
+ error: null,
151
+ elapsedMs: null
152
+ }
153
+ }
154
+
155
+ function annotateCpuRates(snapshot, previous, cpuCount) {
156
+ if (!snapshot || !previous || !snapshot.processes || !previous.processes) {
157
+ return snapshot
158
+ }
159
+ const elapsedMs = Math.max(0, snapshot.collectedAt - previous.collectedAt)
160
+ snapshot.elapsedMs = elapsedMs
161
+ if (elapsedMs < 250) {
162
+ return snapshot
163
+ }
164
+ const elapsedSeconds = elapsedMs / 1000
165
+ const cores = Math.max(1, cpuCount || 1)
166
+ for (const [pid, entry] of snapshot.processes.entries()) {
167
+ const prev = previous.processes.get(pid)
168
+ if (!prev) continue
169
+ const delta = entry.cpuSeconds - prev.cpuSeconds
170
+ if (!Number.isFinite(delta) || delta < 0) continue
171
+ entry.cpuPercentCores = (delta / elapsedSeconds) * 100
172
+ entry.cpuPercent = entry.cpuPercentCores / cores
173
+ }
174
+ return snapshot
175
+ }
176
+
177
+ class ProcessSampler {
178
+ constructor(options = {}) {
179
+ this.platform = options.platform || os.platform()
180
+ this.timeoutMs = options.timeoutMs || DEFAULT_TIMEOUT_MS
181
+ this.ttlMs = options.ttlMs || DEFAULT_TTL_MS
182
+ this.cpuCount = Math.max(1, Array.isArray(os.cpus()) ? os.cpus().length : 1)
183
+ this.current = null
184
+ this.inFlight = null
185
+ this.failureUntil = 0
186
+ }
187
+
188
+ async getSnapshot() {
189
+ const now = Date.now()
190
+ if (this.current && now - this.current.collectedAt < this.ttlMs) {
191
+ return this.current
192
+ }
193
+ if (this.inFlight) {
194
+ return this.inFlight
195
+ }
196
+ if (this.failureUntil && now < this.failureUntil) {
197
+ if (this.current) {
198
+ return { ...this.current, stale: true }
199
+ }
200
+ return {
201
+ available: false,
202
+ stale: true,
203
+ collectedAt: now,
204
+ processes: new Map(),
205
+ error: "Process snapshot unavailable",
206
+ elapsedMs: null
207
+ }
208
+ }
209
+ this.inFlight = collectProcessSnapshot({
210
+ platform: this.platform,
211
+ timeoutMs: this.timeoutMs
212
+ }).then((snapshot) => {
213
+ annotateCpuRates(snapshot, this.current, this.cpuCount)
214
+ this.current = snapshot
215
+ this.failureUntil = 0
216
+ return snapshot
217
+ }).catch((error) => {
218
+ this.failureUntil = Date.now() + 15000
219
+ if (this.current) {
220
+ return {
221
+ ...this.current,
222
+ stale: true,
223
+ error: error && error.message ? error.message : String(error)
224
+ }
225
+ }
226
+ return {
227
+ available: false,
228
+ stale: false,
229
+ collectedAt: Date.now(),
230
+ processes: new Map(),
231
+ error: error && error.message ? error.message : String(error),
232
+ elapsedMs: null
233
+ }
234
+ }).finally(() => {
235
+ this.inFlight = null
236
+ })
237
+ return this.inFlight
238
+ }
239
+ }
240
+
241
+ function getDescendantPids(snapshot, rootPids = []) {
242
+ const processes = snapshot && snapshot.processes instanceof Map ? snapshot.processes : new Map()
243
+ const childrenByParent = new Map()
244
+ for (const entry of processes.values()) {
245
+ if (!entry || !entry.ppid) continue
246
+ if (!childrenByParent.has(entry.ppid)) {
247
+ childrenByParent.set(entry.ppid, [])
248
+ }
249
+ childrenByParent.get(entry.ppid).push(entry.pid)
250
+ }
251
+ const visited = new Set()
252
+ const queue = []
253
+ for (const rootPid of rootPids) {
254
+ const pid = normalizePid(rootPid)
255
+ if (!pid || visited.has(pid)) continue
256
+ visited.add(pid)
257
+ queue.push(pid)
258
+ }
259
+ for (let i = 0; i < queue.length; i += 1) {
260
+ const pid = queue[i]
261
+ const children = childrenByParent.get(pid) || []
262
+ for (const childPid of children) {
263
+ if (visited.has(childPid)) continue
264
+ visited.add(childPid)
265
+ queue.push(childPid)
266
+ }
267
+ }
268
+ return visited
269
+ }
270
+
271
+ function sumProcessMetrics(snapshot, pids) {
272
+ const processes = snapshot && snapshot.processes instanceof Map ? snapshot.processes : new Map()
273
+ let rssBytes = 0
274
+ let cpuPercent = 0
275
+ let cpuPercentCores = 0
276
+ let hasCpu = false
277
+ let processCount = 0
278
+ for (const pid of pids || []) {
279
+ const entry = processes.get(pid)
280
+ if (!entry) continue
281
+ processCount += 1
282
+ rssBytes += entry.rssBytes || 0
283
+ if (Number.isFinite(entry.cpuPercent)) {
284
+ hasCpu = true
285
+ cpuPercent += entry.cpuPercent
286
+ cpuPercentCores += Number.isFinite(entry.cpuPercentCores) ? entry.cpuPercentCores : 0
287
+ }
288
+ }
289
+ return {
290
+ processCount,
291
+ rssBytes,
292
+ cpuPercent: hasCpu ? cpuPercent : null,
293
+ cpuPercentCores: hasCpu ? cpuPercentCores : null
294
+ }
295
+ }
296
+
297
+ module.exports = {
298
+ ProcessSampler,
299
+ execFileText,
300
+ getDescendantPids,
301
+ normalizePid,
302
+ sumProcessMetrics
303
+ }
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "run": [{
3
- "method": "shell.run",
3
+ "method": "github.create",
4
4
  "params": {
5
5
  "path": "{{args.cwd}}",
6
- "message": [
7
- "gh repo create {{args.name}} --{{args.visibility}} --source {{args.cwd}} --push"
8
- ]
6
+ "cwd": "{{args.cwd}}",
7
+ "name": "{{args.name}}",
8
+ "visibility": "{{args.visibility}}"
9
9
  }
10
10
  }]
11
11
  }
@@ -1,21 +1,20 @@
1
1
  {
2
2
  "run": [{
3
3
  "when": "{{args.org}}",
4
- "method": "shell.run",
4
+ "method": "github.fork",
5
5
  "params": {
6
6
  "path": "{{args.cwd}}",
7
- "message": [
8
- "gh repo fork --remote=true --clone=false --fork-name {{args.name}} --org {{args.org}}"
9
- ]
7
+ "cwd": "{{args.cwd}}",
8
+ "name": "{{args.name}}",
9
+ "org": "{{args.org}}"
10
10
  }
11
11
  }, {
12
12
  "when": "{{!args.org}}",
13
- "method": "shell.run",
13
+ "method": "github.fork",
14
14
  "params": {
15
15
  "path": "{{args.cwd}}",
16
- "message": [
17
- "gh repo fork --remote=true --clone=false --fork-name {{args.name}}"
18
- ]
16
+ "cwd": "{{args.cwd}}",
17
+ "name": "{{args.name}}"
19
18
  }
20
19
  }]
21
20
  }