@octonodes/cli 0.2.38 → 0.2.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.
Files changed (56) hide show
  1. package/README.md +32 -2
  2. package/dist/apps/build.d.ts +30 -30
  3. package/dist/apps/build.d.ts.map +1 -1
  4. package/dist/apps/build.js.map +1 -1
  5. package/dist/apps/cloudflared.js.map +1 -1
  6. package/dist/apps/constants.d.ts +5 -1
  7. package/dist/apps/constants.d.ts.map +1 -1
  8. package/dist/apps/constants.js +7 -2
  9. package/dist/apps/constants.js.map +1 -1
  10. package/dist/apps/create.d.ts +1 -1
  11. package/dist/apps/create.d.ts.map +1 -1
  12. package/dist/apps/create.js +15 -27
  13. package/dist/apps/create.js.map +1 -1
  14. package/dist/apps/dev.d.ts +6 -6
  15. package/dist/apps/dev.js.map +1 -1
  16. package/dist/apps/development.js.map +1 -1
  17. package/dist/apps/index.d.ts.map +1 -1
  18. package/dist/apps/index.js +2 -1
  19. package/dist/apps/index.js.map +1 -1
  20. package/dist/apps/publish.js.map +1 -1
  21. package/dist/apps/server.js.map +1 -1
  22. package/dist/apps/source.d.ts +116 -116
  23. package/dist/apps/source.d.ts.map +1 -1
  24. package/dist/apps/source.js +1 -1
  25. package/dist/apps/source.js.map +1 -1
  26. package/dist/apps/tunnel-lease.js.map +1 -1
  27. package/dist/apps/tunnel.js.map +1 -1
  28. package/dist/apps/welcome.d.ts +2 -2
  29. package/dist/apps/welcome.d.ts.map +1 -1
  30. package/dist/apps/welcome.js +529 -13
  31. package/dist/apps/welcome.js.map +1 -1
  32. package/dist/auth.d.ts +6 -0
  33. package/dist/auth.d.ts.map +1 -1
  34. package/dist/auth.js +20 -2
  35. package/dist/auth.js.map +1 -1
  36. package/dist/connect.js.map +1 -1
  37. package/dist/index.js +9 -1
  38. package/dist/index.js.map +1 -1
  39. package/dist/plugins/app.js.map +1 -1
  40. package/dist/plugins/artifact.d.ts.map +1 -1
  41. package/dist/plugins/artifact.js.map +1 -1
  42. package/dist/plugins/build.js.map +1 -1
  43. package/dist/plugins/constants.js.map +1 -1
  44. package/dist/plugins/create.js.map +1 -1
  45. package/dist/plugins/deploy.js.map +1 -1
  46. package/dist/plugins/index.js.map +1 -1
  47. package/dist/plugins/library.js.map +1 -1
  48. package/dist/plugins/publish.d.ts +116 -116
  49. package/dist/plugins/publish.d.ts.map +1 -1
  50. package/dist/plugins/publish.js.map +1 -1
  51. package/dist/plugins/release.d.ts +11 -11
  52. package/dist/plugins/release.js.map +1 -1
  53. package/dist/terminal.d.ts.map +1 -1
  54. package/dist/terminal.js.map +1 -1
  55. package/package.json +6 -4
  56. package/runtime/plugin-lifecycle.cjs +17052 -16676
@@ -1,21 +1,453 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.appCss = exports.welcomeCss = void 0;
3
+ exports.appCss = void 0;
4
4
  exports.welcomeHtml = welcomeHtml;
5
5
  exports.welcomeReact = welcomeReact;
6
- const logoSrc = "data:image/svg+xml," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none"><rect width="64" height="64" rx="16" fill="#181721"/><path d="M24 10h16l14 14v16L40 54H24L10 40V24Z" stroke="#FF765F" stroke-linejoin="round" stroke-width="3.5"/><path d="m21 24 11 8 11-8M32 32v13" stroke="#FF765F" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/><g fill="#FF765F"><circle cx="21" cy="24" r="3"/><circle cx="43" cy="24" r="3"/><circle cx="32" cy="32" r="4"/><circle cx="32" cy="45" r="3"/></g></svg>');
7
- exports.welcomeCss = `:root{font-family:"Avenir Next",Avenir,ui-sans-serif,system-ui,sans-serif;background:#f7f8fa;color:#181721}*{box-sizing:border-box}body{margin:0;min-width:320px}a{color:inherit;text-decoration:none}a:focus-visible{outline:3px solid #ff765f;outline-offset:4px}.shell{max-width:1120px;margin:auto;padding:0 32px}.top{height:82px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid #e5e1e0}.brand{display:flex;align-items:center;gap:10px;font-size:21px;font-weight:800;letter-spacing:-.05em}.mark{display:grid;place-items:center;width:32px;height:32px;border-radius:10px;background:#ff765f;color:white;box-shadow:4px 4px 0 #ffd5cc}.top a{font-size:14px;font-weight:700;color:#923a2a}.hero{min-height:calc(100vh - 82px);display:grid;grid-template-columns:minmax(0,1.1fr) minmax(310px,.9fr);gap:64px;align-items:center;padding-top:64px;padding-bottom:64px}.eyebrow{display:flex;align-items:center;gap:9px;color:#923a2a;font-size:14px;font-weight:700}.eyebrow:before{content:"";width:8px;height:8px;border-radius:50%;background:#ff765f}h1{font-size:clamp(48px,5.4vw,76px);line-height:1.02;letter-spacing:-.075em;margin:20px 0 24px}h1 span{color:#dc5841}.intro{font-size:18px;line-height:1.6;color:#5d5a60;max-width:550px}.actions{display:flex;gap:12px;flex-wrap:wrap;margin-top:32px}.button{border-radius:12px;background:#181721;color:white;padding:14px 20px;font-weight:700;box-shadow:0 7px 18px #1817212a}.button.secondary{background:white;color:#181721;border:1px solid #e2dedf;box-shadow:none}.steps{margin-top:32px;color:#666169;font-size:13px;line-height:1.7}.steps code{color:#923a2a;background:#fff0ed;padding:3px 6px;border-radius:5px}.stage{position:relative;min-height:380px;border:1px solid #e5e1e0;border-radius:24px;background:linear-gradient(155deg,#fff,#fff3ef);padding:28px;box-shadow:0 24px 56px #18172118;overflow:hidden}.stage:before{content:"";position:absolute;inset:0;background-image:linear-gradient(#8c6a5b12 1px,transparent 1px),linear-gradient(90deg,#8c6a5b12 1px,transparent 1px);background-size:28px 28px;mask-image:linear-gradient(to bottom,#000,transparent)}.stage>*{position:relative}.stage-title{display:flex;justify-content:space-between;color:#53607d;font-size:13px;font-weight:700}.flow{display:flex;align-items:center;gap:8px;margin:75px 0 65px}.node{width:115px;min-height:96px;flex:none;background:#fff;border:1px solid #e6dfdd;border-radius:16px;padding:14px;box-shadow:0 8px 20px #18172116}.node.main{border-color:#ff9b89;box-shadow:0 10px 24px #dc58412b}.node-symbol{font-size:22px;color:#e36852}.node strong{display:block;margin-top:14px;font-size:13px}.wire{height:2px;min-width:12px;flex:1;background:#c8aaa4;position:relative}.wire:after{content:"";position:absolute;right:-2px;top:-4px;border-left:7px solid #c8aaa4;border-top:5px solid transparent;border-bottom:5px solid transparent}.connection{padding:16px 18px;background:#fff;border:1px solid #e9dfdc;border-radius:13px;display:flex;align-items:center;gap:11px;font-size:14px;line-height:1.4}.connection:before{content:"";width:10px;height:10px;flex:none;border-radius:50%;background:#ff765f}.connection span{overflow-wrap:anywhere}@media(max-width:850px){.hero{grid-template-columns:1fr;gap:45px}.stage{min-height:330px}.flow{margin:55px 0}}@media(max-width:500px){.shell{padding-left:20px;padding-right:20px}.top{height:70px}.hero{padding-top:45px}.stage{padding:18px;min-height:300px}.flow{margin:45px 0}.node{width:82px;padding:9px;min-height:84px}.node strong{font-size:11px}.intro{font-size:16px}}`;
6
+ exports.welcomeHosted = welcomeHosted;
7
+ // Static artwork is emitted as HTML or native JSX; it has no runtime fixture dependency.
8
+ const projectScene = `<svg class="scene" viewBox="0 0 480 400" fill="none" aria-hidden="true">
9
+ <ellipse cx="240" cy="212" rx="188" ry="152" stroke="var(--line)" stroke-dasharray="2 10" />
10
+ <path
11
+ class="route"
12
+ d="M100 100H175Q195 100 195 120V174M334 104V157Q334 177 314 177H286M312 265V307Q312 327 332 327H374"
13
+ />
14
+ <path class="signal" d="M100 100H175Q195 100 195 120V174" />
15
+ <path class="signal" d="M334 104V157Q334 177 314 177H286" />
16
+ <path class="signal" d="M312 265V307Q312 327 332 327H374" />
17
+ <g class="satellite">
18
+ <rect class="paper" x="48" y="72" width="88" height="66" rx="16" />
19
+ <rect x="66" y="91" width="23" height="23" rx="6" fill="var(--paper)" />
20
+ <path d="M72 102h11m-11 5h7" stroke="var(--soft)" stroke-width="2" stroke-linecap="round" />
21
+ <path d="M98 98h20m-20 10h14" stroke="var(--line)" stroke-width="3" stroke-linecap="round" />
22
+ </g>
23
+ <g class="satellite">
24
+ <rect class="paper" x="306" y="57" width="72" height="64" rx="16" />
25
+ <path d="M327 84h9l5 6h16v14h-30V84Z" stroke="var(--soft)" stroke-width="1.5" stroke-linejoin="round" />
26
+ </g>
27
+ <g class="satellite">
28
+ <rect class="paper" x="351" y="301" width="78" height="58" rx="16" />
29
+ <circle cx="390" cy="330" r="13" stroke="var(--soft)" stroke-width="1.5" />
30
+ <path
31
+ d="m384 330 4 4 8-8"
32
+ stroke="var(--soft)"
33
+ stroke-width="1.5"
34
+ stroke-linecap="round"
35
+ stroke-linejoin="round"
36
+ />
37
+ </g>
38
+ <g class="project project-back">
39
+ <rect class="back" x="147" y="125" width="196" height="158" rx="20" transform="rotate(8 245 204)" />
40
+ </g>
41
+ <g class="project project-front">
42
+ <rect class="paper" x="123" y="145" width="222" height="164" rx="20" />
43
+ <path
44
+ d="M145 173h19l9 10h28v29h-56v-39Z"
45
+ fill="var(--paper)"
46
+ stroke="var(--accent)"
47
+ stroke-width="1.5"
48
+ stroke-linejoin="round"
49
+ />
50
+ <text class="ink" x="145" y="245" font-size="18" font-weight="600">Projects</text>
51
+ <path d="M145 263h116m-116 14h79" stroke="var(--line)" stroke-width="4" stroke-linecap="round" />
52
+ <rect x="293" y="173" width="29" height="29" rx="9" fill="var(--paper)" />
53
+ <path
54
+ d="m302 187 4 4 8-8"
55
+ class="accent"
56
+ stroke-width="1.5"
57
+ stroke-linecap="round"
58
+ stroke-linejoin="round"
59
+ />
60
+ </g>
61
+ <circle cx="94" cy="252" r="5" fill="var(--soft)" opacity=".6" />
62
+ <circle cx="396" cy="201" r="4" fill="var(--accent)" opacity=".65" />
63
+ <circle cx="200" cy="356" r="3" fill="var(--soft)" opacity=".6" />
64
+ </svg>`;
65
+ const workspaceDetails = `<section class="details" aria-label="About your workspace">
66
+ <div class="detail">
67
+ <svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
68
+ <path d="M3 7h6l3 3h9v10H3V7Z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round" />
69
+ <path d="M3 7V4h7l3 3h6v3" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round" />
70
+ </svg>
71
+ <div>
72
+ <h2>Shared work, in one place</h2>
73
+ <p>Open Projects to explore the work shared with this app.</p>
74
+ </div>
75
+ </div>
76
+ <div class="detail">
77
+ <svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
78
+ <path
79
+ d="m12 3 8 3v6c0 5-8 9-8 9s-8-4-8-9V6l8-3Z"
80
+ stroke="currentColor"
81
+ stroke-width="1.5"
82
+ stroke-linejoin="round"
83
+ />
84
+ <path
85
+ d="m8 12 3 3 5-6"
86
+ stroke="currentColor"
87
+ stroke-width="1.5"
88
+ stroke-linecap="round"
89
+ stroke-linejoin="round"
90
+ />
91
+ </svg>
92
+ <div>
93
+ <h2>Access you control</h2>
94
+ <p>Only the projects and permissions granted to this app are available.</p>
95
+ </div>
96
+ </div>
97
+ </section>`;
98
+ const jsxAttributes = {
99
+ class: "className", "stroke-width": "strokeWidth", "stroke-linecap": "strokeLinecap",
100
+ "stroke-linejoin": "strokeLinejoin", "stroke-dasharray": "strokeDasharray",
101
+ "font-size": "fontSize", "font-weight": "fontWeight",
102
+ };
103
+ const asJsx = (markup) => markup.replace(/\b(class|stroke-width|stroke-linecap|stroke-linejoin|stroke-dasharray|font-size|font-weight)=/g, attribute => jsxAttributes[attribute.slice(0, -1)] + "=");
104
+ exports.appCss = `:root {
105
+ color-scheme: light;
106
+ --canvas: #f8f9fc;
107
+ --surface: #fff;
108
+ --ink: #252c43;
109
+ --muted: #647087;
110
+ --line: #dfe3ed;
111
+ --accent: #6158a6;
112
+ --accent-ink: #fff;
113
+ --paper: #eef0f8;
114
+ --soft: #8192b6;
115
+ }
116
+ @media (prefers-color-scheme: dark) {
117
+ :root {
118
+ color-scheme: dark;
119
+ --canvas: #17191f;
120
+ --surface: #20232d;
121
+ --ink: #f2f3f8;
122
+ --muted: #a5adbf;
123
+ --line: #363b4c;
124
+ --accent: #b7b2f0;
125
+ --accent-ink: #242039;
126
+ --paper: #292e3b;
127
+ --soft: #8e99b4;
128
+ }
129
+ }
130
+ * {
131
+ box-sizing: border-box;
132
+ }
133
+ body {
134
+ margin: 0;
135
+ min-width: 0;
136
+ background: var(--canvas);
137
+ color: var(--ink);
138
+ font:
139
+ 16px/1.6 ui-sans-serif,
140
+ system-ui,
141
+ -apple-system,
142
+ BlinkMacSystemFont,
143
+ "Segoe UI",
144
+ sans-serif;
145
+ -webkit-font-smoothing: antialiased;
146
+ }
147
+ .app-page {
148
+ max-width: 1240px;
149
+ margin: auto;
150
+ padding: clamp(24px, 5vw, 72px);
151
+ }
152
+ .context {
153
+ display: flex;
154
+ align-items: center;
155
+ justify-content: space-between;
156
+ gap: 16px;
157
+ color: var(--muted);
158
+ font-size: 13px;
159
+ }
160
+ .identity {
161
+ display: flex;
162
+ align-items: center;
163
+ gap: 9px;
164
+ }
165
+ .identity svg {
166
+ width: 20px;
167
+ height: 20px;
168
+ color: var(--accent);
169
+ }
170
+ .connection {
171
+ display: flex;
172
+ align-items: center;
173
+ gap: 8px;
174
+ }
175
+ .connection::before {
176
+ content: "";
177
+ width: 6px;
178
+ height: 6px;
179
+ border-radius: 50%;
180
+ background: #8ebfb0;
181
+ }
182
+ .welcome {
183
+ min-height: 520px;
184
+ display: grid;
185
+ grid-template-columns: 1fr 1fr;
186
+ align-items: center;
187
+ gap: clamp(24px, 5vw, 72px);
188
+ padding: 56px 0;
189
+ }
190
+ .app-page h1 {
191
+ margin: 0 0 24px;
192
+ max-width: 9em;
193
+ font-size: clamp(38px, 4.6vw, 64px);
194
+ line-height: 1.08;
195
+ letter-spacing: -0.045em;
196
+ font-weight: 650;
197
+ text-wrap: balance;
198
+ }
199
+ .intro {
200
+ margin: 0;
201
+ color: var(--muted);
202
+ max-width: 32em;
203
+ font-size: 17px;
204
+ line-height: 1.75;
205
+ }
206
+ .action {
207
+ display: inline-flex;
208
+ align-items: center;
209
+ justify-content: center;
210
+ gap: 14px;
211
+ margin-top: 32px;
212
+ padding: 13px 20px;
213
+ border-radius: 12px;
214
+ background: var(--accent);
215
+ color: var(--accent-ink);
216
+ font-size: 14px;
217
+ font-weight: 650;
218
+ text-decoration: none;
219
+ transition:
220
+ box-shadow 0.2s,
221
+ transform 0.2s;
222
+ }
223
+ .action svg {
224
+ width: 18px;
225
+ height: 18px;
226
+ }
227
+ .action:hover {
228
+ box-shadow: 0 6px 24px color-mix(in srgb, var(--accent) 20%, transparent);
229
+ transform: translateY(-2px);
230
+ }
231
+ .action:focus-visible {
232
+ outline: 2px solid var(--accent);
233
+ outline-offset: 5px;
234
+ }
235
+ .scene {
236
+ width: 100%;
237
+ max-width: 480px;
238
+ justify-self: center;
239
+ overflow: visible;
240
+ }
241
+ .scene .paper {
242
+ fill: var(--surface);
243
+ stroke: var(--line);
244
+ }
245
+ .scene .back {
246
+ fill: var(--paper);
247
+ stroke: var(--line);
248
+ }
249
+ .scene .ink {
250
+ fill: var(--ink);
251
+ }
252
+ .scene .muted {
253
+ fill: var(--muted);
254
+ }
255
+ .scene .accent {
256
+ stroke: var(--accent);
257
+ }
258
+ .scene .route {
259
+ fill: none;
260
+ stroke: var(--line);
261
+ stroke-width: 1.5;
262
+ }
263
+ .scene .signal {
264
+ fill: none;
265
+ stroke: var(--accent);
266
+ stroke-width: 2;
267
+ stroke-linecap: round;
268
+ stroke-dasharray: 18 240;
269
+ animation: connect 1.5s ease-in-out 3;
270
+ }
271
+ .scene .project {
272
+ transform-origin: 240px 210px;
273
+ animation: unfold 1.1s cubic-bezier(0.2, 0.75, 0.25, 1) both;
274
+ }
275
+ .scene .project-back {
276
+ animation-delay: 0.08s;
277
+ }
278
+ .scene .project-front {
279
+ animation-delay: 0.18s;
280
+ }
281
+ .scene .satellite {
282
+ animation: arrive 0.9s ease-out 0.35s both;
283
+ }
284
+ [hidden] {
285
+ display: none !important;
286
+ }
287
+ .project-view {
288
+ min-height: 520px;
289
+ padding: 72px 0;
290
+ }
291
+ .details {
292
+ display: grid;
293
+ grid-template-columns: 1fr 1fr;
294
+ gap: 48px;
295
+ border-top: 1px solid var(--line);
296
+ padding-top: 32px;
297
+ }
298
+ .detail {
299
+ display: flex;
300
+ align-items: flex-start;
301
+ gap: 16px;
302
+ }
303
+ .detail svg {
304
+ margin-top: 4px;
305
+ flex-shrink: 0;
306
+ width: 22px;
307
+ height: 22px;
308
+ color: var(--soft);
309
+ }
310
+ .app-page h2 {
311
+ margin: 0 0 6px;
312
+ font-size: 15px;
313
+ font-weight: 600;
314
+ }
315
+ .detail p {
316
+ margin: 0;
317
+ max-width: 30em;
318
+ color: var(--muted);
319
+ font-size: 13px;
320
+ line-height: 1.7;
321
+ }
322
+ @keyframes unfold {
323
+ from {
324
+ opacity: 0;
325
+ transform: translateY(22px) rotate(-4deg);
326
+ }
327
+ to {
328
+ opacity: 1;
329
+ transform: translateY(0) rotate(0);
330
+ }
331
+ }
332
+ @keyframes arrive {
333
+ from {
334
+ opacity: 0;
335
+ transform: translateY(12px);
336
+ }
337
+ to {
338
+ opacity: 1;
339
+ transform: translateY(0);
340
+ }
341
+ }
342
+ @keyframes connect {
343
+ from {
344
+ stroke-dashoffset: 250;
345
+ opacity: 0;
346
+ }
347
+ 20%,
348
+ 70% {
349
+ opacity: 1;
350
+ }
351
+ to {
352
+ stroke-dashoffset: 0;
353
+ opacity: 0;
354
+ }
355
+ }
356
+ @media (max-width: 760px) {
357
+ .welcome {
358
+ min-height: 0;
359
+ grid-template-columns: 1fr;
360
+ gap: 16px;
361
+ padding: 48px 0 24px;
362
+ }
363
+ .app-page h1 {
364
+ max-width: 12em;
365
+ }
366
+ .scene {
367
+ max-width: 380px;
368
+ }
369
+ .details {
370
+ grid-template-columns: 1fr;
371
+ gap: 24px;
372
+ }
373
+ }
374
+ @media (max-width: 380px) {
375
+ .context {
376
+ font-size: 12px;
377
+ }
378
+ .connection {
379
+ display: none;
380
+ }
381
+ }
382
+ @media (prefers-reduced-motion: reduce) {
383
+ *,
384
+ *::before,
385
+ *::after {
386
+ animation: none !important;
387
+ transition: none !important;
388
+ }
389
+ .signal {
390
+ display: none;
391
+ }
392
+ }
393
+ @media (forced-colors: active) {
394
+ :root {
395
+ --canvas: Canvas;
396
+ --surface: Canvas;
397
+ --paper: Canvas;
398
+ --ink: CanvasText;
399
+ --muted: CanvasText;
400
+ --line: CanvasText;
401
+ --accent: LinkText;
402
+ --accent-ink: Canvas;
403
+ --soft: CanvasText;
404
+ }
405
+ .action {
406
+ border: 1px solid LinkText;
407
+ }
408
+ }
409
+
410
+ .app-navbar { display: flex; justify-content: space-between; align-items: center; gap: 16px; flex-wrap: wrap; padding: 16px 24px; border-bottom: 1px solid var(--line); }
411
+ .app-navbar nav { display: flex; gap: 8px; flex-wrap: wrap; }
412
+ .app-navbar a { color: var(--muted); text-decoration: none; padding: 8px 12px; border-radius: 8px; }
413
+ .app-navbar a[aria-current="page"] { background: var(--paper); color: var(--ink); }
414
+ .app-navbar a:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
415
+ .project-list { list-style: none; margin: 28px 0 0; padding: 0; border: 1px solid var(--line); border-radius: 16px; overflow: hidden; }
416
+ .project-list li { padding: 20px; background: var(--surface); border-bottom: 1px solid var(--line); }
417
+ .project-list li:last-child { border-bottom: 0; }
418
+ .project-list strong, .project-list small { display: block; overflow-wrap: anywhere; }
419
+ .project-list small { margin-top: 4px; color: var(--muted); }
420
+ .project-view p { color: var(--muted); }
421
+ .access-state { min-height: calc(100dvh - 144px); display: flex; align-items: center; justify-content: center; gap: 14px; margin: auto; text-align: center; }
422
+ .access-state[data-state="connecting"]::before { content: ""; width: 20px; height: 20px; flex-shrink: 0; border: 2px solid var(--line); border-top-color: var(--accent); border-radius: 50%; animation: spin 1s linear infinite; }
423
+ @keyframes spin { to { transform: rotate(360deg); } }
424
+ `;
8
425
  function welcomeHtml(name) {
9
- return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${name} · Octonode</title><style>${exports.welcomeCss}</style></head><body><header class="top shell"><div class="brand"><img class="mark" src="${logoSrc}" alt=""/>octonode</div><a href="https://playbook.octonodes.com/docs/apps">App guide ↗</a></header><main class="hero shell"><div><div class="eyebrow">Your app is running</div><h1>Hello, <span>world.</span></h1><p class="intro">A small beginning for something useful. Your page and backend are ready to shape.</p><div class="actions"><a class="button" href="https://playbook.octonodes.com/docs/apps">Build your app</a><a class="button secondary" href="https://octonodes.com/studio">Open Studio</a></div><p class="steps">Start in <code>src/server.ts</code> · Add optional contributions with <code>app extension add</code></p></div><div class="stage"><div class="stage-title"><span>Connection map</span><span>${name}</span></div><div class="flow"><div class="node"><span class="node-symbol">◇</span><strong>Studio</strong></div><div class="wire"></div><div class="node main"><span class="node-symbol">◆</span><strong>Your app</strong></div><div class="wire"></div><div class="node"><span class="node-symbol">◈</span><strong>Backend</strong></div></div><div class="connection" role="status"><span id="message">Checking connection…</span></div></div></main><script>/* OCTONODE_HOSTED_BRIDGE */</script></body></html>`;
426
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${name} · Octonode</title><style>${exports.appCss}</style></head><body>
427
+ <main class="app-page">
428
+ <p class="intro access-state" id="message" role="status" data-state="connecting">Connecting to your workspace…</p>
429
+ <div id="workspace-content" hidden>
430
+ <header class="context"><span class="identity">Your workspace</span><span class="connection">App connected</span></header>
431
+ <div id="overview-view">
432
+ <section class="welcome" aria-labelledby="overview-title">
433
+ <div><h1 id="overview-title">Workspace overview</h1><p class="intro">Your shared projects appear here.<br />A little clarity for everything you’re working on.</p><a class="action" href="/projects" id="explore">Explore projects <span aria-hidden="true">→</span></a></div>
434
+ ${projectScene}
435
+ </section>
436
+ ${workspaceDetails}
437
+ </div>
438
+ <section class="project-view" id="projects-view" hidden aria-labelledby="projects-title">
439
+ <h1 id="projects-title">Projects</h1><p id="projects-status" role="status">Loading granted projects…</p><ul class="project-list" id="project-list"></ul><a class="action" href="/" id="return">Back to overview</a>
440
+ </section>
441
+ </div>
442
+ </main><script>/* OCTONODE_HOSTED_BRIDGE */</script></body></html>`;
10
443
  }
11
- exports.appCss = `:root{font-family:Inter,ui-sans-serif,system-ui,sans-serif;color:#181721;background:#fff}*{box-sizing:border-box}body{margin:0;min-width:320px}.app-navbar{display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap;padding:16px 24px;border-bottom:1px solid #e4e6ea}.app-navbar nav{display:flex;gap:8px;flex-wrap:wrap}.app-navbar a{color:#555b67;text-decoration:none;padding:8px 12px;border-radius:8px}.app-navbar a[aria-current=page]{background:#eceef1;color:#181721}.app-navbar a:focus-visible{outline:2px solid #b65b49}.app-page{max-width:920px;padding:44px clamp(20px,5vw,64px)}h1{font-size:clamp(28px,3vw,40px);letter-spacing:-.04em;line-height:1.15;margin:0 0 12px}p{color:#5f6470;line-height:1.6}.eyebrow{font-size:13px;font-weight:700;color:#92513f;margin:0 0 12px}.project-list{list-style:none;margin:28px 0 0;padding:0;border:1px solid #e4e6ea;border-radius:12px;overflow:hidden}.project-list li{padding:16px 20px;border-bottom:1px solid #e4e6ea}.project-list li:last-child{border-bottom:0}.project-list strong{display:block}.project-list small{display:block;margin-top:4px;color:#5f6470;overflow-wrap:anywhere}`;
12
444
  function welcomeReact(name) {
13
445
  return `"use client";
14
446
  import { useEffect, useState } from "react";
15
447
  import { OctonodeAppProvider, useOctonodeApp } from "@octonodes/ui-extensions/app/react";
16
448
  const navigation = [{ label: "Overview", path: "/" }, { label: "Projects", path: "/projects" }];
17
449
  function WorkspacePage({ showNavbar }: { showNavbar: boolean }) {
18
- const { bridge, session, workspace } = useOctonodeApp();
450
+ const { bridge, session } = useOctonodeApp();
19
451
  const [projects, setProjects] = useState<{ id: string; name: string }[]>([]);
20
452
  const [status, setStatus] = useState("Loading granted projects…");
21
453
  useEffect(() => {
@@ -30,19 +462,103 @@ function WorkspacePage({ showNavbar }: { showNavbar: boolean }) {
30
462
  }).catch(error => { if (!controller.signal.aborted) setStatus(error.message); });
31
463
  return () => controller.abort();
32
464
  }, [bridge, session.token]);
33
- const projectsPage = session.path.split("?")[0] === "/projects";
465
+ const projectsPage = session.path.split(/[?#]/)[0] === "/projects";
466
+ const navigate = (event: React.MouseEvent<HTMLAnchorElement>, path: string) => {
467
+ if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
468
+ event.preventDefault(); bridge.navigate(path);
469
+ };
34
470
  return <>
35
- {showNavbar && <header className="app-navbar"><strong>${name}</strong><nav aria-label="App navigation">{navigation.map(item => <a key={item.path} href={item.path} aria-current={session.path.split("?")[0] === item.path ? "page" : undefined} onClick={event => { if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return; event.preventDefault(); bridge.navigate(item.path); }}>{item.label}</a>)}</nav></header>}
471
+ {showNavbar && <header className="app-navbar"><strong>${name}</strong><nav aria-label="App navigation">{navigation.map(item => <a key={item.path} href={item.path} aria-current={session.path.split(/[?#]/)[0] === item.path ? "page" : undefined} onClick={event => navigate(event, item.path)}>{item.label}</a>)}</nav></header>}
36
472
  <main className="app-page">
37
- <p className="eyebrow">${name}</p>
38
- <h1>{projectsPage ? "Projects" : "Workspace overview"}</h1>
39
- <p>Connected to {workspace.kind}:{workspace.id}</p>
40
- {projectsPage && <><p role="status">{status}</p><ul className="project-list">{projects.map(project => <li key={project.id}><strong>{project.name}</strong><small>{project.id}</small></li>)}</ul></>}
41
- </main></>;
473
+ <header className="context"><span className="identity">Your workspace</span><span className="connection">App connected</span></header>
474
+ {projectsPage ? <section className="project-view" aria-labelledby="projects-title">
475
+ <h1 id="projects-title">Projects</h1><p role="status">{status}</p>
476
+ {projects.length > 0 && <ul className="project-list">{projects.map(project => <li key={project.id}><strong>{project.name}</strong><small>{project.id}</small></li>)}</ul>}
477
+ <a className="action" href="/" onClick={event => navigate(event, "/")}>Back to overview</a>
478
+ </section> : <>
479
+ <section className="welcome" aria-labelledby="overview-title">
480
+ <div><h1 id="overview-title">Workspace overview</h1><p className="intro">Your shared projects appear here.<br />A little clarity for everything you’re working on.</p><a className="action" href="/projects" onClick={event => navigate(event, "/projects")}>Explore projects <span aria-hidden="true">→</span></a></div>
481
+ ${asJsx(projectScene)}
482
+ </section>
483
+ ${asJsx(workspaceDetails)}
484
+ </>}
485
+ </main>
486
+ </>;
42
487
  }
43
488
  export default function App({ showNavbar = false }: { showNavbar?: boolean }) {
44
489
  return <OctonodeAppProvider navigation={navigation}><WorkspacePage showNavbar={showNavbar} /></OctonodeAppProvider>;
45
490
  }
46
491
  `;
47
492
  }
493
+ function welcomeHosted() {
494
+ return `import { connectHostedApp } from "@octonodes/ui-extensions/app";
495
+ const app = connectHostedApp();
496
+ app.setNavigation([{ label: "Overview", path: "/" }, { label: "Projects", path: "/projects" }]);
497
+ const output = document.getElementById("message")!;
498
+ const content = document.getElementById("workspace-content")!;
499
+ const overview = document.getElementById("overview-view")!;
500
+ const projectsView = document.getElementById("projects-view")!;
501
+ const projectStatus = document.getElementById("projects-status")!;
502
+ const list = document.getElementById("project-list")!;
503
+ let token: string | undefined;
504
+ let controller: AbortController | undefined;
505
+ const update = () => {
506
+ const current = app.getSnapshot();
507
+ const projectsPage = current.path.split(/[?#]/)[0] === "/projects";
508
+ overview.hidden = projectsPage;
509
+ projectsView.hidden = !projectsPage;
510
+ if (current.status !== "ready") {
511
+ controller?.abort();
512
+ token = undefined;
513
+ content.hidden = true;
514
+ output.hidden = false;
515
+ output.dataset.state = current.status;
516
+ output.textContent = current.status === "expired" ? "Session expired. Reopen the app from Studio." : current.status === "standalone" ? "Open this app from Studio to connect your workspace." : "Connecting to your workspace…";
517
+ return;
518
+ }
519
+ if (current.token === token) return;
520
+ token = current.token;
521
+ controller?.abort();
522
+ const request = new AbortController();
523
+ controller = request;
524
+ content.hidden = true;
525
+ output.hidden = false;
526
+ output.dataset.state = "connecting";
527
+ output.textContent = "Connecting to your workspace…";
528
+ list.replaceChildren();
529
+ projectStatus.textContent = "Loading granted projects…";
530
+ app.fetch("/api/context", { signal: request.signal }).then(async response => {
531
+ if (!response.ok) throw Error("Workspace access could not be verified.");
532
+ const data = await response.json();
533
+ if (!data.workspace?.kind || !data.workspace.id) throw Error("Missing workspace scope.");
534
+ if (request.signal.aborted) return;
535
+ output.hidden = true;
536
+ content.hidden = false;
537
+ try {
538
+ const response = await app.fetch("/api/projects", { signal: request.signal });
539
+ if (!response.ok) throw Error(response.status === 403 ? "Project access was not granted." : "Project service is unavailable. Try again.");
540
+ const data = await response.json();
541
+ if (request.signal.aborted) return;
542
+ for (const project of data.projects as { id: string; name: string }[]) {
543
+ const item = document.createElement("li");
544
+ const title = document.createElement("strong");
545
+ const id = document.createElement("small");
546
+ title.textContent = project.name; id.textContent = project.id;
547
+ item.append(title, id); list.append(item);
548
+ }
549
+ projectStatus.textContent = data.failed ? "Some projects could not be loaded." : data.projects.length ? "Connected to selected projects." : "No projects granted. Review access in Studio.";
550
+ } catch (error) { if (!request.signal.aborted) projectStatus.textContent = error instanceof Error ? error.message : "Could not load projects."; }
551
+ }).catch(error => { if (!request.signal.aborted) { output.dataset.state = "error"; output.textContent = error.message; } });
552
+ };
553
+ for (const [id, path] of [["explore", "/projects"], ["return", "/"]]) {
554
+ document.getElementById(id)!.addEventListener("click", event => {
555
+ if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
556
+ event.preventDefault(); app.navigate(path);
557
+ });
558
+ }
559
+ const unsubscribe = app.subscribe(update);
560
+ update();
561
+ addEventListener("pagehide", event => { if (event.persisted) return; controller?.abort(); unsubscribe(); app.dispose(); });
562
+ `;
563
+ }
48
564
  //# sourceMappingURL=welcome.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"welcome.js","sourceRoot":"","sources":["../../src/apps/welcome.ts"],"names":[],"mappings":";;;;;AAAA,MAAM,OAAO,GAAG,qBAAqB,GAAG,kBAAkB,CAAC,0fAA0f,CAAC,CAAC;AAE1iB,QAAA,UAAU,GAAG,+8GAA+8G,CAAC;AAE1+G,qBAA4B,IAAY;IACtC,OAAO,wIAAwI,IAAI,6BAA6B,QAAA,UAAU,4FAA4F,OAAO,qsBAAqsB,IAAI,ofAAof,CAAC;AAC79C,CAAC;AAEY,QAAA,MAAM,GAAG,4nCAA4nC,CAAC;AAEnpC,sBAA6B,IAAY;IACvC,OAAO;;;;;;;;;;;;;;;;;;;;;;4DAsBmD,IAAI;;6BAEnC,IAAI;;;;;;;;;CAShC,CAAC;AACF,CAAC"}
1
+ {"version":3,"file":"welcome.js","sourceRoot":"","sources":["../../src/apps/welcome.ts"],"names":[],"mappings":";;;AAoaA,kCAkBC;AAED,oCAgDC;AAED,sCAsEC;AAhjBD,yFAAyF;AACzF,MAAM,YAAY,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAwDd,CAAC;AACR,MAAM,gBAAgB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAgCd,CAAC;AACZ,MAAM,aAAa,GAA2B;IAC5C,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,aAAa,EAAE,gBAAgB,EAAE,eAAe;IACpF,iBAAiB,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,iBAAiB;IAC1E,WAAW,EAAE,UAAU,EAAE,aAAa,EAAE,YAAY;CACrD,CAAC;AACF,MAAM,KAAK,GAAG,CAAC,MAAc,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,gGAAgG,EAAE,SAAS,CAAC,EAAE,CAAC,aAAa,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC;AAEhM,QAAA,MAAM,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgUrB,CAAC;AAEF,SAAgB,WAAW,CAAC,IAAY;IACtC,OAAO,wIAAwI,IAAI,6BAA6B,cAAM;;;;;;;;YAQ5K,YAAY;;UAEd,gBAAgB;;;;;;qEAM2C,CAAC;AACtE,CAAC;AAED,SAAgB,YAAY,CAAC,IAAY;IACvC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;4DA0BmD,IAAI;;;;;;;;;;YAUpD,KAAK,CAAC,YAAY,CAAC;;UAErB,KAAK,CAAC,gBAAgB,CAAC;;;;;;;;CAQhC,CAAC;AACF,CAAC;AAED,SAAgB,aAAa;IAC3B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoER,CAAC;AACF,CAAC"}
package/dist/auth.d.ts CHANGED
@@ -5,6 +5,12 @@ export interface LoginOptions {
5
5
  openBrowser?: (url: string) => Promise<void>;
6
6
  }
7
7
  export declare function login(apiUrl: string, options?: LoginOptions): Promise<string>;
8
+ export declare function whoami(): Promise<{
9
+ userId: string;
10
+ email: string | undefined;
11
+ providers: string[];
12
+ workspace: string;
13
+ }>;
8
14
  export declare function accessToken(): Promise<string | undefined>;
9
15
  export declare function logout(): Promise<{
10
16
  removed: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAqBA,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9C;AAwPD,wBAAsB,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAkCvF;AAED,wBAAsB,WAAW,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAwB/D;AAED,wBAAsB,MAAM,IAAI,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAkB9E"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAqBA,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9C;AAwPD,wBAAsB,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAkCvF;AAED,wBAAsB,MAAM;;;;;GAe3B;AAED,wBAAsB,WAAW,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAwB/D;AAED,wBAAsB,MAAM,IAAI,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAkB9E"}
package/dist/auth.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.login = login;
4
+ exports.whoami = whoami;
4
5
  exports.accessToken = accessToken;
5
6
  exports.logout = logout;
6
7
  const node_child_process_1 = require("node:child_process");
@@ -254,7 +255,7 @@ async function browserLogin(config, launch) {
254
255
  throw new Error("Supabase did not return a session");
255
256
  saveSession(config, exchanged.data.session);
256
257
  removeFile("access-token");
257
- return `Finished octonodes login as ${exchanged.data.user?.email ?? "a Studio user"}.`;
258
+ return `Finished octonodes login as ${exchanged.data.user?.email ?? "a Studio user"}.\nPersonal workspace: user:${exchanged.data.session.user.id}`;
258
259
  }
259
260
  finally {
260
261
  callback.close();
@@ -294,7 +295,24 @@ async function login(apiUrl, options = {}) {
294
295
  throw new Error("Supabase did not return a session");
295
296
  saveSession(config, verified.data.session);
296
297
  removeFile("access-token");
297
- return `Finished octonodes login as ${verified.data.user?.email ?? email}.`;
298
+ return `Finished octonodes login as ${verified.data.user?.email ?? email}.\nPersonal workspace: user:${verified.data.session.user.id}`;
299
+ }
300
+ async function whoami() {
301
+ if (process.env.OCTONODE_TOKEN?.trim() || readFile("access-token"))
302
+ throw new Error("API token authentication is active; run octonodes login and unset OCTONODE_TOKEN to inspect a Studio user");
303
+ const stored = readSession();
304
+ if (!stored)
305
+ throw new Error("Run octonodes login before inspecting your Studio user");
306
+ const result = await authClient(stored).getUser(await accessToken());
307
+ if (result.error || !result.data.user)
308
+ throw new Error(`Could not verify Studio user${result.error ? `: ${result.error.message}` : ""}; run octonodes login again`);
309
+ const user = result.data.user;
310
+ return {
311
+ userId: user.id,
312
+ email: user.email,
313
+ providers: [...new Set(user.identities?.map((identity) => identity.provider) ?? [])],
314
+ workspace: `user:${user.id}`,
315
+ };
298
316
  }
299
317
  async function accessToken() {
300
318
  const environmentToken = process.env.OCTONODE_TOKEN?.trim();