pinokiod 5.3.6 → 5.3.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinokiod",
3
- "version": "5.3.6",
3
+ "version": "5.3.8",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/server/index.js CHANGED
@@ -48,6 +48,8 @@ const REGISTRY_PING_PNG = Buffer.from(
48
48
  'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMBAAObF+6bAAAAAElFTkSuQmCC',
49
49
  'base64'
50
50
  )
51
+ const DEFAULT_REGISTRY_URL = 'https://beta.pinokio.co'
52
+ const DEFAULT_REGISTRY_CONNECT_URL = `${DEFAULT_REGISTRY_URL}/connect/pinokio`
51
53
 
52
54
  const ex = fn => (req, res, next) => {
53
55
  Promise.resolve(fn(req, res, next)).catch(next);
@@ -1106,9 +1108,9 @@ class Server {
1106
1108
  result.snapshotFooterEnabled = !!(options && options.snapshotFooterEnabled)
1107
1109
  result.hasSnapshots = !!(options && options.hasSnapshots)
1108
1110
  result.pendingSnapshotId = options && options.pendingSnapshotId ? String(options.pendingSnapshotId) : null
1109
- const registryBetaEnabled = await this.isRegistryBetaEnabled().catch(() => false)
1110
- result.registryBetaEnabled = registryBetaEnabled
1111
- if (!registryBetaEnabled) {
1111
+ const registryEnabled = await this.isRegistryEnabled().catch(() => false)
1112
+ result.registryEnabled = registryEnabled
1113
+ if (!registryEnabled) {
1112
1114
  result.pendingSnapshotId = null
1113
1115
  }
1114
1116
  // if (!this.kernel.proto.config) {
@@ -1131,28 +1133,8 @@ class Server {
1131
1133
  }
1132
1134
  return { editorUrl, prevUrl }
1133
1135
  }
1134
- parseRegistryBetaValue(value) {
1135
- if (value === true) return true
1136
- if (value === false) return false
1137
- if (value == null) return null
1138
- const str = String(value).trim().toLowerCase()
1139
- if (!str) return null
1140
- if (["1", "true", "yes", "on", "enable", "enabled"].includes(str)) return true
1141
- if (["0", "false", "no", "off", "disable", "disabled"].includes(str)) return false
1142
- return null
1143
- }
1144
- async isRegistryBetaEnabled() {
1145
- let raw = null
1146
- try {
1147
- const env = await Environment.get(this.kernel.homedir, this.kernel)
1148
- if (env && Object.prototype.hasOwnProperty.call(env, "PINOKIO_REGISTRY_BETA")) {
1149
- raw = env.PINOKIO_REGISTRY_BETA
1150
- }
1151
- } catch (_) {}
1152
- if (raw == null && process.env.PINOKIO_REGISTRY_BETA != null) {
1153
- raw = process.env.PINOKIO_REGISTRY_BETA
1154
- }
1155
- return this.parseRegistryBetaValue(raw) === true
1136
+ async isRegistryEnabled() {
1137
+ return true
1156
1138
  }
1157
1139
  async updateEnvironmentVars(updates) {
1158
1140
  await Environment.init({}, this.kernel)
@@ -1194,6 +1176,9 @@ class Server {
1194
1176
  await this.updateEnvironmentVars(updates).catch(() => {})
1195
1177
  }
1196
1178
  }
1179
+ if (!url) {
1180
+ url = DEFAULT_REGISTRY_URL
1181
+ }
1197
1182
  return { url, apiKey }
1198
1183
  }
1199
1184
  async getRegistryConnectUrl() {
@@ -1229,6 +1214,11 @@ class Server {
1229
1214
  }
1230
1215
  }
1231
1216
  }
1217
+ if (!value) {
1218
+ const registry = await this.getRegistryConfig().catch(() => ({ url: null }))
1219
+ const baseUrl = registry && registry.url ? String(registry.url).replace(/\/$/, "") : ""
1220
+ value = baseUrl ? `${baseUrl}/connect/pinokio` : DEFAULT_REGISTRY_CONNECT_URL
1221
+ }
1232
1222
  if (value && source === "env-endpoint") {
1233
1223
  await this.updateEnvironmentVars({
1234
1224
  PINOKIO_REGISTRY_CONNECT_URL: value,
@@ -1271,9 +1261,9 @@ class Server {
1271
1261
  let hasSnapshots = false
1272
1262
  let pendingSnapshotId = null
1273
1263
  const debugLogs = []
1274
- const registryBetaEnabled = await this.isRegistryBetaEnabled().catch(() => false)
1264
+ const registryEnabled = await this.isRegistryEnabled().catch(() => false)
1275
1265
  const shouldTreatPending = (entry) => {
1276
- if (!registryBetaEnabled || !entry || entry.decision) return false
1266
+ if (!registryEnabled || !entry || entry.decision) return false
1277
1267
  const status = entry.sync && entry.sync.status ? String(entry.sync.status) : "local"
1278
1268
  if (status === "published" || status === "imported") return false
1279
1269
  return true
@@ -4616,7 +4606,7 @@ class Server {
4616
4606
  })
4617
4607
  }))
4618
4608
  this.app.get("/checkpoints", ex(async (req, res) => {
4619
- const registryBetaEnabled = await this.isRegistryBetaEnabled().catch(() => false)
4609
+ const registryEnabled = await this.isRegistryEnabled().catch(() => false)
4620
4610
  const peerAccess = await this.composePeerAccessPayload()
4621
4611
  const list = this.getPeers()
4622
4612
  const history = this.kernel.git && this.kernel.git.history ? this.kernel.git.history : { version: "1", apps: {} }
@@ -4819,7 +4809,7 @@ class Server {
4819
4809
  autoInstall,
4820
4810
  importError,
4821
4811
  checkpointsDir,
4822
- registryBetaEnabled,
4812
+ registryEnabled,
4823
4813
  portal: this.portal,
4824
4814
  logo: this.logo,
4825
4815
  theme: this.theme,
@@ -4827,19 +4817,37 @@ class Server {
4827
4817
  list,
4828
4818
  })
4829
4819
  }))
4830
- this.app.get("/checkpoints/registry_beta", ex(async (req, res) => {
4831
- const registryBetaEnabled = await this.isRegistryBetaEnabled().catch(() => false)
4820
+ this.app.get("/checkpoints/registry", ex(async (req, res) => {
4832
4821
  const registryConnectUrl = await this.getRegistryConnectUrl().catch(() => null)
4833
4822
  const registryUrlPrefill = typeof req.query.registry_url === "string" ? req.query.registry_url.trim() : ""
4834
- res.render("checkpoints_registry_beta", {
4835
- registryBetaEnabled,
4823
+ res.render("checkpoints_registry", {
4836
4824
  registryConnectUrl,
4837
4825
  registryUrlPrefill,
4838
4826
  theme: this.theme,
4839
4827
  agent: req.agent,
4840
4828
  })
4841
4829
  }))
4842
-
4830
+ this.app.post("/checkpoints/registry", ex(async (req, res) => {
4831
+ const connectRaw = Object.prototype.hasOwnProperty.call(req.body || {}, "connectUrl")
4832
+ ? req.body.connectUrl
4833
+ : (Object.prototype.hasOwnProperty.call(req.body || {}, "registry_url")
4834
+ ? req.body.registry_url
4835
+ : (Object.prototype.hasOwnProperty.call(req.body || {}, "connectEndpoint")
4836
+ ? req.body.connectEndpoint
4837
+ : (Object.prototype.hasOwnProperty.call(req.query || {}, "connectUrl")
4838
+ ? req.query.connectUrl
4839
+ : (Object.prototype.hasOwnProperty.call(req.query || {}, "registry_url")
4840
+ ? req.query.registry_url
4841
+ : (Object.prototype.hasOwnProperty.call(req.query || {}, "connectEndpoint")
4842
+ ? req.query.connectEndpoint
4843
+ : "")))))
4844
+ const connectUrl = connectRaw != null ? String(connectRaw).trim() : ""
4845
+ await this.updateEnvironmentVars({
4846
+ PINOKIO_REGISTRY_CONNECT_URL: connectUrl,
4847
+ PINOKIO_REGISTRY_CONNECT_ENDPOINT: ""
4848
+ })
4849
+ res.json({ ok: true, connectUrl: connectUrl || null })
4850
+ }))
4843
4851
  this.app.get("/registry/ping.png", ex(async (_req, res) => {
4844
4852
  res.setHeader('Content-Type', 'image/png')
4845
4853
  res.setHeader('Cache-Control', 'no-store')
@@ -4852,7 +4860,7 @@ class Server {
4852
4860
  const returnRaw = typeof req.query.return === 'string' ? req.query.return.trim() : ''
4853
4861
  const successRaw = typeof req.query.success === 'string' ? req.query.success.trim() : ''
4854
4862
  const appSlug = typeof req.query.app === 'string' ? req.query.app.trim() : ''
4855
- const registryEnabled = await this.isRegistryBetaEnabled().catch(() => false)
4863
+ const registryEnabled = await this.isRegistryEnabled().catch(() => false)
4856
4864
 
4857
4865
  let returnUrl = null
4858
4866
  if (returnRaw) {
@@ -5018,49 +5026,9 @@ class Server {
5018
5026
  res.json({ linked: !!apiKey, url: baseUrl })
5019
5027
  }))
5020
5028
 
5021
- this.app.post("/checkpoints/registry_beta", ex(async (req, res) => {
5022
- const rawValue = Object.prototype.hasOwnProperty.call(req.body || {}, "enabled")
5023
- ? req.body.enabled
5024
- : (Object.prototype.hasOwnProperty.call(req.query || {}, "enabled") ? req.query.enabled : null)
5025
- const hasConnectField = Object.prototype.hasOwnProperty.call(req.body || {}, "connectUrl")
5026
- || Object.prototype.hasOwnProperty.call(req.body || {}, "connectEndpoint")
5027
- || Object.prototype.hasOwnProperty.call(req.body || {}, "registry_url")
5028
- || Object.prototype.hasOwnProperty.call(req.query || {}, "connectUrl")
5029
- || Object.prototype.hasOwnProperty.call(req.query || {}, "connectEndpoint")
5030
- || Object.prototype.hasOwnProperty.call(req.query || {}, "registry_url")
5031
- const connectRaw = Object.prototype.hasOwnProperty.call(req.body || {}, "connectUrl")
5032
- ? req.body.connectUrl
5033
- : (Object.prototype.hasOwnProperty.call(req.body || {}, "registry_url")
5034
- ? req.body.registry_url
5035
- : (Object.prototype.hasOwnProperty.call(req.body || {}, "connectEndpoint")
5036
- ? req.body.connectEndpoint
5037
- : (Object.prototype.hasOwnProperty.call(req.query || {}, "connectUrl")
5038
- ? req.query.connectUrl
5039
- : (Object.prototype.hasOwnProperty.call(req.query || {}, "registry_url")
5040
- ? req.query.registry_url
5041
- : (Object.prototype.hasOwnProperty.call(req.query || {}, "connectEndpoint")
5042
- ? req.query.connectEndpoint
5043
- : null)))))
5044
- const connectUrl = connectRaw != null ? String(connectRaw).trim() : ""
5045
- const parsed = this.parseRegistryBetaValue(rawValue)
5046
- if (parsed == null) {
5047
- res.status(400).json({ ok: false, error: "Invalid enabled value" })
5048
- return
5049
- }
5050
- const updates = {
5051
- PINOKIO_REGISTRY_BETA: parsed ? "1" : ""
5052
- }
5053
- if (hasConnectField) {
5054
- updates.PINOKIO_REGISTRY_CONNECT_URL = connectUrl
5055
- updates.PINOKIO_REGISTRY_CONNECT_ENDPOINT = ""
5056
- }
5057
- await this.updateEnvironmentVars(updates)
5058
- res.json({ ok: true, enabled: parsed, connectUrl: connectUrl || null })
5059
- }))
5060
-
5061
5029
  this.app.post("/checkpoints/publish", ex(async (req, res) => {
5062
- const registryBetaEnabled = await this.isRegistryBetaEnabled().catch(() => false)
5063
- if (!registryBetaEnabled) {
5030
+ const registryEnabled = await this.isRegistryEnabled().catch(() => false)
5031
+ if (!registryEnabled) {
5064
5032
  res.status(404).json({ ok: false, error: "Not found" })
5065
5033
  return
5066
5034
  }
@@ -5130,8 +5098,8 @@ class Server {
5130
5098
  }))
5131
5099
 
5132
5100
  this.app.post("/checkpoints/decision", ex(async (req, res) => {
5133
- const registryBetaEnabled = await this.isRegistryBetaEnabled().catch(() => false)
5134
- if (!registryBetaEnabled) {
5101
+ const registryEnabled = await this.isRegistryEnabled().catch(() => false)
5102
+ if (!registryEnabled) {
5135
5103
  res.status(404).json({ ok: false, error: "Not found" })
5136
5104
  return
5137
5105
  }
@@ -5161,13 +5129,13 @@ class Server {
5161
5129
  res.json({ ok: false })
5162
5130
  return
5163
5131
  }
5164
- const registryBetaEnabled = await this.isRegistryBetaEnabled().catch(() => false)
5132
+ const registryEnabled = await this.isRegistryEnabled().catch(() => false)
5165
5133
  const publishRaw = typeof req.query.publish === 'string' ? req.query.publish.trim().toLowerCase() : ''
5166
- const wantPublish = registryBetaEnabled && (publishRaw === '1' || publishRaw === 'true' || publishRaw === 'cloud')
5134
+ const wantPublish = registryEnabled && (publishRaw === '1' || publishRaw === 'true' || publishRaw === 'cloud')
5167
5135
  const root = this.kernel.path("api", name)
5168
5136
  const repos = await this.kernel.git.repos(root)
5169
5137
  const created = await this.kernel.git.appendWorkspaceSnapshot(name, repos)
5170
- if (registryBetaEnabled && created && created.remoteKey && created.id) {
5138
+ if (registryEnabled && created && created.remoteKey && created.id) {
5171
5139
  const existingDecision = this.kernel.git.getCheckpointDecision(created.remoteKey, created.id)
5172
5140
  if (!existingDecision) {
5173
5141
  await this.kernel.git.setCheckpointDecision(created.remoteKey, created.id, "pending").catch(() => {})
@@ -3702,12 +3702,12 @@ body.dark .snapshot-footer-input input {
3702
3702
  <iframe class='selected' src="<%=editor_tab%>"></iframe>
3703
3703
  <% } %>
3704
3704
  </main>
3705
- <% const registryEnabled = typeof registryBetaEnabled === "undefined" ? false : registryBetaEnabled; %>
3705
+ <% const registryEnabledValue = typeof registryEnabled === "undefined" ? false : registryEnabled; %>
3706
3706
  <% const snapshotFooterAllowed = typeof snapshotFooterEnabled === "undefined" ? false : snapshotFooterEnabled; %>
3707
3707
  <% if (snapshotFooterAllowed) { %>
3708
- <div class='snapshot-footer hidden' data-workspace="<%=name%>" data-pending-snapshot-id="<%= pendingSnapshotId ? pendingSnapshotId : '' %>" data-registry-beta-enabled="<%= registryEnabled ? '1' : '0' %>">
3709
- <div class="snapshot-footer-save <%= registryEnabled && pendingSnapshotId ? 'hidden' : '' %>">
3710
- <div class='caption' title="<%= registryEnabled ? 'Saves a tiny JSON checkpoint of exact commit hashes (no files). You can publish it after saving.' : 'Saves a tiny JSON checkpoint of exact commit hashes (no files).' %>">
3708
+ <div class='snapshot-footer hidden' data-workspace="<%=name%>" data-pending-snapshot-id="<%= pendingSnapshotId ? pendingSnapshotId : '' %>" data-registry-enabled="<%= registryEnabledValue ? '1' : '0' %>">
3709
+ <div class="snapshot-footer-save <%= registryEnabledValue && pendingSnapshotId ? 'hidden' : '' %>">
3710
+ <div class='caption' title="<%= registryEnabledValue ? 'Saves a tiny JSON checkpoint of exact commit hashes (no files). You can publish it after saving.' : 'Saves a tiny JSON checkpoint of exact commit hashes (no files).' %>">
3711
3711
  Snapshot installed modules information and restore later
3712
3712
  </div>
3713
3713
  <div class="snapshot-footer-actions" style="display:flex; gap:10px; flex-wrap:wrap; align-items:center;">
@@ -3718,7 +3718,7 @@ body.dark .snapshot-footer-input input {
3718
3718
  <button type="button" class="btn snapshot-btn-dismiss"><i class="fa-solid fa-circle-xmark"></i> Dismiss</button>
3719
3719
  </div>
3720
3720
  </div>
3721
- <% if (registryEnabled) { %>
3721
+ <% if (registryEnabledValue) { %>
3722
3722
  <div class="snapshot-footer-publish <%= pendingSnapshotId ? '' : 'hidden' %>" aria-live="polite">
3723
3723
  <div class="snapshot-footer-publish-text">Local save success! Publish to the registry?</div>
3724
3724
  <div class="snapshot-footer-actions" style="display:flex; gap:10px; flex-wrap:wrap; align-items:center;">
@@ -9731,7 +9731,7 @@ document.addEventListener("DOMContentLoaded", () => {
9731
9731
  const publishText = publishPanel ? publishPanel.querySelector(".snapshot-footer-publish-text") : null
9732
9732
  const publishActions = publishPanel ? publishPanel.querySelector(".snapshot-footer-actions") : null
9733
9733
  if (!saveBtn) return
9734
- const registryBetaEnabled = footer.getAttribute("data-registry-beta-enabled") === "1"
9734
+ const registryEnabled = footer.getAttribute("data-registry-enabled") === "1"
9735
9735
  const workspace = footer.getAttribute("data-workspace")
9736
9736
  if (!workspace) {
9737
9737
  footer.classList.add("hidden")
@@ -9780,7 +9780,7 @@ document.addEventListener("DOMContentLoaded", () => {
9780
9780
  }
9781
9781
  }
9782
9782
  const showPublishPanel = () => {
9783
- if (!registryBetaEnabled) {
9783
+ if (!registryEnabled) {
9784
9784
  footer.classList.add("hidden")
9785
9785
  return
9786
9786
  }
@@ -9795,7 +9795,7 @@ document.addEventListener("DOMContentLoaded", () => {
9795
9795
  const hasSnapshots = !!(status && status.hasSnapshots)
9796
9796
  pendingSnapshotId = status && status.pendingSnapshotId ? String(status.pendingSnapshotId) : ""
9797
9797
  footer.setAttribute("data-pending-snapshot-id", pendingSnapshotId)
9798
- const shouldShow = !hasSnapshots || (registryBetaEnabled && pendingSnapshotId)
9798
+ const shouldShow = !hasSnapshots || (registryEnabled && pendingSnapshotId)
9799
9799
  if (!shouldShow) {
9800
9800
  setDismissedClass(false)
9801
9801
  footer.classList.add("hidden")
@@ -10194,8 +10194,8 @@ document.addEventListener("DOMContentLoaded", () => {
10194
10194
  }
10195
10195
 
10196
10196
  saveBtn.addEventListener("click", handleSave)
10197
- if (registryBetaEnabled && publishBtn) publishBtn.addEventListener("click", handlePublish)
10198
- if (registryBetaEnabled && laterBtn) laterBtn.addEventListener("click", handleLater)
10197
+ if (registryEnabled && publishBtn) publishBtn.addEventListener("click", handlePublish)
10198
+ if (registryEnabled && laterBtn) laterBtn.addEventListener("click", handleLater)
10199
10199
  if (dismissBtn) dismissBtn.addEventListener("click", handleDismiss)
10200
10200
  })
10201
10201
  </script>
@@ -403,12 +403,12 @@ body.dark .backup-loading .backup-loading-text {
403
403
  window.backupItems = <%- JSON.stringify(items || []) %>;
404
404
  window.backupAutoInstall = <%- JSON.stringify(autoInstall || null) %>;
405
405
  window.backupImportError = <%- JSON.stringify(importError || null) %>;
406
- window.registryBetaEnabled = <%- JSON.stringify(!!registryBetaEnabled) %>;
406
+ window.registryEnabled = <%- JSON.stringify(!!registryEnabled) %>;
407
407
  document.addEventListener("DOMContentLoaded", () => {
408
408
  const items = Array.isArray(window.backupItems) ? window.backupItems : []
409
409
  const autoInstall = window.backupAutoInstall && typeof window.backupAutoInstall === "object" ? window.backupAutoInstall : null
410
410
  const importError = typeof window.backupImportError === "string" && window.backupImportError.trim() ? window.backupImportError.trim() : null
411
- const registryBetaEnabled = window.registryBetaEnabled === true
411
+ const registryEnabled = window.registryEnabled === true
412
412
 
413
413
  const escapeHtml = (value) => {
414
414
  const str = value == null ? '' : String(value)
@@ -509,7 +509,7 @@ document.addEventListener("DOMContentLoaded", () => {
509
509
  if (when) metaParts.push(when)
510
510
  const metaText = metaParts.join(" · ")
511
511
  const syncStatus = snap && snap.sync && snap.sync.status ? String(snap.sync.status) : null
512
- const publishMarkup = registryBetaEnabled
512
+ const publishMarkup = registryEnabled
513
513
  ? (syncStatus === "published"
514
514
  ? `<span class="badge">Cloud saved</span>`
515
515
  : `<button type="button" class="url-modal-button confirm snapshot-publish" data-snapshot-id="${snap.id}">Save to Cloud</button>`)
@@ -1070,12 +1070,17 @@ document.addEventListener("DOMContentLoaded", () => {
1070
1070
  <div class='content backups-container'>
1071
1071
  <h2>Checkpoints</h2>
1072
1072
  <p class="subtitle">Local version history for workspaces on this machine.</p>
1073
- <div style="margin: 10px 0 20px 0;">
1073
+ <div style="margin: 10px 0 20px 0; display:flex; gap:10px; flex-wrap:wrap;">
1074
1074
  <% if (checkpointsDir) { %>
1075
1075
  <button type="button" class="btn" data-filepath="<%= checkpointsDir %>">
1076
1076
  <i class="fa-solid fa-folder-open"></i> Open checkpoints folder
1077
1077
  </button>
1078
1078
  <% } %>
1079
+ <% if (registryEnabled) { %>
1080
+ <a class="btn" href="/checkpoints/registry">
1081
+ <i class="fa-solid fa-link"></i> Registry settings
1082
+ </a>
1083
+ <% } %>
1079
1084
  </div>
1080
1085
  <% if (items && items.length > 0) { %>
1081
1086
  <% items.forEach((item) => { %>
@@ -0,0 +1,153 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <style>
7
+ body {
8
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
9
+ display: flex;
10
+ align-items: center;
11
+ justify-content: center;
12
+ min-height: 100vh;
13
+ margin: 0;
14
+ background: #f5f5f5;
15
+ color: #0f172a;
16
+ }
17
+ .card {
18
+ background: white;
19
+ padding: 32px;
20
+ border-radius: 12px;
21
+ box-shadow: 0 2px 10px rgba(15, 23, 42, 0.08);
22
+ max-width: 520px;
23
+ width: calc(100% - 32px);
24
+ }
25
+ h1 {
26
+ margin: 0 0 8px;
27
+ font-size: 22px;
28
+ }
29
+ .subtitle {
30
+ margin: 0 0 16px;
31
+ color: #64748b;
32
+ font-size: 14px;
33
+ }
34
+ label {
35
+ display: block;
36
+ font-weight: 600;
37
+ font-size: 13px;
38
+ margin-bottom: 6px;
39
+ }
40
+ input[type="url"] {
41
+ width: 100%;
42
+ padding: 10px 12px;
43
+ border-radius: 8px;
44
+ border: 1px solid #e2e8f0;
45
+ font-size: 14px;
46
+ box-sizing: border-box;
47
+ }
48
+ .hint {
49
+ margin-top: 6px;
50
+ font-size: 12px;
51
+ color: #94a3b8;
52
+ }
53
+ .actions {
54
+ margin-top: 16px;
55
+ display: flex;
56
+ gap: 10px;
57
+ flex-wrap: wrap;
58
+ }
59
+ .btn {
60
+ border: none;
61
+ border-radius: 8px;
62
+ padding: 10px 16px;
63
+ font-size: 14px;
64
+ cursor: pointer;
65
+ text-decoration: none;
66
+ display: inline-flex;
67
+ align-items: center;
68
+ justify-content: center;
69
+ }
70
+ .btn-primary {
71
+ background: #2563eb;
72
+ color: white;
73
+ }
74
+ .btn-secondary {
75
+ background: #e2e8f0;
76
+ color: #0f172a;
77
+ }
78
+ .message {
79
+ margin-top: 12px;
80
+ font-size: 13px;
81
+ min-height: 18px;
82
+ }
83
+ .message.success {
84
+ color: #166534;
85
+ }
86
+ .message.error {
87
+ color: #991b1b;
88
+ }
89
+ </style>
90
+ </head>
91
+ <body>
92
+ <div class="card">
93
+ <h1>Registry Settings</h1>
94
+ <div class="subtitle">Set the connect URL used for linking and published checkpoints.</div>
95
+ <form id="registry-form">
96
+ <label for="registry-connect-url">Registry connect URL</label>
97
+ <input
98
+ id="registry-connect-url"
99
+ type="url"
100
+ placeholder="https://your-registry/connect/pinokio"
101
+ value="<%= registryUrlPrefill || registryConnectUrl || '' %>"
102
+ autocomplete="off"
103
+ />
104
+ <div class="hint">Example: https://your-registry/connect/pinokio</div>
105
+ <div class="actions">
106
+ <button class="btn btn-primary" type="submit">Save</button>
107
+ <a class="btn btn-secondary" href="/checkpoints">Back</a>
108
+ </div>
109
+ </form>
110
+ <div id="registry-message" class="message" aria-live="polite"></div>
111
+ </div>
112
+
113
+ <script>
114
+ const form = document.getElementById("registry-form");
115
+ const input = document.getElementById("registry-connect-url");
116
+ const message = document.getElementById("registry-message");
117
+
118
+ const setMessage = (text, isError) => {
119
+ if (!message) return;
120
+ message.textContent = text || "";
121
+ message.classList.toggle("error", !!isError);
122
+ message.classList.toggle("success", !isError && !!text);
123
+ };
124
+
125
+ if (form) {
126
+ form.addEventListener("submit", async (event) => {
127
+ event.preventDefault();
128
+ setMessage("");
129
+ const connectUrl = input && input.value != null ? String(input.value).trim() : "";
130
+ const submitBtn = form.querySelector("button[type='submit']");
131
+ if (submitBtn) submitBtn.disabled = true;
132
+ try {
133
+ const res = await fetch("/checkpoints/registry", {
134
+ method: "POST",
135
+ headers: { "Content-Type": "application/json", "Accept": "application/json" },
136
+ body: JSON.stringify({ connectUrl })
137
+ });
138
+ const payload = res.ok ? await res.json().catch(() => null) : null;
139
+ if (!payload || !payload.ok) {
140
+ setMessage("Could not save registry settings.", true);
141
+ } else {
142
+ setMessage("Saved.", false);
143
+ }
144
+ } catch (_) {
145
+ setMessage("Could not save registry settings.", true);
146
+ } finally {
147
+ if (submitBtn) submitBtn.disabled = false;
148
+ }
149
+ });
150
+ }
151
+ </script>
152
+ </body>
153
+ </html>
@@ -1,260 +0,0 @@
1
- <html>
2
- <head>
3
- <meta charset="UTF-8" />
4
- <meta name="viewport" content="width=device-width, initial-scale=1" />
5
- <style>
6
- :root {
7
- --bg: #f5f5f5;
8
- --card: #ffffff;
9
- --text: #0f172a;
10
- --muted: #64748b;
11
- --border: rgba(15, 23, 42, 0.08);
12
- --btn: #111827;
13
- --btn-text: #ffffff;
14
- --btn-secondary: #e2e8f0;
15
- --btn-secondary-text: #0f172a;
16
- --badge-on: #16a34a;
17
- --badge-off: #dc2626;
18
- }
19
- body.dark {
20
- --bg: #0b1120;
21
- --card: #111827;
22
- --text: #e2e8f0;
23
- --muted: #94a3b8;
24
- --border: rgba(148, 163, 184, 0.2);
25
- --btn: #e2e8f0;
26
- --btn-text: #0b1120;
27
- --btn-secondary: #1f2937;
28
- --btn-secondary-text: #e2e8f0;
29
- --badge-on: #22c55e;
30
- --badge-off: #f87171;
31
- }
32
- body {
33
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
34
- background: var(--bg);
35
- color: var(--text);
36
- display: flex;
37
- align-items: center;
38
- justify-content: center;
39
- min-height: 100vh;
40
- margin: 0;
41
- padding: 24px;
42
- }
43
- .card {
44
- background: var(--card);
45
- border: 1px solid var(--border);
46
- border-radius: 16px;
47
- box-shadow: 0 12px 30px rgba(15, 23, 42, 0.08);
48
- padding: 28px;
49
- max-width: 520px;
50
- width: 100%;
51
- }
52
- .title {
53
- font-size: 22px;
54
- font-weight: 700;
55
- margin: 0 0 6px 0;
56
- }
57
- .subtitle {
58
- margin: 0 0 18px 0;
59
- color: var(--muted);
60
- font-size: 14px;
61
- }
62
- .status-row {
63
- display: flex;
64
- align-items: center;
65
- gap: 12px;
66
- margin-bottom: 18px;
67
- }
68
- .field {
69
- display: flex;
70
- flex-direction: column;
71
- gap: 6px;
72
- margin-bottom: 18px;
73
- }
74
- .field label {
75
- font-size: 13px;
76
- color: var(--muted);
77
- text-transform: uppercase;
78
- letter-spacing: 0.08em;
79
- }
80
- .field input {
81
- border: 1px solid var(--border);
82
- background: transparent;
83
- color: var(--text);
84
- padding: 10px 12px;
85
- border-radius: 10px;
86
- font-size: 14px;
87
- }
88
- .field-hint {
89
- font-size: 12px;
90
- color: var(--muted);
91
- }
92
- .status-label {
93
- font-size: 13px;
94
- color: var(--muted);
95
- text-transform: uppercase;
96
- letter-spacing: 0.08em;
97
- }
98
- .status-badge {
99
- padding: 6px 12px;
100
- border-radius: 999px;
101
- font-weight: 600;
102
- font-size: 12px;
103
- border: 1px solid transparent;
104
- }
105
- .status-badge.enabled {
106
- color: var(--badge-on);
107
- border-color: rgba(22, 163, 74, 0.3);
108
- background: rgba(22, 163, 74, 0.12);
109
- }
110
- .status-badge.disabled {
111
- color: var(--badge-off);
112
- border-color: rgba(220, 38, 38, 0.3);
113
- background: rgba(220, 38, 38, 0.12);
114
- }
115
- .actions {
116
- display: flex;
117
- gap: 10px;
118
- flex-wrap: wrap;
119
- }
120
- .btn {
121
- border: none;
122
- border-radius: 10px;
123
- padding: 10px 16px;
124
- font-weight: 600;
125
- cursor: pointer;
126
- transition: transform 0.05s ease, opacity 0.15s ease;
127
- }
128
- .btn:disabled {
129
- opacity: 0.6;
130
- cursor: not-allowed;
131
- }
132
- .btn:active {
133
- transform: translateY(1px);
134
- }
135
- .btn-primary {
136
- background: var(--btn);
137
- color: var(--btn-text);
138
- }
139
- .btn-secondary {
140
- background: var(--btn-secondary);
141
- color: var(--btn-secondary-text);
142
- }
143
- .note {
144
- margin-top: 16px;
145
- font-size: 13px;
146
- color: var(--muted);
147
- }
148
- .message {
149
- margin-top: 10px;
150
- font-size: 13px;
151
- min-height: 18px;
152
- }
153
- .message.success {
154
- color: var(--badge-on);
155
- }
156
- .message.error {
157
- color: var(--badge-off);
158
- }
159
- </style>
160
- </head>
161
- <body class="<%= theme %>" data-agent="<%= agent %>">
162
- <div class="card">
163
- <div class="title">Registry Beta</div>
164
- <div class="subtitle">Enable or disable registry publish features for this Pinokio install.</div>
165
- <div class="status-row">
166
- <div class="status-label">Status</div>
167
- <div
168
- id="registry-beta-status"
169
- class="status-badge <%= registryBetaEnabled ? 'enabled' : 'disabled' %>"
170
- data-enabled="<%= registryBetaEnabled ? '1' : '0' %>"
171
- >
172
- <%= registryBetaEnabled ? 'Enabled' : 'Disabled' %>
173
- </div>
174
- </div>
175
- <div class="field">
176
- <label for="registry-connect-url">Registry connect URL</label>
177
- <input
178
- id="registry-connect-url"
179
- type="url"
180
- placeholder="http://localhost:3000/connect/pinokio"
181
- value="<%= registryUrlPrefill || registryConnectUrl || '' %>"
182
- autocomplete="off"
183
- />
184
- <div class="field-hint">Used for connect prompts and published links.</div>
185
- </div>
186
- <div class="actions">
187
- <button id="registry-beta-enable" class="btn btn-primary" type="button">Enable</button>
188
- <button id="registry-beta-disable" class="btn btn-secondary" type="button">Disable</button>
189
- </div>
190
- <div class="note">Writes PINOKIO_REGISTRY_BETA and PINOKIO_REGISTRY_CONNECT_URL in ENVIRONMENT. Reload other pages to see changes.</div>
191
- <div id="registry-beta-message" class="message" aria-live="polite"></div>
192
- </div>
193
- <script>
194
- document.addEventListener("DOMContentLoaded", () => {
195
- const statusEl = document.getElementById("registry-beta-status")
196
- const messageEl = document.getElementById("registry-beta-message")
197
- const enableBtn = document.getElementById("registry-beta-enable")
198
- const disableBtn = document.getElementById("registry-beta-disable")
199
- const connectInput = document.getElementById("registry-connect-url")
200
- if (!statusEl || !enableBtn || !disableBtn) return
201
-
202
- let enabled = statusEl.getAttribute("data-enabled") === "1"
203
-
204
- const setMessage = (text, isError) => {
205
- if (!messageEl) return
206
- messageEl.textContent = text || ""
207
- messageEl.classList.toggle("error", !!isError)
208
- messageEl.classList.toggle("success", !isError && !!text)
209
- }
210
-
211
- const setState = (next) => {
212
- enabled = !!next
213
- statusEl.textContent = enabled ? "Enabled" : "Disabled"
214
- statusEl.setAttribute("data-enabled", enabled ? "1" : "0")
215
- statusEl.classList.toggle("enabled", enabled)
216
- statusEl.classList.toggle("disabled", !enabled)
217
- enableBtn.textContent = enabled ? "Save" : "Enable"
218
- enableBtn.disabled = false
219
- disableBtn.disabled = !enabled
220
- }
221
-
222
- const update = async (next, includeEndpoint) => {
223
- setMessage("")
224
- enableBtn.disabled = true
225
- disableBtn.disabled = true
226
- try {
227
- const connectUrl = connectInput ? String(connectInput.value || "").trim() : ""
228
- const res = await fetch("/checkpoints/registry_beta", {
229
- method: "POST",
230
- headers: {
231
- "Content-Type": "application/json",
232
- "Accept": "application/json"
233
- },
234
- body: JSON.stringify({
235
- enabled: next ? "1" : "0",
236
- ...(includeEndpoint ? { connectUrl } : {})
237
- })
238
- })
239
- const payload = res.ok ? await res.json().catch(() => null) : null
240
- if (!payload || !payload.ok) {
241
- throw new Error("Update failed")
242
- }
243
- setState(!!payload.enabled)
244
- setMessage("Updated.")
245
- } catch (_) {
246
- setState(enabled)
247
- setMessage("Could not update setting.", true)
248
- } finally {
249
- enableBtn.disabled = enabled
250
- disableBtn.disabled = !enabled
251
- }
252
- }
253
-
254
- setState(enabled)
255
- enableBtn.addEventListener("click", () => update(true, true))
256
- disableBtn.addEventListener("click", () => update(false, true))
257
- })
258
- </script>
259
- </body>
260
- </html>