@juuno-sdk/cli 1.0.12 → 2.0.0

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 (38) hide show
  1. package/README.md +5 -3
  2. package/bin/cli-router.js +54 -72
  3. package/bin/cli.js +2 -3
  4. package/dist/assets/main-GRbDNSIa.css +1 -0
  5. package/dist/assets/main-TDlmPBlt.js +1 -0
  6. package/dist/assets/main-rFvAfxK4.js +1 -0
  7. package/dist/cli/src/auth/index.js +47 -34
  8. package/dist/cli/src/deploy/index.js +209 -115
  9. package/dist/cli/src/dev/index.js +46 -40
  10. package/dist/cli/src/info/index.js +12 -36
  11. package/dist/cli/src/list/index.js +12 -36
  12. package/dist/cli/src/login/index.js +37 -42
  13. package/dist/cli/src/logout/index.js +34 -33
  14. package/dist/cli/src/simulator/index.js +54 -39
  15. package/dist/cli/src/utils/api.js +45 -0
  16. package/dist/cli/src/utils/error.js +9 -0
  17. package/dist/cli/src/utils/is-record.js +11 -0
  18. package/dist/cli/src/whoami/index.js +37 -33
  19. package/dist/cli/tsconfig.build.tsbuildinfo +1 -1
  20. package/dist/index.html +5 -2
  21. package/dist/types/src/auth/index.d.ts +17 -0
  22. package/dist/types/src/auth/index.d.ts.map +1 -1
  23. package/dist/types/src/deploy/index.d.ts +12 -0
  24. package/dist/types/src/deploy/index.d.ts.map +1 -1
  25. package/dist/types/src/dev/index.d.ts.map +1 -1
  26. package/dist/types/src/info/index.d.ts.map +1 -1
  27. package/dist/types/src/list/index.d.ts.map +1 -1
  28. package/dist/types/src/login/index.d.ts.map +1 -1
  29. package/dist/types/src/logout/index.d.ts.map +1 -1
  30. package/dist/types/src/simulator/index.d.ts.map +1 -1
  31. package/dist/types/src/utils/api.d.ts +27 -0
  32. package/dist/types/src/utils/api.d.ts.map +1 -0
  33. package/dist/types/src/utils/error.d.ts +8 -0
  34. package/dist/types/src/utils/error.d.ts.map +1 -0
  35. package/dist/types/src/utils/is-record.d.ts +10 -0
  36. package/dist/types/src/utils/is-record.d.ts.map +1 -0
  37. package/dist/types/src/whoami/index.d.ts.map +1 -1
  38. package/package.json +28 -26
package/README.md CHANGED
@@ -478,12 +478,14 @@ If you get authentication errors:
478
478
 
479
479
  ## Example
480
480
 
481
- See [@juuno-sdk/app-sdk-example](../app-sdk-example) for a complete working example.
481
+ See [`SKILL.md`](../app-sdk/SKILL.md) in `@juuno-sdk/app-sdk` for the full
482
+ workflow: creating an app, publishing a version, and upgrading the contract
483
+ version, with the preflight checks and failure modes for each.
482
484
 
483
485
  ## Related Packages
484
486
 
485
- - **@juuno-sdk/app-sdk**: SDK for building external apps
486
- - **@juuno-sdk/app-sdk-example**: Example external app demonstrating the workflow
487
+ - **@juuno-sdk/app-sdk**: SDK for building external apps, and the `SKILL.md`
488
+ walkthrough of the whole lifecycle
487
489
 
488
490
  ## Package Exports
489
491
 
package/bin/cli-router.js CHANGED
@@ -22,6 +22,56 @@ export function parseAuthOptions(args) {
22
22
  };
23
23
  }
24
24
 
25
+ /**
26
+ * Per-command dispatch table. adding a command is one entry.
27
+ */
28
+ const COMMANDS = {
29
+ login: async (args, importPrefix) => {
30
+ const { login } = await import(`${importPrefix}login/index.js`);
31
+ const auth = parseAuthOptions(args);
32
+ await login({
33
+ email: auth.email,
34
+ password: auth.password,
35
+ profile: auth.profile,
36
+ apiUrl: auth.apiUrl,
37
+ });
38
+ },
39
+ logout: async (args, importPrefix) => {
40
+ const { logout } = await import(`${importPrefix}logout/index.js`);
41
+ const auth = parseAuthOptions(args);
42
+ await logout({ profile: auth.profile, all: args.includes('--all') });
43
+ },
44
+ whoami: async (args, importPrefix) => {
45
+ const { whoami } = await import(`${importPrefix}whoami/index.js`);
46
+ const auth = parseAuthOptions(args);
47
+ await whoami({ profile: auth.profile, all: args.includes('--all') });
48
+ },
49
+ deploy: async (args, importPrefix) => {
50
+ const { deployApp } = await import(`${importPrefix}deploy/index.js`);
51
+ const buildDirIndex = args.indexOf('--build-dir');
52
+ const buildDir = buildDirIndex !== -1 ? args[buildDirIndex + 1] : './dist';
53
+ await deployApp({
54
+ buildDir,
55
+ skipIfDeployed: args.includes('--skip-if-deployed'),
56
+ ...parseAuthOptions(args),
57
+ });
58
+ },
59
+ list: async (args, importPrefix) => {
60
+ const { listApps } = await import(`${importPrefix}list/index.js`);
61
+ await listApps(parseAuthOptions(args));
62
+ },
63
+ info: async (args, importPrefix) => {
64
+ const { getAppInfo } = await import(`${importPrefix}info/index.js`);
65
+ const appId = args[1];
66
+ if (!appId || appId.startsWith('--')) {
67
+ console.error('❌ App ID is required');
68
+ console.error(' Usage: juuno-cli info <app-id> [options]');
69
+ process.exit(1);
70
+ }
71
+ await getAppInfo({ appId, ...parseAuthOptions(args) });
72
+ },
73
+ };
74
+
25
75
  /**
26
76
  * Execute a CLI command with the given import prefix.
27
77
  *
@@ -31,78 +81,10 @@ export function parseAuthOptions(args) {
31
81
  * @returns {Promise<number>} Exit code (0 for success, 1 for error)
32
82
  */
33
83
  export async function runCommand(command, args, importPrefix) {
34
- try {
35
- // Handle login command.
36
- if (command === 'login') {
37
- const { login } = await import(`${importPrefix}login/index.js`);
38
- const authOptions = parseAuthOptions(args);
39
- await login({
40
- email: authOptions.email,
41
- password: authOptions.password,
42
- profile: authOptions.profile,
43
- apiUrl: authOptions.apiUrl,
44
- });
45
- return 0;
46
- }
47
-
48
- // Handle logout command.
49
- if (command === 'logout') {
50
- const { logout } = await import(`${importPrefix}logout/index.js`);
51
- const authOptions = parseAuthOptions(args);
52
- const all = args.includes('--all');
53
- await logout({ profile: authOptions.profile, all });
54
- return 0;
55
- }
56
-
57
- // Handle whoami command.
58
- if (command === 'whoami') {
59
- const { whoami } = await import(`${importPrefix}whoami/index.js`);
60
- const authOptions = parseAuthOptions(args);
61
- const all = args.includes('--all');
62
- await whoami({ profile: authOptions.profile, all });
63
- return 0;
64
- }
65
-
66
- // Handle deploy command.
67
- if (command === 'deploy') {
68
- const { deployApp } = await import(`${importPrefix}deploy/index.js`);
69
- const buildDirIndex = args.indexOf('--build-dir');
70
- const buildDir =
71
- buildDirIndex !== -1 ? args[buildDirIndex + 1] : './dist';
72
- const skipIfDeployed = args.includes('--skip-if-deployed');
73
- const authOptions = parseAuthOptions(args);
74
- await deployApp({ buildDir, skipIfDeployed, ...authOptions });
75
- return 0;
76
- }
77
-
78
- // Handle list command.
79
- if (command === 'list') {
80
- const { listApps } = await import(`${importPrefix}list/index.js`);
81
- const authOptions = parseAuthOptions(args);
82
- await listApps(authOptions);
83
- return 0;
84
- }
85
-
86
- // Handle info command.
87
- if (command === 'info') {
88
- const { getAppInfo } = await import(`${importPrefix}info/index.js`);
89
- const appId = args[1];
90
-
91
- if (!appId || appId.startsWith('--')) {
92
- console.error('Error: App ID is required');
93
- console.error('Usage: juuno-cli info <app-id> [options]');
94
- return 1;
95
- }
96
-
97
- const authOptions = parseAuthOptions(args);
98
- await getAppInfo({ appId, ...authOptions });
99
- return 0;
100
- }
101
-
102
- // Command not recognized - return error code.
84
+ const handler = COMMANDS[command];
85
+ if (!handler) {
103
86
  return 1;
104
- } catch (error) {
105
- // Let errors propagate to the caller for proper handling.
106
- throw error;
107
87
  }
88
+ await handler(args, importPrefix);
89
+ return 0;
108
90
  }
package/bin/cli.js CHANGED
@@ -164,9 +164,8 @@ if (appUrlIndex !== -1) {
164
164
  process.exit(1);
165
165
  }
166
166
 
167
- const { launchSimulator } = await import(
168
- '../dist/cli/src/simulator/index.js'
169
- );
167
+ const { launchSimulator } =
168
+ await import('../dist/cli/src/simulator/index.js');
170
169
  launchSimulator(appUrl, port, simulatorDistPath);
171
170
  } else {
172
171
  // Unified dev mode: auto-detect app and start everything.
@@ -0,0 +1 @@
1
+ [data-v-d3c29943]{box-sizing:border-box}.simulator-layout[data-v-d3c29943]{color:#1a1a2e;background-color:#f8f9fa;flex-direction:column;min-height:100vh;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;transition:background-color .2s,color .2s;display:flex}.simulator-layout.dark-mode[data-v-d3c29943]{color:#e8e8e8;background-color:#1a1a2e}.header[data-v-d3c29943]{color:#fff;background:linear-gradient(135deg,#6366f1 0%,#8b5cf6 100%);justify-content:space-between;align-items:center;padding:.75rem 1.5rem;display:flex}.header-left[data-v-d3c29943]{align-items:center;gap:1.5rem;display:flex}.header h1[data-v-d3c29943]{letter-spacing:-.02em;margin:0;font-size:1.125rem;font-weight:600}.app-info[data-v-d3c29943]{background:#ffffff26;border-radius:4px;align-items:center;gap:.5rem;padding:.25rem .75rem;font-size:.8125rem;display:flex}.app-name[data-v-d3c29943]{font-weight:500}.app-version[data-v-d3c29943]{opacity:.8;font-size:.75rem}.header-right[data-v-d3c29943]{align-items:center;gap:.75rem;display:flex}.status-badge[data-v-d3c29943]{border-radius:999px;align-items:center;gap:.375rem;padding:.25rem .625rem;font-size:.75rem;font-weight:500;display:flex}.status-badge.live[data-v-d3c29943]{color:#86efac;background:#22c55e33}.status-dot[data-v-d3c29943]{background:#22c55e;border-radius:50%;width:6px;height:6px;animation:2s infinite pulse-d3c29943}@keyframes pulse-d3c29943{0%,to{opacity:1}50%{opacity:.5}}.icon-btn[data-v-d3c29943]{color:#fff;cursor:pointer;background:#ffffff1a;border:none;border-radius:6px;justify-content:center;align-items:center;width:32px;height:32px;transition:background .15s;display:flex}.icon-btn[data-v-d3c29943]:hover{background:#fff3}.loading-state[data-v-d3c29943],.error-state[data-v-d3c29943]{text-align:center;flex-direction:column;flex:1;justify-content:center;align-items:center;padding:3rem;display:flex}.spinner[data-v-d3c29943]{border:3px solid #e5e7eb;border-top-color:#6366f1;border-radius:50%;width:40px;height:40px;animation:.8s linear infinite spin-d3c29943}.dark-mode .spinner[data-v-d3c29943]{border-color:#8b5cf6 #374151 #374151}@keyframes spin-d3c29943{to{transform:rotate(360deg)}}.url-info[data-v-d3c29943]{color:#6b7280;margin-top:.75rem;font-family:SF Mono,Monaco,monospace;font-size:.8125rem}.error-icon[data-v-d3c29943]{color:#dc2626;background:#fef2f2;border-radius:50%;justify-content:center;align-items:center;width:48px;height:48px;margin-bottom:1rem;font-size:1.5rem;font-weight:700;display:flex}.dark-mode .error-icon[data-v-d3c29943]{background:#450a0a}.error-message[data-v-d3c29943]{color:#dc2626;margin:.5rem 0;font-weight:500}.error-details[data-v-d3c29943]{background:#fff;border-radius:8px;margin-top:1.5rem;padding:1rem;font-size:.875rem}.dark-mode .error-details[data-v-d3c29943]{background:#1f2937}.retry-btn[data-v-d3c29943]{color:#fff;cursor:pointer;background:#6366f1;border:none;border-radius:6px;margin-top:1rem;padding:.5rem 1rem;font-weight:500;transition:background .15s}.retry-btn[data-v-d3c29943]:hover{background:#4f46e5}.main-content[data-v-d3c29943]{flex-direction:column;flex:1;gap:1rem;padding:1rem;display:flex}.tabs[data-v-d3c29943]{background:#fff;border-radius:8px;align-items:center;gap:.25rem;padding:.25rem;display:flex;box-shadow:0 1px 2px #0000000d}.dark-mode .tabs[data-v-d3c29943]{background:#1f2937}.tab[data-v-d3c29943]{color:#6b7280;cursor:pointer;background:0 0;border:none;border-radius:6px;padding:.5rem 1rem;font-size:.875rem;font-weight:500;transition:all .15s}.tab[data-v-d3c29943]:hover{color:#1f2937}.dark-mode .tab[data-v-d3c29943]:hover{color:#e5e7eb}.tab.active[data-v-d3c29943]{color:#fff;background:#6366f1}.tab-controls[data-v-d3c29943]{gap:.25rem;margin-left:auto;display:flex}.control-btn[data-v-d3c29943]{color:#9ca3af;cursor:pointer;background:0 0;border:none;border-radius:6px;justify-content:center;align-items:center;width:36px;height:32px;transition:all .15s;display:flex}.control-btn[data-v-d3c29943]:hover{color:#4b5563;background:#f3f4f6}.dark-mode .control-btn[data-v-d3c29943]:hover{color:#d1d5db;background:#374151}.control-btn.active[data-v-d3c29943]{color:#6366f1;background:#ede9fe}.dark-mode .control-btn.active[data-v-d3c29943]{color:#c4b5fd;background:#4c1d95}.tab-panels[data-v-d3c29943]{flex-direction:column;flex:1;display:flex}.tab-panel[data-v-d3c29943]{background:#fff;border-radius:8px;flex:1;overflow:auto;box-shadow:0 1px 3px #0000000d}.dark-mode .tab-panel[data-v-d3c29943]{background:#1f2937}.player-panel[data-v-d3c29943]{background:#111827;flex-direction:column;display:flex}.player-panel.fullscreen[data-v-d3c29943]{z-index:1000;border-radius:0;padding:0;position:fixed;inset:0}.player-container[data-v-d3c29943]{flex:1;transition:all .3s;display:flex}.player-container.aspect-landscape[data-v-d3c29943]{aspect-ratio:16/9}.player-container.aspect-portrait[data-v-d3c29943]{aspect-ratio:9/16}.fullscreen .player-container[data-v-d3c29943]{width:100%;height:100%}.fullscreen .player-container.aspect-portrait[data-v-d3c29943]{width:auto;height:100%}.player-frame[data-v-d3c29943]{background:#000;flex:1;position:relative;overflow:hidden}.player-frame[data-v-d3c29943]>*{width:100%;height:100%;position:absolute;inset:0}.exit-fullscreen[data-v-d3c29943]{color:#fff;cursor:pointer;z-index:1001;background:#000000bf;border:none;border-radius:6px;padding:.5rem 1rem;font-size:.875rem;position:fixed;top:1rem;right:1rem}.exit-fullscreen[data-v-d3c29943]:hover{background:#000000e6}.player-controls[data-v-d3c29943]{background:#ffffff1a;justify-content:center;align-items:center;gap:1.5rem;padding:.75rem 1rem;display:flex}.fullscreen .player-controls[data-v-d3c29943]{z-index:1001;background:#000000bf;position:fixed;bottom:1rem;left:50%;transform:translate(-50%)}.controls-group[data-v-d3c29943]{gap:.5rem;display:flex}.player-btn[data-v-d3c29943]{color:#e5e7eb;cursor:pointer;background:#ffffff1a;border:none;border-radius:8px;justify-content:center;align-items:center;width:40px;height:40px;transition:all .15s;display:flex}.player-btn[data-v-d3c29943]:hover{color:#fff;background:#fff3}.player-btn.active[data-v-d3c29943]{color:#fff;background:#6366f1}.controls-info[data-v-d3c29943]{gap:.5rem;display:flex}.state-badge[data-v-d3c29943]{color:#86efac;background:#22c55e33;border-radius:4px;padding:.25rem .5rem;font-size:.75rem;font-weight:500}.state-badge.inactive[data-v-d3c29943]{color:#fca5a5;background:#ef444433}.config-panel[data-v-d3c29943]{padding:1.5rem;overflow-y:auto}.meta-panel[data-v-d3c29943]{padding:1rem}.meta-header[data-v-d3c29943]{justify-content:space-between;align-items:center;margin-bottom:.75rem;display:flex}.meta-header h3[data-v-d3c29943]{color:#6b7280;margin:0;font-size:.875rem;font-weight:600}.copy-btn[data-v-d3c29943]{color:#4b5563;cursor:pointer;background:#f3f4f6;border:none;border-radius:4px;padding:.25rem .5rem;font-size:.75rem}.dark-mode .copy-btn[data-v-d3c29943]{color:#d1d5db;background:#374151}.copy-btn[data-v-d3c29943]:hover{background:#e5e7eb}.dark-mode .copy-btn[data-v-d3c29943]:hover{background:#4b5563}.meta-code[data-v-d3c29943]{white-space:pre-wrap;word-break:break-word;background:#f9fafb;border-radius:6px;margin:0;padding:1rem;font-family:SF Mono,Monaco,Courier New,monospace;font-size:.8125rem;line-height:1.6;overflow-x:auto}.dark-mode .meta-code[data-v-d3c29943]{color:#e5e7eb;background:#111827}.placeholder[data-v-d3c29943]{color:#9ca3af;justify-content:center;align-items:center;height:100%;min-height:200px;font-style:italic;display:flex}@media (width<=640px){.header[data-v-d3c29943]{padding:.5rem 1rem}.header h1[data-v-d3c29943]{font-size:1rem}.app-info[data-v-d3c29943]{display:none}.tabs[data-v-d3c29943]{flex-wrap:wrap}.tab-controls[data-v-d3c29943]{justify-content:center;width:100%;margin-top:.5rem;margin-left:0}}
@@ -0,0 +1 @@
1
+ import{computed as e,createApp as t,createBlock as n,createCommentVNode as r,createElementBlock as i,createElementVNode as a,createTextVNode as o,defineComponent as s,markRaw as c,normalizeClass as l,onMounted as u,onUnmounted as d,openBlock as f,reactive as p,ref as m,resolveDynamicComponent as h,shallowRef as g,toDisplayString as _,unref as v,vShow as y,withDirectives as b}from"vue";import{provideAppConfigContext as x}from"@juuno-sdk/app-sdk";(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),t.credentials=e.crossOrigin===`use-credentials`?`include`:e.crossOrigin===`anonymous`?`omit`:`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var S={class:`header`},C={class:`header-left`},w={class:`app-info`},T={class:`app-name`},E={key:0,class:`app-version`},D={class:`header-right`},ee={key:0,class:`status-badge live`},O={key:0,width:`16`,height:`16`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`},te={key:1,width:`16`,height:`16`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`},ne={key:0,class:`loading-state`},re={class:`url-info`},ie={key:1,class:`error-state`},ae={class:`error-message`},oe={class:`error-details`},se={key:2,class:`main-content`},ce={class:`tabs`},le={key:0,class:`tab-controls`},k={class:`tab-panels`},A={class:`player-frame`},j={key:1,class:`placeholder`},M={class:`player-controls`},N={class:`controls-group`},P=[`title`],F={key:0,width:`20`,height:`20`,viewBox:`0 0 24 24`,fill:`currentColor`},I={key:1,width:`20`,height:`20`,viewBox:`0 0 24 24`,fill:`currentColor`},L=[`title`],R={key:0,width:`20`,height:`20`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`},z={key:1,width:`20`,height:`20`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`},B=[`title`],V={key:0,width:`20`,height:`20`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`},H={key:1,width:`20`,height:`20`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`},U={class:`controls-info`},ue={class:`tab-panel config-panel`},de={key:1,class:`placeholder`},fe={class:`tab-panel meta-panel`},pe={class:`meta-code`},W=((e,t)=>{let n=e.__vccOpts||e;for(let[e,r]of t)n[e]=r;return n})(s({name:`SimulatorLayout`,props:{playerComponent:{},configComponent:{},scene:{},appUrl:{},isLoading:{type:Boolean},error:{},manifest:{},liveReloadActive:{type:Boolean}},emits:[`reload`],setup(t,{emit:s}){let c=t,p=s,g=m(`player`),v=m(`landscape`),x=m(!1),W=m(!1),G=m(!0),K=m(!0),q=m(!1),J=m(3e4),Y=e(()=>c.manifest?.name||`External App`),X=e(()=>c.manifest?.version||``),me=e(()=>v.value===`landscape`?`aspect-landscape`:`aspect-portrait`),Z=e(()=>{try{return JSON.stringify(c.scene?.meta||{},null,2)}catch{return`{}`}});function Q(){p(`reload`)}async function he(){try{await navigator.clipboard.writeText(Z.value)}catch{console.warn(`Failed to copy to clipboard`)}}function $(e){e.key===`Escape`&&x.value&&(x.value=!1)}return u(()=>{window.addEventListener(`keydown`,$)}),d(()=>{window.removeEventListener(`keydown`,$)}),(e,s)=>(f(),i(`div`,{class:l([`simulator-layout`,{"dark-mode":W.value}])},[a(`header`,S,[a(`div`,C,[s[11]||=a(`h1`,null,`Juuno Simulator`,-1),a(`div`,w,[a(`span`,T,_(Y.value),1),X.value?(f(),i(`span`,E,`v`+_(X.value),1)):r(``,!0)])]),a(`div`,D,[t.liveReloadActive?(f(),i(`div`,ee,[...s[12]||=[a(`span`,{class:`status-dot`},null,-1),o(` Live `,-1)]])):r(``,!0),a(`button`,{class:`icon-btn`,title:`Reload app`,onClick:Q},[...s[13]||=[a(`svg`,{width:`16`,height:`16`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`},[a(`path`,{d:`M23 4v6h-6M1 20v-6h6M3.51 9a9 9 0 0114.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0020.49 15`})],-1)]]),a(`button`,{class:`icon-btn`,title:`Toggle dark mode`,onClick:s[0]||=e=>W.value=!W.value},[W.value?(f(),i(`svg`,te,[...s[15]||=[a(`path`,{d:`M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z`},null,-1)]])):(f(),i(`svg`,O,[...s[14]||=[a(`circle`,{cx:`12`,cy:`12`,r:`5`},null,-1),a(`path`,{d:`M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42`},null,-1)]]))])])]),t.isLoading?(f(),i(`div`,ne,[s[16]||=a(`div`,{class:`spinner`},null,-1),s[17]||=a(`p`,null,`Loading app...`,-1),a(`p`,re,_(t.appUrl),1)])):t.error?(f(),i(`div`,ie,[s[19]||=a(`div`,{class:`error-icon`},`!`,-1),s[20]||=a(`h2`,null,`Failed to Load App`,-1),a(`p`,ae,_(t.error),1),a(`div`,oe,[a(`p`,null,[s[18]||=a(`strong`,null,`App URL:`,-1),o(` `+_(t.appUrl),1)]),a(`button`,{class:`retry-btn`,onClick:Q},`Try Again`)])])):(f(),i(`div`,se,[a(`div`,ce,[a(`button`,{class:l([`tab`,{active:g.value===`player`}]),onClick:s[1]||=e=>g.value=`player`},` Player `,2),a(`button`,{class:l([`tab`,{active:g.value===`config`}]),onClick:s[2]||=e=>g.value=`config`},` Config `,2),a(`button`,{class:l([`tab`,{active:g.value===`meta`}]),onClick:s[3]||=e=>g.value=`meta`},` Scene Meta `,2),g.value===`player`?(f(),i(`div`,le,[a(`button`,{class:l([`control-btn`,{active:v.value===`landscape`}]),title:`Landscape (16:9)`,onClick:s[4]||=e=>v.value=`landscape`},[...s[21]||=[a(`svg`,{width:`18`,height:`12`,viewBox:`0 0 18 12`,fill:`currentColor`},[a(`rect`,{x:`0.5`,y:`0.5`,width:`17`,height:`11`,rx:`1`,fill:`none`,stroke:`currentColor`})],-1)]],2),a(`button`,{class:l([`control-btn`,{active:v.value===`portrait`}]),title:`Portrait (9:16)`,onClick:s[5]||=e=>v.value=`portrait`},[...s[22]||=[a(`svg`,{width:`12`,height:`18`,viewBox:`0 0 12 18`,fill:`currentColor`},[a(`rect`,{x:`0.5`,y:`0.5`,width:`11`,height:`17`,rx:`1`,fill:`none`,stroke:`currentColor`})],-1)]],2),a(`button`,{class:l([`control-btn`,{active:x.value}]),title:`Fullscreen (Esc to exit)`,onClick:s[6]||=e=>x.value=!x.value},[...s[23]||=[a(`svg`,{width:`16`,height:`16`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`},[a(`path`,{d:`M8 3H5a2 2 0 00-2 2v3m18 0V5a2 2 0 00-2-2h-3m0 18h3a2 2 0 002-2v-3M3 16v3a2 2 0 002 2h3`})],-1)]],2)])):r(``,!0)]),a(`div`,k,[b(a(`div`,{class:l([`tab-panel player-panel`,{fullscreen:x.value}])},[a(`div`,{class:l([`player-container`,me.value])},[a(`div`,A,[t.playerComponent?(f(),n(h(t.playerComponent),{key:t.scene.idx,scene:t.scene,playing:G.value,"is-visible":K.value,"duration-ms":J.value,muted:q.value},null,8,[`scene`,`playing`,`is-visible`,`duration-ms`,`muted`])):(f(),i(`div`,j,`No player component`))])],2),a(`div`,M,[a(`div`,N,[a(`button`,{class:l([`player-btn`,{active:G.value}]),title:G.value?`Pause`:`Play`,onClick:s[7]||=e=>G.value=!G.value},[G.value?(f(),i(`svg`,F,[...s[24]||=[a(`rect`,{x:`6`,y:`4`,width:`4`,height:`16`},null,-1),a(`rect`,{x:`14`,y:`4`,width:`4`,height:`16`},null,-1)]])):(f(),i(`svg`,I,[...s[25]||=[a(`polygon`,{points:`5,3 19,12 5,21`},null,-1)]]))],10,P),a(`button`,{class:l([`player-btn`,{active:!K.value}]),title:K.value?`Hide (simulate off-screen)`:`Show`,onClick:s[8]||=e=>K.value=!K.value},[K.value?(f(),i(`svg`,R,[...s[26]||=[a(`path`,{d:`M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z`},null,-1),a(`circle`,{cx:`12`,cy:`12`,r:`3`},null,-1)]])):(f(),i(`svg`,z,[...s[27]||=[a(`path`,{d:`M17.94 17.94A10.07 10.07 0 0112 20c-7 0-11-8-11-8a18.45 18.45 0 015.06-5.94M9.9 4.24A9.12 9.12 0 0112 4c7 0 11 8 11 8a18.5 18.5 0 01-2.16 3.19m-6.72-1.07a3 3 0 11-4.24-4.24`},null,-1),a(`line`,{x1:`1`,y1:`1`,x2:`23`,y2:`23`},null,-1)]]))],10,L),a(`button`,{class:l([`player-btn`,{active:q.value}]),title:q.value?`Unmute`:`Mute`,onClick:s[9]||=e=>q.value=!q.value},[q.value?(f(),i(`svg`,H,[...s[29]||=[a(`polygon`,{points:`11,5 6,9 2,9 2,15 6,15 11,19`},null,-1),a(`line`,{x1:`23`,y1:`9`,x2:`17`,y2:`15`},null,-1),a(`line`,{x1:`17`,y1:`9`,x2:`23`,y2:`15`},null,-1)]])):(f(),i(`svg`,V,[...s[28]||=[a(`polygon`,{points:`11,5 6,9 2,9 2,15 6,15 11,19`},null,-1),a(`path`,{d:`M19.07 4.93a10 10 0 010 14.14M15.54 8.46a5 5 0 010 7.07`},null,-1)]]))],10,B)]),a(`div`,U,[a(`span`,{class:l([`state-badge`,{inactive:!G.value}])},_(G.value?`Playing`:`Paused`),3),a(`span`,{class:l([`state-badge`,{inactive:!K.value}])},_(K.value?`Visible`:`Hidden`),3),a(`span`,{class:l([`state-badge`,{inactive:!q.value}])},_(q.value?`Muted`:`Sound On`),3)])]),x.value?(f(),i(`button`,{key:0,class:`exit-fullscreen`,onClick:s[10]||=e=>x.value=!1},` Exit Fullscreen `)):r(``,!0)],2),[[y,g.value===`player`]]),b(a(`div`,ue,[t.configComponent?(f(),n(h(t.configComponent),{key:`config-`+t.scene.idx})):(f(),i(`div`,de,`No config component`))],512),[[y,g.value===`config`]]),b(a(`div`,fe,[a(`div`,{class:`meta-header`},[s[30]||=a(`h3`,null,`scene.meta`,-1),a(`button`,{class:`copy-btn`,onClick:he},`Copy`)]),a(`pre`,pe,_(Z.value),1)],512),[[y,g.value===`meta`]])])]))],2))}}),[[`__scopeId`,`data-v-d3c29943`]]);function G(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}var K=`modulepreload`,q=function(e){return`/`+e},J={},Y=function(e,t,n){let r=Promise.resolve();if(t&&t.length>0){let e=document.getElementsByTagName(`link`),i=document.querySelector(`meta[property=csp-nonce]`),a=i?.nonce||i?.getAttribute(`nonce`);function o(e){return Promise.all(e.map(e=>Promise.resolve(e).then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}))))}function s(e){return import.meta.resolve?import.meta.resolve(e):new URL(e,import.meta.url).href}r=o(t.map(t=>{if(t=q(t,n),t=s(t),t in J)return;J[t]=!0;let r=t.endsWith(`.css`);for(let n=e.length-1;n>=0;n--){let i=e[n];if(i.href===t&&(!r||i.rel===`stylesheet`))return}let i=document.createElement(`link`);if(i.rel=r?`stylesheet`:K,r||(i.as=`script`),i.crossOrigin=``,i.href=t,a&&i.setAttribute(`nonce`,a),document.head.appendChild(i),r)return new Promise((e,n)=>{i.addEventListener(`load`,e),i.addEventListener(`error`,()=>n(Error(`Unable to preload CSS for ${t}`)))})}))}function i(e){let t=new Event(`vite:preloadError`,{cancelable:!0});if(t.payload=e,window.dispatchEvent(t),!t.defaultPrevented)throw e}return r.then(t=>{for(let e of t||[])e.status===`rejected`&&i(e.reason);return e().catch(i)})};t(s({name:`JuunoCLI`,setup(e){let t=m(window.__JUUNO_EXTERNAL_APP_URL__||`http://localhost:5002/index.js`),r=m(!1),i=m(null),a=[],o=g(null),s=g(null),l=m(!0),d=m(null),h=p({idx:`simulator-scene-1`,name:`Simulator Test Scene`,app:null,app_idx:`simulator-app-1`,created_at:new Date().toISOString(),display_time:5e3,language:`en`,order:0,playlist_idx:`simulator-playlist-1`,updated_at:new Date().toISOString(),translations:{},meta:{title:`Test App`,backgroundColor:`#2c3e50`,textColor:`#ecf0f1`},mediaAssets:[]}),_=x({async save(e){console.log(`Simulator: Scene updated`,e),Object.assign(h,e)}},{scene:h});async function y(){try{let e=await fetch(`${t.value}/manifest.json`);if(e.ok){let t=await e.json();return i.value=t,t}}catch{}return i.value=null,null}function b(e){a.forEach(e=>e.remove()),a.length=0,e.forEach(e=>{let n=document.createElement(`link`);n.rel=`stylesheet`,n.href=new URL(e,t.value).toString(),document.head.appendChild(n),a.push(n)})}function S(e){if(!G(e))return null;let t=e;return t.default?t.default:typeof t.render==`function`?t.render():null}function C(e){let t=[];for(let n of[e?.resources?.player,e?.resources?.config])n&&t.push(...n.filter(e=>e.endsWith(`.css`)));return t}async function w(){let e=S(await Y(()=>import(`external-app/player`),[]));if(!e)throw Error(`External app must export a default component or render function from /player`);o.value=c(e)}async function T(){try{let e=S(await Y(()=>import(`external-app/config`),[]));s.value=e?c(e):null}catch(e){console.warn(`Optional config module could not be loaded:`,e),s.value=null}}async function E(){try{l.value=!0,d.value=null,console.log(`Loading external app from: ${t.value}`);let e=C(await y());e.length>0&&(console.log(`Loading CSS files:`,e),b(e)),await w(),await T(),_.load(h),console.log(`✅ External app loaded successfully`),l.value=!1}catch(e){console.error(`Failed to load external app:`,e),d.value=e instanceof Error?e.message:`Failed to load external app`,l.value=!1}}function D(){console.log(`🔄 Reloading app...`),window.location.reload()}return u(E),(e,a)=>(f(),n(v(W),{"player-component":o.value,"config-component":s.value,scene:h,"app-url":t.value,"is-loading":l.value,error:d.value,manifest:i.value,"live-reload-active":r.value,onReload:D},null,8,[`player-component`,`config-component`,`scene`,`app-url`,`is-loading`,`error`,`manifest`,`live-reload-active`]))}})).mount(`#app`);
@@ -0,0 +1 @@
1
+ import{defineComponent as I,ref as g,computed as A,onMounted as J,onUnmounted as z,createElementBlock as u,openBlock as c,normalizeClass as p,createElementVNode as t,createCommentVNode as P,toDisplayString as C,createTextVNode as T,withDirectives as R,createBlock as $,resolveDynamicComponent as F,vShow as B,shallowRef as N,reactive as H,unref as K,markRaw as j,createApp as W}from"vue";import{provideAppConfigContext as X}from"@juuno-sdk/app-sdk";(function(){const f=document.createElement("link").relList;if(f&&f.supports&&f.supports("modulepreload"))return;for(const n of document.querySelectorAll('link[rel="modulepreload"]'))w(n);new MutationObserver(n=>{for(const i of n)if(i.type==="childList")for(const r of i.addedNodes)r.tagName==="LINK"&&r.rel==="modulepreload"&&w(r)}).observe(document,{childList:!0,subtree:!0});function m(n){const i={};return n.integrity&&(i.integrity=n.integrity),n.referrerPolicy&&(i.referrerPolicy=n.referrerPolicy),n.crossOrigin==="use-credentials"?i.credentials="include":n.crossOrigin==="anonymous"?i.credentials="omit":i.credentials="same-origin",i}function w(n){if(n.ep)return;n.ep=!0;const i=m(n);fetch(n.href,i)}})();const G="modulepreload",Q=function(l){return"/"+l},V={},D=function(f,m,w){let n=Promise.resolve();if(m&&m.length>0){let r=function(s){return Promise.all(s.map(d=>Promise.resolve(d).then(k=>({status:"fulfilled",value:k}),k=>({status:"rejected",reason:k}))))};document.getElementsByTagName("link");const a=document.querySelector("meta[property=csp-nonce]"),v=(a==null?void 0:a.nonce)||(a==null?void 0:a.getAttribute("nonce"));n=r(m.map(s=>{if(s=Q(s),s in V)return;V[s]=!0;const d=s.endsWith(".css"),k=d?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${s}"]${k}`))return;const h=document.createElement("link");if(h.rel=d?"stylesheet":G,d||(h.as="script"),h.crossOrigin="",h.href=s,v&&h.setAttribute("nonce",v),document.head.appendChild(h),d)return new Promise((x,_)=>{h.addEventListener("load",x),h.addEventListener("error",()=>_(new Error(`Unable to preload CSS for ${s}`)))})}))}function i(r){const a=new Event("vite:preloadError",{cancelable:!0});if(a.payload=r,window.dispatchEvent(a),!a.defaultPrevented)throw r}return n.then(r=>{for(const a of r||[])a.status==="rejected"&&i(a.reason);return f().catch(i)})},Y={class:"header"},Z={class:"header-left"},ee={class:"app-info"},te={class:"app-name"},oe={key:0,class:"app-version"},ne={class:"header-right"},le={key:0,class:"status-badge live"},se={key:0,width:"16",height:"16",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2"},ie={key:1,width:"16",height:"16",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2"},re={key:0,class:"loading-state"},ae={class:"url-info"},ce={key:1,class:"error-state"},ue={class:"error-message"},de={class:"error-details"},ve={key:2,class:"main-content"},pe={class:"tabs"},fe={key:0,class:"tab-controls"},me={class:"tab-panels"},ye={class:"player-frame"},ge={key:1,class:"placeholder"},he={class:"player-controls"},we={class:"controls-group"},ke=["title"],Ce={key:0,width:"20",height:"20",viewBox:"0 0 24 24",fill:"currentColor"},be={key:1,width:"20",height:"20",viewBox:"0 0 24 24",fill:"currentColor"},xe=["title"],_e={key:0,width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2"},Ee={key:1,width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2"},Le=["title"],Se={key:0,width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2"},Ae={key:1,width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2"},Pe={class:"controls-info"},Me={class:"tab-panel config-panel"},Re={key:1,class:"placeholder"},Be={class:"tab-panel meta-panel"},$e={class:"meta-code"},Oe={name:"SimulatorLayout"},Ue=I({...Oe,props:{playerComponent:{},configComponent:{},scene:{},appUrl:{},isLoading:{type:Boolean},error:{},manifest:{},liveReloadActive:{type:Boolean}},emits:["reload"],setup(l,{emit:f}){const m=l,w=f,n=g("player"),i=g("landscape"),r=g(!1),a=g(!1),v=g(!0),s=g(!0),d=g(!1),k=g(3e4),h=A(()=>{var y;return((y=m.manifest)==null?void 0:y.name)||"External App"}),x=A(()=>{var y;return((y=m.manifest)==null?void 0:y.version)||""}),_=A(()=>i.value==="landscape"?"aspect-landscape":"aspect-portrait"),E=A(()=>{var y;try{return JSON.stringify(((y=m.scene)==null?void 0:y.meta)||{},null,2)}catch{return"{}"}});function L(){w("reload")}async function M(){try{await navigator.clipboard.writeText(E.value)}catch{console.warn("Failed to copy to clipboard")}}function S(y){y.key==="Escape"&&r.value&&(r.value=!1)}return J(()=>{window.addEventListener("keydown",S)}),z(()=>{window.removeEventListener("keydown",S)}),(y,e)=>(c(),u("div",{class:p(["simulator-layout",{"dark-mode":a.value}])},[t("header",Y,[t("div",Z,[e[11]||(e[11]=t("h1",null,"Juuno Simulator",-1)),t("div",ee,[t("span",te,C(h.value),1),x.value?(c(),u("span",oe,"v"+C(x.value),1)):P("",!0)])]),t("div",ne,[l.liveReloadActive?(c(),u("div",le,[...e[12]||(e[12]=[t("span",{class:"status-dot"},null,-1),T(" Live ",-1)])])):P("",!0),t("button",{class:"icon-btn",title:"Reload app",onClick:L},[...e[13]||(e[13]=[t("svg",{width:"16",height:"16",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2"},[t("path",{d:"M23 4v6h-6M1 20v-6h6M3.51 9a9 9 0 0114.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0020.49 15"})],-1)])]),t("button",{class:"icon-btn",title:"Toggle dark mode",onClick:e[0]||(e[0]=o=>a.value=!a.value)},[a.value?(c(),u("svg",ie,[...e[15]||(e[15]=[t("path",{d:"M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"},null,-1)])])):(c(),u("svg",se,[...e[14]||(e[14]=[t("circle",{cx:"12",cy:"12",r:"5"},null,-1),t("path",{d:"M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"},null,-1)])]))])])]),l.isLoading?(c(),u("div",re,[e[16]||(e[16]=t("div",{class:"spinner"},null,-1)),e[17]||(e[17]=t("p",null,"Loading app...",-1)),t("p",ae,C(l.appUrl),1)])):l.error?(c(),u("div",ce,[e[19]||(e[19]=t("div",{class:"error-icon"},"!",-1)),e[20]||(e[20]=t("h2",null,"Failed to Load App",-1)),t("p",ue,C(l.error),1),t("div",de,[t("p",null,[e[18]||(e[18]=t("strong",null,"App URL:",-1)),T(" "+C(l.appUrl),1)]),t("button",{class:"retry-btn",onClick:L},"Try Again")])])):(c(),u("div",ve,[t("div",pe,[t("button",{class:p(["tab",{active:n.value==="player"}]),onClick:e[1]||(e[1]=o=>n.value="player")}," Player ",2),t("button",{class:p(["tab",{active:n.value==="config"}]),onClick:e[2]||(e[2]=o=>n.value="config")}," Config ",2),t("button",{class:p(["tab",{active:n.value==="meta"}]),onClick:e[3]||(e[3]=o=>n.value="meta")}," Scene Meta ",2),n.value==="player"?(c(),u("div",fe,[t("button",{class:p(["control-btn",{active:i.value==="landscape"}]),title:"Landscape (16:9)",onClick:e[4]||(e[4]=o=>i.value="landscape")},[...e[21]||(e[21]=[t("svg",{width:"18",height:"12",viewBox:"0 0 18 12",fill:"currentColor"},[t("rect",{x:"0.5",y:"0.5",width:"17",height:"11",rx:"1",fill:"none",stroke:"currentColor"})],-1)])],2),t("button",{class:p(["control-btn",{active:i.value==="portrait"}]),title:"Portrait (9:16)",onClick:e[5]||(e[5]=o=>i.value="portrait")},[...e[22]||(e[22]=[t("svg",{width:"12",height:"18",viewBox:"0 0 12 18",fill:"currentColor"},[t("rect",{x:"0.5",y:"0.5",width:"11",height:"17",rx:"1",fill:"none",stroke:"currentColor"})],-1)])],2),t("button",{class:p(["control-btn",{active:r.value}]),title:"Fullscreen (Esc to exit)",onClick:e[6]||(e[6]=o=>r.value=!r.value)},[...e[23]||(e[23]=[t("svg",{width:"16",height:"16",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2"},[t("path",{d:"M8 3H5a2 2 0 00-2 2v3m18 0V5a2 2 0 00-2-2h-3m0 18h3a2 2 0 002-2v-3M3 16v3a2 2 0 002 2h3"})],-1)])],2)])):P("",!0)]),t("div",me,[R(t("div",{class:p(["tab-panel player-panel",{fullscreen:r.value}])},[t("div",{class:p(["player-container",_.value])},[t("div",ye,[l.playerComponent?(c(),$(F(l.playerComponent),{key:l.scene.idx,scene:l.scene,playing:v.value,"is-visible":s.value,"duration-ms":k.value,muted:d.value},null,8,["scene","playing","is-visible","duration-ms","muted"])):(c(),u("div",ge,"No player component"))])],2),t("div",he,[t("div",we,[t("button",{class:p(["player-btn",{active:v.value}]),title:v.value?"Pause":"Play",onClick:e[7]||(e[7]=o=>v.value=!v.value)},[v.value?(c(),u("svg",Ce,[...e[24]||(e[24]=[t("rect",{x:"6",y:"4",width:"4",height:"16"},null,-1),t("rect",{x:"14",y:"4",width:"4",height:"16"},null,-1)])])):(c(),u("svg",be,[...e[25]||(e[25]=[t("polygon",{points:"5,3 19,12 5,21"},null,-1)])]))],10,ke),t("button",{class:p(["player-btn",{active:!s.value}]),title:s.value?"Hide (simulate off-screen)":"Show",onClick:e[8]||(e[8]=o=>s.value=!s.value)},[s.value?(c(),u("svg",_e,[...e[26]||(e[26]=[t("path",{d:"M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"},null,-1),t("circle",{cx:"12",cy:"12",r:"3"},null,-1)])])):(c(),u("svg",Ee,[...e[27]||(e[27]=[t("path",{d:"M17.94 17.94A10.07 10.07 0 0112 20c-7 0-11-8-11-8a18.45 18.45 0 015.06-5.94M9.9 4.24A9.12 9.12 0 0112 4c7 0 11 8 11 8a18.5 18.5 0 01-2.16 3.19m-6.72-1.07a3 3 0 11-4.24-4.24"},null,-1),t("line",{x1:"1",y1:"1",x2:"23",y2:"23"},null,-1)])]))],10,xe),t("button",{class:p(["player-btn",{active:d.value}]),title:d.value?"Unmute":"Mute",onClick:e[9]||(e[9]=o=>d.value=!d.value)},[d.value?(c(),u("svg",Ae,[...e[29]||(e[29]=[t("polygon",{points:"11,5 6,9 2,9 2,15 6,15 11,19"},null,-1),t("line",{x1:"23",y1:"9",x2:"17",y2:"15"},null,-1),t("line",{x1:"17",y1:"9",x2:"23",y2:"15"},null,-1)])])):(c(),u("svg",Se,[...e[28]||(e[28]=[t("polygon",{points:"11,5 6,9 2,9 2,15 6,15 11,19"},null,-1),t("path",{d:"M19.07 4.93a10 10 0 010 14.14M15.54 8.46a5 5 0 010 7.07"},null,-1)])]))],10,Le)]),t("div",Pe,[t("span",{class:p(["state-badge",{inactive:!v.value}])},C(v.value?"Playing":"Paused"),3),t("span",{class:p(["state-badge",{inactive:!s.value}])},C(s.value?"Visible":"Hidden"),3),t("span",{class:p(["state-badge",{inactive:!d.value}])},C(d.value?"Muted":"Sound On"),3)])]),r.value?(c(),u("button",{key:0,class:"exit-fullscreen",onClick:e[10]||(e[10]=o=>r.value=!1)}," Exit Fullscreen ")):P("",!0)],2),[[B,n.value==="player"]]),R(t("div",Me,[l.configComponent?(c(),$(F(l.configComponent),{key:"config-"+l.scene.idx})):(c(),u("div",Re,"No config component"))],512),[[B,n.value==="config"]]),R(t("div",Be,[t("div",{class:"meta-header"},[e[30]||(e[30]=t("h3",null,"scene.meta",-1)),t("button",{class:"copy-btn",onClick:M},"Copy")]),t("pre",$e,C(E.value),1)],512),[[B,n.value==="meta"]])])]))],2))}}),Te=(l,f)=>{const m=l.__vccOpts||l;for(const[w,n]of f)m[w]=n;return m},Fe=Te(Ue,[["__scopeId","data-v-d3c29943"]]);function Ne(l){return typeof l=="object"&&l!==null&&!Array.isArray(l)}const je={name:"JuunoCLI"},Ve=I({...je,setup(l){const f=g(window.__JUUNO_EXTERNAL_APP_URL__||"http://localhost:5002/index.js"),m=g(!1),w=g(null),n=[],i=N(null),r=N(null),a=g(!0),v=g(null),s=H({idx:"simulator-scene-1",name:"Simulator Test Scene",app:null,app_idx:"simulator-app-1",created_at:new Date().toISOString(),display_time:5e3,language:"en",order:0,playlist_idx:"simulator-playlist-1",updated_at:new Date().toISOString(),translations:{},meta:{title:"Test App",backgroundColor:"#2c3e50",textColor:"#ecf0f1"},mediaAssets:[]}),k=X({async save(e){console.log("Simulator: Scene updated",e),Object.assign(s,e)}},{scene:s});async function h(){try{const e=await fetch(`${f.value}/manifest.json`);if(e.ok){const o=await e.json();return w.value=o,o}}catch{}return w.value=null,null}function x(e){n.forEach(o=>o.remove()),n.length=0,e.forEach(o=>{const b=document.createElement("link");b.rel="stylesheet",b.href=new URL(o,f.value).toString(),document.head.appendChild(b),n.push(b)})}function _(e){if(!Ne(e))return null;const o=e;return o.default?o.default:typeof o.render=="function"?o.render():null}function E(e){var b,O;const o=[];for(const U of[(b=e==null?void 0:e.resources)==null?void 0:b.player,(O=e==null?void 0:e.resources)==null?void 0:O.config])U&&o.push(...U.filter(q=>q.endsWith(".css")));return o}async function L(){const e=await D(()=>import("external-app/player"),[]),o=_(e);if(!o)throw new Error("External app must export a default component or render function from /player");i.value=j(o)}async function M(){try{const e=await D(()=>import("external-app/config"),[]),o=_(e);r.value=o?j(o):null}catch(e){console.warn("Optional config module could not be loaded:",e),r.value=null}}async function S(){try{a.value=!0,v.value=null,console.log(`Loading external app from: ${f.value}`);const e=await h(),o=E(e);o.length>0&&(console.log("Loading CSS files:",o),x(o)),await L(),await M(),k.load(s),console.log("✅ External app loaded successfully"),a.value=!1}catch(e){console.error("Failed to load external app:",e),v.value=e instanceof Error?e.message:"Failed to load external app",a.value=!1}}function y(){console.log("🔄 Reloading app..."),window.location.reload()}return J(S),(e,o)=>(c(),$(K(Fe),{"player-component":i.value,"config-component":r.value,scene:s,"app-url":f.value,"is-loading":a.value,error:v.value,manifest:w.value,"live-reload-active":m.value,onReload:y},null,8,["player-component","config-component","scene","app-url","is-loading","error","manifest","live-reload-active"]))}}),De=W(Ve);De.mount("#app");
@@ -2,6 +2,8 @@ import inquirer from 'inquirer';
2
2
  import { homedir } from 'os';
3
3
  import { join } from 'path';
4
4
  import { readFileSync, writeFileSync, existsSync, mkdirSync, unlinkSync, } from 'fs';
5
+ import { wrapError } from '../utils/api.js';
6
+ import { isRecord } from '../utils/is-record.js';
5
7
  export const DEFAULT_PROFILE = 'default';
6
8
  const DEFAULT_API_URL = 'https://api.juuno.co';
7
9
  /**
@@ -19,28 +21,28 @@ function ensureCredentialsDir() {
19
21
  }
20
22
  }
21
23
  /**
22
- * Reads and parses the credentials file. Migrates legacy single-profile
23
- * files to the new profiles format transparently.
24
+ * Parse a raw credentials file body into the modern profiles format.
25
+ * - Modern shape ({ profiles: {...} }) passes through.
26
+ * - Legacy single-profile shape ({ token, api_url, ... }) migrates to a
27
+ * default-profile entry.
28
+ * - Anything else is treated as empty so the CLI starts in a clean state.
29
+ *
30
+ * Pure. Exported for testing.
24
31
  */
25
- function readCredentialsFile() {
26
- if (cachedFile) {
27
- return cachedFile;
32
+ export function parseCredentialsContent(parsed) {
33
+ if (!parsed || typeof parsed !== 'object') {
34
+ return { profiles: {} };
28
35
  }
29
- const credentialsPath = getCredentialsPath();
30
- if (!existsSync(credentialsPath)) {
31
- cachedFile = { profiles: {} };
32
- return cachedFile;
33
- }
34
- const fileContent = readFileSync(credentialsPath, 'utf-8');
35
- const parsed = JSON.parse(fileContent);
36
- if (parsed && typeof parsed === 'object' && 'profiles' in parsed) {
37
- cachedFile = parsed;
38
- return cachedFile;
36
+ if ('profiles' in parsed) {
37
+ const candidate = parsed.profiles;
38
+ if (!isRecord(candidate)) {
39
+ return { profiles: {} };
40
+ }
41
+ return { profiles: candidate };
39
42
  }
40
- // Legacy format: { token, api_url, created_at }. Migrate to default profile.
41
- if (parsed && typeof parsed === 'object' && 'token' in parsed) {
43
+ if ('token' in parsed) {
42
44
  const legacy = parsed;
43
- cachedFile = {
45
+ return {
44
46
  profiles: {
45
47
  [DEFAULT_PROFILE]: {
46
48
  token: legacy.token,
@@ -49,9 +51,23 @@ function readCredentialsFile() {
49
51
  },
50
52
  },
51
53
  };
54
+ }
55
+ return { profiles: {} };
56
+ }
57
+ /**
58
+ * Reads and parses the credentials file. Migrates legacy single-profile
59
+ * files to the new profiles format transparently.
60
+ */
61
+ function readCredentialsFile() {
62
+ if (cachedFile) {
52
63
  return cachedFile;
53
64
  }
54
- cachedFile = { profiles: {} };
65
+ const credentialsPath = getCredentialsPath();
66
+ if (!existsSync(credentialsPath)) {
67
+ cachedFile = { profiles: {} };
68
+ return cachedFile;
69
+ }
70
+ cachedFile = parseCredentialsContent(JSON.parse(readFileSync(credentialsPath, 'utf-8')));
55
71
  return cachedFile;
56
72
  }
57
73
  function writeCredentialsFile(file) {
@@ -158,16 +174,18 @@ export function getApiUrl(options = {}) {
158
174
  const profile = getProfile(resolveProfile(options));
159
175
  return profile?.api_url || DEFAULT_API_URL;
160
176
  }
177
+ function buildNotLoggedInError(profile) {
178
+ const profileFlag = formatProfileFlag(profile);
179
+ const suffix = profile === DEFAULT_PROFILE ? '' : ` for profile "${profile}"`;
180
+ return new Error(`Not logged in${suffix}. Please run "juuno-cli login${profileFlag}" first or provide credentials via --email and --password`);
181
+ }
161
182
  /**
162
183
  * Gets the authentication token from options, environment, or stored profile.
163
184
  */
164
185
  export async function getAuthToken(options) {
165
- if (options.token) {
166
- return options.token;
167
- }
168
- const envToken = process.env.JUUNO_API_TOKEN;
169
- if (envToken) {
170
- return envToken;
186
+ const explicit = options.token || process.env.JUUNO_API_TOKEN;
187
+ if (explicit) {
188
+ return explicit;
171
189
  }
172
190
  const profile = resolveProfile(options);
173
191
  const storedToken = loadToken(profile);
@@ -178,9 +196,7 @@ export async function getAuthToken(options) {
178
196
  const credentials = await getCredentials(options);
179
197
  return await loginWithCredentials(credentials, getApiUrl(options));
180
198
  }
181
- const profileFlag = formatProfileFlag(profile);
182
- const suffix = profile === DEFAULT_PROFILE ? '' : ` for profile "${profile}"`;
183
- throw new Error(`Not logged in${suffix}. Please run "juuno-cli login${profileFlag}" first or provide credentials via --email and --password`);
199
+ throw buildNotLoggedInError(profile);
184
200
  }
185
201
  /**
186
202
  * Gets credentials from options or prompts the user.
@@ -228,18 +244,15 @@ export async function loginWithCredentials(credentials, apiUrl) {
228
244
  });
229
245
  if (!response.ok) {
230
246
  const errorText = await response.text();
231
- throw new Error(`Authentication failed: ${response.status} ${response.statusText}\n${errorText}`);
247
+ throw new Error(`${response.status} ${response.statusText}\n${errorText}`);
232
248
  }
233
249
  const data = (await response.json());
234
250
  if (!data.success || !data.token) {
235
- throw new Error('Authentication failed: Invalid response from server');
251
+ throw new Error('Invalid response from server');
236
252
  }
237
253
  return data.token;
238
254
  }
239
255
  catch (error) {
240
- if (error instanceof Error) {
241
- throw new Error(`Authentication failed: ${error.message}`);
242
- }
243
- throw new Error('Authentication failed: Unknown error');
256
+ throw wrapError('Authentication failed', error);
244
257
  }
245
258
  }