nio-cat 0.5.1 → 0.8.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.
- package/README.md +39 -6
- package/bin/nio.js +158 -9
- package/package.json +3 -2
- package/src/agents/claude.js +8 -0
- package/src/audit.js +55 -0
- package/src/auth.js +175 -0
- package/src/claude-hook.js +6 -0
- package/src/control.js +238 -0
- package/src/directory.js +56 -0
- package/src/hub.js +33 -12
- package/src/jobs.js +287 -0
- package/src/mcp.js +142 -0
- package/src/memory.js +131 -0
- package/src/proc.js +1 -1
- package/src/push.js +52 -18
- package/src/server.js +97 -33
- package/src/service.js +123 -0
- package/src/telegram.js +311 -0
- package/src/tunnel.js +150 -34
- package/src/voice.js +164 -0
- package/web/app/app.js +693 -86
- package/web/app/style.css +68 -7
- package/web/board.css +3 -2
- package/web/board.js +15 -14
- package/web/index.html +35 -17
- package/web/privadesa.html +148 -0
- package/web/sw.js +13 -9
package/web/app/style.css
CHANGED
|
@@ -314,7 +314,7 @@ body.board-mode .view-enter { animation: none; }
|
|
|
314
314
|
|
|
315
315
|
@media (prefers-reduced-motion: no-preference) {
|
|
316
316
|
.view-enter { animation: view-in .25s var(--ease); }
|
|
317
|
-
@keyframes view-in { from { opacity: 0;
|
|
317
|
+
@keyframes view-in { from { opacity: 0; } }
|
|
318
318
|
}
|
|
319
319
|
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation: none !important; transition: none !important; } }
|
|
320
320
|
|
|
@@ -348,12 +348,13 @@ body.board-mode .view-enter { animation: none; }
|
|
|
348
348
|
.paste .pair-form { flex-direction: column; }
|
|
349
349
|
.side nav .grp { padding: 16px 12px 4px; }
|
|
350
350
|
.top h1 { font-size: 26px; }
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
body.
|
|
351
|
+
/* Al mòbil només fa scroll el document: res de contenidors amb scroll propi. */
|
|
352
|
+
.chat { height: auto; min-height: calc(100dvh - 56px - var(--safe-t)); }
|
|
353
|
+
.mode-bar { position: sticky; top: calc(56px + var(--safe-t)); z-index: 20; background: color-mix(in srgb, var(--bg) 82%, transparent); -webkit-backdrop-filter: blur(18px) saturate(160%); backdrop-filter: blur(18px) saturate(160%); }
|
|
354
|
+
.feed { flex: 1 0 auto; overflow: visible; overscroll-behavior: auto; }
|
|
355
|
+
.composer { position: sticky; bottom: var(--kb, 0px); z-index: 20; background: var(--bg); }
|
|
356
|
+
body.kb .composer { padding-bottom: 10px; }
|
|
357
|
+
.tool pre, .approval pre, .ev-logs pre, .proj-list { max-height: none; overflow-y: visible; }
|
|
357
358
|
.composer textarea, input[type="text"], input[type="password"], input[type="url"], input[type="search"], select, textarea.field { font-size: 16px; }
|
|
358
359
|
.chat-head { display: none; }
|
|
359
360
|
.mode-bar { padding: 10px 16px; }
|
|
@@ -569,3 +570,63 @@ dialog.sheet.wiz.compact .sheet-in, .compact .wiz-body { flex: 0 1 auto; }
|
|
|
569
570
|
.new-fab { position: fixed; right: 16px; bottom: calc(72px + var(--safe-b)); z-index: 25; height: 48px; padding: 0 18px 0 14px; border-radius: 16px; box-shadow: 0 14px 30px -14px color-mix(in srgb, var(--fg) 70%, transparent); }
|
|
570
571
|
body:not(.no-tabs):not(.kb) .main:has(.new-fab) { padding-bottom: calc(130px + var(--safe-b)); }
|
|
571
572
|
}
|
|
573
|
+
|
|
574
|
+
.skel { display: block; height: 14px; border-radius: 8px; background: linear-gradient(90deg, var(--border) 25%, color-mix(in srgb, var(--border) 45%, var(--white)) 50%, var(--border) 75%) 0 0 / 200% 100%; animation: skel 1.3s linear infinite; }
|
|
575
|
+
.skel.tall { height: 22px; }
|
|
576
|
+
.skel.r { margin-left: auto; }
|
|
577
|
+
.skel-card { display: grid; gap: 12px; padding: 18px; border-radius: var(--radius); border: 1px solid var(--border); background: var(--white); }
|
|
578
|
+
.skel-feed { display: grid; gap: 14px; padding: 8px 16px; max-width: 760px; margin: 0 auto; width: 100%; box-sizing: border-box; }
|
|
579
|
+
@keyframes skel { to { background-position: -200% 0; } }
|
|
580
|
+
.chip.fav { border-color: color-mix(in srgb, var(--lemon) 70%, var(--border)); }
|
|
581
|
+
.chip[data-fav][aria-pressed="true"] { background: var(--lemon); border-color: var(--lemon); }
|
|
582
|
+
.manage-bar { position: sticky; top: 0; z-index: 5; display: grid; gap: 10px; margin: 0 0 12px; padding: 12px; border: 1px solid var(--border); border-radius: 14px; background: var(--bg); }
|
|
583
|
+
.manage-bar .row { flex-wrap: wrap; }
|
|
584
|
+
.session-card.pick-mode { cursor: pointer; position: relative; padding-left: 46px; user-select: none; }
|
|
585
|
+
.session-card.pick-mode::before { content: ""; position: absolute; left: 14px; top: 16px; width: 20px; height: 20px; border-radius: 6px; border: 1.5px solid var(--border); background: var(--white); }
|
|
586
|
+
.session-card.picked { border-color: var(--fg); }
|
|
587
|
+
.session-card.picked::before { background: var(--fg); border-color: var(--fg); }
|
|
588
|
+
.session-card.picked::after { content: ""; position: absolute; left: 21px; top: 20px; width: 6px; height: 11px; border: solid var(--bg); border-width: 0 2px 2px 0; transform: rotate(45deg); }
|
|
589
|
+
|
|
590
|
+
.mode-bar .chip { height: 30px; gap: 7px; padding: 0 12px 0 10px; }
|
|
591
|
+
.mode-bar .chip > .ico, .mode-bar .chip > .agent-tag .ico { width: 15px; height: 15px; flex: none; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
|
|
592
|
+
.mode-bar .chip > span:not(.agent-tag) { line-height: 1; }
|
|
593
|
+
.mode-bar .chip .abot { --s: 18px; display: inline-grid; place-items: center; }
|
|
594
|
+
.mode-bar .chip .agent-tag { gap: 7px; line-height: 1; }
|
|
595
|
+
|
|
596
|
+
/* lliscar entre pàgines: es mou només el contingut; barres i botons fixos no es toquen */
|
|
597
|
+
@media (max-width: 820px) {
|
|
598
|
+
.main { overflow-x: clip; }
|
|
599
|
+
#view.swiping .page, #view.swipe-anim .page { position: relative; left: var(--sx, 0); opacity: var(--so, 1); will-change: left, opacity; }
|
|
600
|
+
#view.swipe-anim .page { transition: left .18s var(--ease), opacity .18s ease-out; }
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/* ---------- Motiu i captura a les aprovacions ---------- */
|
|
604
|
+
.approval .why { display: grid; gap: 2px; padding: 10px 12px; border-radius: 8px; background: color-mix(in srgb, var(--lemon) 22%, var(--white)); }
|
|
605
|
+
.approval .why p { font-size: 14px; white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
606
|
+
.approval .shot, .screen-img { width: 100%; border-radius: 10px; border: 1px solid var(--border); background: var(--bg-2); display: block; }
|
|
607
|
+
|
|
608
|
+
/* ---------- Activitat ---------- */
|
|
609
|
+
.act-filter { margin-bottom: 16px; overflow-x: auto; scrollbar-width: none; }
|
|
610
|
+
.act-filter button { padding: 0 12px; }
|
|
611
|
+
.act { display: grid; }
|
|
612
|
+
.act-day { font-size: 12px; font-weight: 600; color: var(--fg-muted); margin: 18px 0 6px; text-transform: capitalize; }
|
|
613
|
+
.act-day:first-child { margin-top: 0; }
|
|
614
|
+
.act-row { display: flex; gap: 12px; padding: 10px 0; border-top: 1px solid color-mix(in srgb, var(--border) 70%, transparent); }
|
|
615
|
+
.act-t { flex: none; width: 44px; font-size: 12px; color: var(--fg-muted); padding-top: 2px; }
|
|
616
|
+
.act-row .row { gap: 8px; }
|
|
617
|
+
.act-what { overflow-wrap: anywhere; color: var(--fg); margin-top: 2px; }
|
|
618
|
+
.act-row .verdict { font-size: 12px; }
|
|
619
|
+
|
|
620
|
+
/* ---------- Feines ---------- */
|
|
621
|
+
.job .title { font-size: 16px; font-weight: 500; letter-spacing: -.02em; margin-top: 8px; }
|
|
622
|
+
.job .meta { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 8px; font-size: 12px; color: var(--fg-muted); }
|
|
623
|
+
.chip.ok { color: var(--moss); border-color: color-mix(in srgb, var(--moss) 40%, transparent); }
|
|
624
|
+
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
|
|
625
|
+
.check { gap: 10px; font-size: 14px; }
|
|
626
|
+
.check input { width: 18px; height: 18px; accent-color: var(--fg); }
|
|
627
|
+
@media (max-width: 520px) { .grid-2 { grid-template-columns: 1fr; } }
|
|
628
|
+
|
|
629
|
+
/* ---------- Aturada d'emergència (sempre a la vista amb el control actiu) ---------- */
|
|
630
|
+
.panic { position: fixed; z-index: 35; right: 16px; top: calc(66px + var(--safe-t)); display: inline-flex; align-items: center; gap: 6px; height: 34px; padding: 0 12px 0 9px; border: 0; border-radius: 999px; background: var(--red); color: #fff; font: 600 13px var(--font); box-shadow: 0 8px 20px -10px color-mix(in srgb, var(--red) 80%, transparent); }
|
|
631
|
+
.panic .ico { width: 17px; height: 17px; }
|
|
632
|
+
@media (min-width: 821px) { .panic { top: 16px; right: 20px; } }
|
package/web/board.css
CHANGED
|
@@ -104,9 +104,10 @@
|
|
|
104
104
|
.bd-chat .m.nio b { font-weight: 600; }
|
|
105
105
|
.bd-chat .mav { --s: 24px; --bw: .15; margin-bottom: 4px; }
|
|
106
106
|
@keyframes bd-msg { from { opacity: 0; transform: translateY(6px); } }
|
|
107
|
-
.bd-chat .chips { display: flex; gap: 6px; overflow-x: auto; padding:
|
|
107
|
+
.bd-chat .chips { flex: none; display: flex; gap: 6px; overflow-x: auto; overflow-y: hidden; padding: 4px 16px 10px; scrollbar-width: none; -webkit-overflow-scrolling: touch; overscroll-behavior-x: contain; scroll-snap-type: x proximity; }
|
|
108
|
+
.bd-chat header, .bd-chat .brain, .bd-chat .cin { flex: none; }
|
|
108
109
|
.bd-chat .chips::-webkit-scrollbar { display: none; }
|
|
109
|
-
.bd-chat .chip-s { flex: none; max-width: 260px; box-sizing: border-box; height: 32px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: 0 12px; line-height: 30px; text-align: center; border: 1px solid var(--border); border-radius: 999px; background: var(--white); font-size: 12px; font-weight: 500; cursor: pointer; }
|
|
110
|
+
.bd-chat .chip-s { flex: none; scroll-snap-align: start; max-width: 260px; box-sizing: border-box; height: 32px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: 0 12px; line-height: 30px; text-align: center; border: 1px solid var(--border); border-radius: 999px; background: var(--white); font-size: 12px; font-weight: 500; cursor: pointer; }
|
|
110
111
|
.bd-chat .chip-s:hover { border-color: var(--fg); }
|
|
111
112
|
.bd-chat .cin { display: flex; gap: 8px; align-items: flex-end; padding: 12px 14px calc(14px + env(safe-area-inset-bottom)); border-top: 1px solid var(--border); }
|
|
112
113
|
.bd-chat .cin textarea { flex: 1; min-height: 42px; max-height: 140px; padding: 10px 12px; border: 1px solid var(--border); border-radius: 12px; background: var(--white); resize: none; font: inherit; font-size: 14px; line-height: 1.4; outline: none; }
|
package/web/board.js
CHANGED
|
@@ -740,21 +740,21 @@ function orgChart(spec) {
|
|
|
740
740
|
return pairs.length ? buildGraph(pairs, [], 'v') : [];
|
|
741
741
|
}
|
|
742
742
|
const TEMPLATES = {
|
|
743
|
-
agent: { say: 'Aquí tens com treballa un agent
|
|
744
|
-
aprovacio: { say: 'Flux d’aprovació: el que
|
|
745
|
-
|
|
743
|
+
agent: { say: 'Aquí tens com treballa un agent amb nio, del mòbil a l’ordinador.', edges: [['Mòbil', 'nio', 'instrucció'], ['nio', 'Claude Code', ''], ['Claude Code', 'Acció de risc?', ''], ['Acció de risc?', 'Aprovació al mòbil', 'sí'], ['Acció de risc?', 'Fa la feina', 'no'], ['Aprovació al mòbil', 'Fa la feina', 'aprovada'], ['Fa la feina', 'Registre', '']] },
|
|
744
|
+
aprovacio: { say: 'Flux d’aprovació: el que és arriscat passa per tu.', edges: [['Inici', 'L’agent vol actuar', ''], ['L’agent vol actuar', 'Només llegeix?', ''], ['Només llegeix?', 'Executa', 'sí'], ['Només llegeix?', 'Demana permís', 'no'], ['Demana permís', 'Aprovada?', ''], ['Aprovada?', 'Executa', 'sí'], ['Aprovada?', 'Ho deixa estar', 'no'], ['Executa', 'Fi', ''], ['Ho deixa estar', 'Fi', '']] },
|
|
745
|
+
revisio: { say: 'Una feina encadenada: un agent fa, l’altre revisa.', edges: [['Issue nova', 'Claude Code arregla', ''], ['Claude Code arregla', 'Tests', ''], ['Tests', 'Passen?', ''], ['Passen?', 'Codex revisa', 'sí'], ['Passen?', 'Claude Code torna-hi', 'no'], ['Claude Code torna-hi', 'Tests', ''], ['Codex revisa', 'Pull request', '']] }
|
|
746
746
|
};
|
|
747
747
|
|
|
748
748
|
/* ---------- Xat ---------- */
|
|
749
|
-
const SUGG = ['Flux:
|
|
749
|
+
const SUGG = ['Flux: Idea -> Prototip -> Proves -> Publicació', 'Mapa mental de nio: agents, ordinadors, aprovacions, memòria', 'Arbre: Web > Portada, App; App > Sessions, Feines', 'Plantilla d’aprovació', 'Ajuda'];
|
|
750
750
|
const HELP = `Puc dibuixar i editar per tu. Prova, per exemple:
|
|
751
751
|
• <b>Flux: A -> B -> C</b> (una fletxa per línia, amb <b>: etiqueta</b> si vols)
|
|
752
752
|
• <b>Mapa mental de X: a, b, c</b>
|
|
753
|
-
• <b>Organigrama:
|
|
754
|
-
• <b>Afegeix un rombe “
|
|
755
|
-
• <b>Pinta
|
|
756
|
-
• <b>Esborra
|
|
757
|
-
• <b>Plantilla agent</b>, <b>Plantilla d’aprovació</b>, <b>Plantilla
|
|
753
|
+
• <b>Organigrama: Web > Portada, App</b>
|
|
754
|
+
• <b>Afegeix un rombe “Passen?”</b> · <b>Connecta Tests amb Deploy</b>
|
|
755
|
+
• <b>Pinta Tests de verd</b> · <b>Reanomena Tests a Proves</b>
|
|
756
|
+
• <b>Esborra Deploy</b> · <b>Ordena-ho</b> · <b>Centra</b> · <b>Desfés</b>
|
|
757
|
+
• <b>Plantilla agent</b>, <b>Plantilla d’aprovació</b>, <b>Plantilla revisió</b>
|
|
758
758
|
• <b>Exporta PNG</b>, <b>SVG</b> o <b>JSON</b>`;
|
|
759
759
|
function say(html, me) {
|
|
760
760
|
B.chat.push({ me: !!me, html, t: Date.now() }); if (B.chat.length > 200) B.chat.splice(0, B.chat.length - 200);
|
|
@@ -792,7 +792,7 @@ function ruleAct(text) {
|
|
|
792
792
|
}
|
|
793
793
|
else if (/^exporta/.test(n)) { const k = /svg/.test(n) ? 'svg' : /json/.test(n) ? 'json' : 'png'; reply = exportAs(k) ? `T’he descarregat la pissarra en ${k.toUpperCase()}.` : 'Encara no hi ha res per exportar.'; }
|
|
794
794
|
else if (/plantilla|com treballa un agent|flux d.?un agent/.test(n)) {
|
|
795
|
-
const k = /aprova/.test(n) ? 'aprovacio' : /
|
|
795
|
+
const k = /aprova/.test(n) ? 'aprovacio' : /revisi|revisa/.test(n) ? 'revisio' : 'agent';
|
|
796
796
|
record(); const t = TEMPLATES[k], list = buildGraph(t.edges, [], 'h'); done(t.say, list);
|
|
797
797
|
}
|
|
798
798
|
else if (/^(mapa mental|mapa conceptual|mindmap)/.test(n)) {
|
|
@@ -952,7 +952,8 @@ function drawBrains() {
|
|
|
952
952
|
const sel_ = R.root.querySelector('#bdBrain'); if (!sel_) return;
|
|
953
953
|
const list = window.NioBrains?.list() || [{ id: 'rules', label: 'Ordres ràpides' }];
|
|
954
954
|
const cur = list.some((b) => b.id === B.opts.brain) ? B.opts.brain : 'rules'; // la preferència es manté encara que ara no hi sigui
|
|
955
|
-
|
|
955
|
+
const html = list.map((b) => `<option value="${esc(b.id)}" ${b.id === cur ? 'selected' : ''}>${esc(b.label)}</option>`).join('');
|
|
956
|
+
if (sel_.dataset.sig !== html) { sel_.dataset.sig = html; sel_.innerHTML = html; }
|
|
956
957
|
}
|
|
957
958
|
|
|
958
959
|
/* ---------- Panell d'estil ---------- */
|
|
@@ -1034,7 +1035,7 @@ function openChat(open, focus = true, persist = true) {
|
|
|
1034
1035
|
R.root.classList.toggle('chat-open', open);
|
|
1035
1036
|
R.chat.inert = !open;
|
|
1036
1037
|
const t = R.root.querySelector('[data-bd=chat]'); t.setAttribute('aria-pressed', open);
|
|
1037
|
-
if (open && focus) setTimeout(() => R.input.focus(), reduce ? 0 : 280);
|
|
1038
|
+
if (open && focus && !matchMedia('(pointer: coarse)').matches) setTimeout(() => R.input.focus(), reduce ? 0 : 280);
|
|
1038
1039
|
if (!open && wasOpen && R.chat.contains(document.activeElement)) t.focus({ preventScroll: true });
|
|
1039
1040
|
}
|
|
1040
1041
|
|
|
@@ -1045,7 +1046,7 @@ function template() {
|
|
|
1045
1046
|
<div class="bd-stage" id="bdStage" data-tool="select">
|
|
1046
1047
|
<svg class="bd-svg" id="bdSvg" role="img" aria-label="Pissarra"><g id="bdWorld"><g id="bdEls"></g><g id="bdSel" class="bd-selg"></g></g></svg>
|
|
1047
1048
|
<textarea class="bd-edit" id="bdEdit" hidden spellcheck="false" aria-label="Edita el text"></textarea>
|
|
1048
|
-
<div class="bd-empty" id="bdEmpty"><b>Pissarra en blanc</b><span>Dibuixa amb les eines de dalt o demana-ho a en Nio: «Flux:
|
|
1049
|
+
<div class="bd-empty" id="bdEmpty"><b>Pissarra en blanc</b><span>Dibuixa amb les eines de dalt o demana-ho a en Nio: «Flux: Idea -> Prototip -> Proves».</span></div>
|
|
1049
1050
|
|
|
1050
1051
|
<div class="bd-bar bd-tools" id="bdTools" role="toolbar" aria-label="Eines">
|
|
1051
1052
|
${TOOLS.map(t => `<button type="button" class="tb" data-tool="${t[0]}" aria-pressed="${t[0] === tool}" title="${t[1]} — ${t[2]}${t[3] ? ' o ' + t[3] : ''}">${svgI(t[4])}${t[3] ? `<small>${t[3]}</small>` : ''}</button>`).join('')}
|
|
@@ -1175,7 +1176,7 @@ function mount(root) {
|
|
|
1175
1176
|
on(R.input, 'keydown', e => { if (e.key === 'Enter' && !e.shiftKey && !e.isComposing) { e.preventDefault(); send(); } if (e.key === 'Escape') { e.stopPropagation(); openChat(false); } });
|
|
1176
1177
|
on(R.input, 'input', () => { R.input.style.height = ''; R.input.style.height = Math.min(140, R.input.scrollHeight) + 'px'; });
|
|
1177
1178
|
on(R.root.querySelector('#bdBrain'), 'change', (e) => { B.opts.brain = e.target.value; save(); say(B.opts.brain === 'rules' ? 'Faig servir les ordres ràpides. Escriu <b>ajuda</b> per veure-les.' : `Ara t’entendré amb <b>${esc(e.target.selectedOptions[0].textContent)}</b>. Demana’m el diagrama com vulguis.`); });
|
|
1178
|
-
on(R.root.querySelector('#bdBrain'), 'focus', drawBrains);
|
|
1179
|
+
on(R.root.querySelector('#bdBrain'), 'focus', () => { drawBrains(); window.NioBrains?.refresh?.(); });
|
|
1179
1180
|
on(R.root.querySelector('#bdImport'), 'change', e => { const f = e.target.files[0]; if (f) importJSON(f); e.target.value = ''; });
|
|
1180
1181
|
on(document, 'click', e => { if (R && !e.target.closest('.bd-menuwrap')) { const m = R.root.querySelector('#bdMenu'); if (m) m.hidden = true; } });
|
|
1181
1182
|
on(window, 'resize', () => { applyView(); drawSel(); });
|
package/web/index.html
CHANGED
|
@@ -3,23 +3,23 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
-
<title>nio // agents
|
|
7
|
-
<meta name="description" content="
|
|
6
|
+
<title>nio // els teus agents, des del mòbil</title>
|
|
7
|
+
<meta name="description" content="Claude Code i Codex als teus ordinadors, controlats des del mòbil. Personal, gratuït i amb les teves claus.">
|
|
8
8
|
<link rel="canonical" href="https://www.nio.cat/">
|
|
9
9
|
<meta name="theme-color" content="#f0ede7">
|
|
10
10
|
<meta property="og:type" content="website">
|
|
11
11
|
<meta property="og:site_name" content="nio">
|
|
12
12
|
<meta property="og:locale" content="ca_ES">
|
|
13
13
|
<meta property="og:url" content="https://www.nio.cat/">
|
|
14
|
-
<meta property="og:title" content="nio // agents
|
|
15
|
-
<meta property="og:description" content="
|
|
14
|
+
<meta property="og:title" content="nio // els teus agents, des del mòbil">
|
|
15
|
+
<meta property="og:description" content="Claude Code i Codex als teus ordinadors, controlats des del mòbil. Personal, gratuït i amb les teves claus.">
|
|
16
16
|
<meta property="og:image" content="https://www.nio.cat/og.png">
|
|
17
17
|
<meta property="og:image:width" content="1200">
|
|
18
18
|
<meta property="og:image:height" content="630">
|
|
19
19
|
<meta property="og:image:alt" content="nio">
|
|
20
20
|
<meta name="twitter:card" content="summary_large_image">
|
|
21
|
-
<meta name="twitter:title" content="nio // agents
|
|
22
|
-
<meta name="twitter:description" content="
|
|
21
|
+
<meta name="twitter:title" content="nio // els teus agents, des del mòbil">
|
|
22
|
+
<meta name="twitter:description" content="Claude Code i Codex als teus ordinadors, controlats des del mòbil. Personal, gratuït i amb les teves claus.">
|
|
23
23
|
<meta name="twitter:image" content="https://www.nio.cat/og.png">
|
|
24
24
|
<meta name="format-detection" content="telephone=no">
|
|
25
25
|
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
|
@@ -64,6 +64,7 @@ section p { text-wrap: pretty; }
|
|
|
64
64
|
section p + p, section p + ul, section ul + p, section p + .cmd, section .cmd + p { margin-top: 1em; }
|
|
65
65
|
main a { text-decoration-thickness: 1px; text-underline-offset: 4px; text-decoration-color: var(--accent); }
|
|
66
66
|
main a:hover { color: var(--accent); }
|
|
67
|
+
main ul.list { padding-left: 1.2em; display: grid; gap: .5em; }
|
|
67
68
|
main code { font: 500 .88em ui-monospace, "SF Mono", Menlo, monospace; padding: 2px 7px; border-radius: 7px; background: #e6e1d8; }
|
|
68
69
|
main .cmd { display: flex; align-items: center; gap: 4px; padding: 6px 6px 6px 18px; border-radius: 12px; background: #e6e1d8; }
|
|
69
70
|
main .cmd code { flex: 1; min-width: 0; overflow-x: auto; white-space: nowrap; padding: 8px 0; background: none; border-radius: 0; font-size: 16px; scrollbar-width: none; }
|
|
@@ -102,26 +103,43 @@ main strong { font-weight: 600; }
|
|
|
102
103
|
</header>
|
|
103
104
|
|
|
104
105
|
<main class="page">
|
|
105
|
-
<h1>Els agents
|
|
106
|
-
<p class="lead">
|
|
106
|
+
<h1>Els teus agents, a les teves màquines, des del mòbil.</h1>
|
|
107
|
+
<p class="lead">nio és un comandament per a Claude Code i Codex. Treballen als teus ordinadors (el portàtil, el mini PC de casa, un servidor), amb els teus comptes i les teves claus. Tu els dones feina des del mòbil, els segueixes en directe i aproves el que és arriscat. És personal i gratuït.</p>
|
|
107
108
|
|
|
108
109
|
<section aria-labelledby="com">
|
|
109
110
|
<h2 id="com">Com funciona</h2>
|
|
110
|
-
<p><strong>1.
|
|
111
|
-
<div class="cmd"><code id="cmd">
|
|
112
|
-
<p>
|
|
113
|
-
<p><strong>2.
|
|
114
|
-
<p><strong>3.
|
|
111
|
+
<p><strong>1. A cada ordinador</strong> (Linux o Mac), una sola ordre:</p>
|
|
112
|
+
<div class="cmd"><code id="cmd">curl -fsSL https://www.nio.cat/install.sh | sh</code><button class="copy" type="button" data-copy="curl -fsSL https://www.nio.cat/install.sh | sh" aria-label="Copia l’ordre"><svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2"/><path d="M16 8V5a1 1 0 0 0-1-1H5a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h3"/></svg><svg class="ok" viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12.5l4.5 4.5L19 7.5"/></svg></button></div>
|
|
113
|
+
<p>Instal·la nio, el deixa engegat i et mostra un codi QR. Si només el vols provar: <code>npx nio-cat</code>.</p>
|
|
114
|
+
<p><strong>2. Al mòbil</strong>, obre <a href="/app">nio.cat/app</a> i escaneja el QR. El codi serveix un sol cop i el mòbil en rep una clau pròpia, que pots revocar quan vulguis. Afegeix-lo a la pantalla d’inici i tindràs avisos.</p>
|
|
115
|
+
<p><strong>3. Dona-li feina.</strong> Tria l’ordinador, l’agent i la carpeta, i explica què vols. Si l’ordinador es reinicia, el mòbil el torna a trobar sol.</p>
|
|
115
116
|
</section>
|
|
116
117
|
|
|
117
|
-
<section aria-labelledby="
|
|
118
|
-
<h2 id="
|
|
119
|
-
<
|
|
118
|
+
<section aria-labelledby="que">
|
|
119
|
+
<h2 id="que">Què hi pots fer</h2>
|
|
120
|
+
<ul class="list">
|
|
121
|
+
<li><strong>Sessions</strong> amb Claude Code i Codex, en directe, a qualsevol dels teus ordinadors.</li>
|
|
122
|
+
<li><strong>Aprovacions</strong>: abans d’editar, executar o clicar res, te’n demana permís amb el motiu. Tot queda registrat.</li>
|
|
123
|
+
<li><strong>Feines</strong> que es fan soles: ara, més tard o cada dia, i encadenades («quan Claude Code acabi, que Codex ho revisi»).</li>
|
|
124
|
+
<li><strong>Memòria compartida</strong>: el que recorda un agent, ho sap l’altre. Fitxers markdown al teu ordinador.</li>
|
|
125
|
+
<li><strong>Telegram i veu</strong>: parla-hi amb el teu bot o amb notes de veu, i aprova amb un botó.</li>
|
|
126
|
+
<li><strong>Navegador i escriptori</strong>: els agents poden mirar webs i apps; per actuar, sempre et demanen permís amb una captura.</li>
|
|
127
|
+
</ul>
|
|
128
|
+
</section>
|
|
129
|
+
|
|
130
|
+
<section aria-labelledby="teu">
|
|
131
|
+
<h2 id="teu">Tot és teu</h2>
|
|
132
|
+
<p>Fas servir els teus agents i les teves claus (Anthropic, OpenAI, ElevenLabs, el bot de Telegram). Les converses, la memòria i les claus es queden als teus ordinadors. nio.cat només serveix l’app i guarda, signada, l’adreça on trobar cada ordinador. Més detalls a <a href="/privadesa">privadesa</a>.</p>
|
|
133
|
+
</section>
|
|
134
|
+
|
|
135
|
+
<section aria-labelledby="prova">
|
|
136
|
+
<h2 id="prova">Prova-ho</h2>
|
|
137
|
+
<p>Obre <a href="/app">el panell</a>. Si encara no tens cap ordinador connectat, t’ensenya com fer-ho pas a pas.</p>
|
|
120
138
|
</section>
|
|
121
139
|
</main>
|
|
122
140
|
|
|
123
141
|
<footer class="footer">
|
|
124
|
-
<div class="page">© 2026 nio // un projecte de <a href="https://f.cat">f.cat</a></div>
|
|
142
|
+
<div class="page">© 2026 nio // un projecte de <a href="https://f.cat">f.cat</a> · <a href="mailto:hola@nio.cat">hola@nio.cat</a> · <a href="/privadesa">Privadesa</a></div>
|
|
125
143
|
</footer>
|
|
126
144
|
<script src="/bot.js" defer></script>
|
|
127
145
|
<script>
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="ca">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>nio // privadesa</title>
|
|
7
|
+
<meta name="description" content="Què passa per nio.cat i què no hi passa mai.">
|
|
8
|
+
<link rel="canonical" href="https://www.nio.cat/privadesa">
|
|
9
|
+
<meta name="theme-color" content="#f0ede7">
|
|
10
|
+
<meta property="og:type" content="website">
|
|
11
|
+
<meta property="og:site_name" content="nio">
|
|
12
|
+
<meta property="og:locale" content="ca_ES">
|
|
13
|
+
<meta property="og:url" content="https://www.nio.cat/privadesa">
|
|
14
|
+
<meta property="og:title" content="nio // privadesa">
|
|
15
|
+
<meta property="og:description" content="Què passa per nio.cat i què no hi passa mai.">
|
|
16
|
+
<meta property="og:image" content="https://www.nio.cat/og.png">
|
|
17
|
+
<meta property="og:image:width" content="1200">
|
|
18
|
+
<meta property="og:image:height" content="630">
|
|
19
|
+
<meta property="og:image:alt" content="nio">
|
|
20
|
+
<meta name="twitter:card" content="summary_large_image">
|
|
21
|
+
<meta name="twitter:title" content="nio // privadesa">
|
|
22
|
+
<meta name="twitter:description" content="Què passa per nio.cat i què no hi passa mai.">
|
|
23
|
+
<meta name="twitter:image" content="https://www.nio.cat/og.png">
|
|
24
|
+
<meta name="format-detection" content="telephone=no">
|
|
25
|
+
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
|
26
|
+
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
|
|
27
|
+
<link rel="manifest" href="/manifest.webmanifest">
|
|
28
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
29
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
30
|
+
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600&display=swap" rel="stylesheet">
|
|
31
|
+
<link rel="stylesheet" href="/bot.css">
|
|
32
|
+
<style>
|
|
33
|
+
:root {
|
|
34
|
+
--bg: #f0ede7;
|
|
35
|
+
--fg: #272224;
|
|
36
|
+
--fg-muted: #69615c;
|
|
37
|
+
--accent: #f46a3b;
|
|
38
|
+
--border: #cec7bd;
|
|
39
|
+
--lemon: #e7e66a;
|
|
40
|
+
--red: #e2382b;
|
|
41
|
+
--font: "Manrope", system-ui, sans-serif;
|
|
42
|
+
}
|
|
43
|
+
* { box-sizing: border-box; margin: 0; padding: 0; -webkit-tap-highlight-color: transparent; }
|
|
44
|
+
html { -webkit-text-size-adjust: 100%; }
|
|
45
|
+
body { background: var(--bg); color: var(--fg); font: 400 19px/1.6 var(--font); letter-spacing: -.02em; -webkit-font-smoothing: antialiased; }
|
|
46
|
+
a { color: inherit; }
|
|
47
|
+
::selection { background: var(--lemon); }
|
|
48
|
+
:focus-visible { outline: 2px solid var(--fg); outline-offset: 3px; border-radius: 2px; }
|
|
49
|
+
|
|
50
|
+
.page { width: min(640px, 100% - 48px); margin: 0 auto; }
|
|
51
|
+
.head { padding: 56px 0 0; display: flex; justify-content: space-between; align-items: baseline; }
|
|
52
|
+
.head .enter { display: inline-flex; align-items: center; gap: 8px; height: 34px; padding: 0 14px 0 12px; border: 1px solid var(--border); border-radius: 999px; font-size: 14px; font-weight: 500; color: var(--fg); text-decoration: none; transition: border-color .2s, background-color .2s; }
|
|
53
|
+
.head .enter svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
|
|
54
|
+
.head .enter:hover { border-color: var(--fg); background: color-mix(in srgb, var(--fg) 4%, transparent); }
|
|
55
|
+
.mark { font-size: 28px; font-weight: 600; letter-spacing: -.07em; line-height: 1; text-decoration: none; }
|
|
56
|
+
|
|
57
|
+
main { padding: 96px 0 96px; }
|
|
58
|
+
h1 { font-size: clamp(36px, 6vw, 52px); font-weight: 500; line-height: 1.06; letter-spacing: -.055em; text-wrap: balance; }
|
|
59
|
+
.lead { margin-top: 28px; font-size: 21px; line-height: 1.5; color: var(--fg-muted); text-wrap: pretty; }
|
|
60
|
+
|
|
61
|
+
section { margin-top: 56px; }
|
|
62
|
+
h2 { font-size: clamp(26px, 3.4vw, 32px); font-weight: 500; line-height: 1.15; letter-spacing: -.045em; margin-bottom: 18px; }
|
|
63
|
+
section p { text-wrap: pretty; }
|
|
64
|
+
section p + p, section p + ul, section ul + p, section p + .cmd, section .cmd + p { margin-top: 1em; }
|
|
65
|
+
main a { text-decoration-thickness: 1px; text-underline-offset: 4px; text-decoration-color: var(--accent); }
|
|
66
|
+
main a:hover { color: var(--accent); }
|
|
67
|
+
main ul.list { padding-left: 1.2em; display: grid; gap: .5em; }
|
|
68
|
+
main code { font: 500 .88em ui-monospace, "SF Mono", Menlo, monospace; padding: 2px 7px; border-radius: 7px; background: #e6e1d8; }
|
|
69
|
+
main .cmd { display: flex; align-items: center; gap: 4px; padding: 6px 6px 6px 18px; border-radius: 12px; background: #e6e1d8; }
|
|
70
|
+
main .cmd code { flex: 1; min-width: 0; overflow-x: auto; white-space: nowrap; padding: 8px 0; background: none; border-radius: 0; font-size: 16px; scrollbar-width: none; }
|
|
71
|
+
main .cmd code::-webkit-scrollbar { display: none; }
|
|
72
|
+
.copy { flex: none; display: inline-grid; place-items: center; width: 38px; height: 38px; padding: 0; border: 0; border-radius: 9px; background: none; color: var(--fg-muted); cursor: pointer; transition: color .2s, background-color .2s, transform .12s; }
|
|
73
|
+
.copy:hover { color: var(--fg); }
|
|
74
|
+
.copy:active { transform: scale(.97); }
|
|
75
|
+
.copy svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
|
|
76
|
+
.copy .ok { display: none; }
|
|
77
|
+
.copy.done .ok { display: block; color: #5b6551; animation: copy-ok .2s cubic-bezier(.22,1,.36,1); }
|
|
78
|
+
@keyframes copy-ok { from { opacity: 0; transform: scale(.6); } }
|
|
79
|
+
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation: none; transition: none; } }
|
|
80
|
+
.copy.done .ic { display: none; }
|
|
81
|
+
main strong { font-weight: 600; }
|
|
82
|
+
|
|
83
|
+
.footer .page { padding: 40px 0 56px; text-align: center; font-size: 13px; color: var(--fg-muted); }
|
|
84
|
+
.footer a { color: var(--fg); text-decoration: underline; text-decoration-color: var(--red); text-decoration-thickness: 1px; text-underline-offset: 3px; }
|
|
85
|
+
.footer a:hover { color: var(--red); }
|
|
86
|
+
|
|
87
|
+
@media (max-width: 620px) {
|
|
88
|
+
body { font-size: 17px; }
|
|
89
|
+
.page { width: calc(100% - 40px); }
|
|
90
|
+
.head { padding-top: 32px; }
|
|
91
|
+
main { padding: 80px 0; }
|
|
92
|
+
.lead { font-size: 19px; }
|
|
93
|
+
section { margin-top: 64px; }
|
|
94
|
+
main .cmd { padding-left: 14px; }
|
|
95
|
+
main .cmd code { font-size: 14px; }
|
|
96
|
+
}
|
|
97
|
+
</style>
|
|
98
|
+
</head>
|
|
99
|
+
<body>
|
|
100
|
+
<header class="page head">
|
|
101
|
+
<a class="mark word" href="/" aria-label="Nio, inici">ni<span class="bot" data-mood="idle" aria-hidden="true"><span class="eyes"><i></i><i></i></span></span></a>
|
|
102
|
+
<a class="enter" href="/app"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M14 4h4a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2h-4"/><path d="M9 16l4-4-4-4"/><path d="M13 12H4"/></svg>Entra al panell</a>
|
|
103
|
+
</header>
|
|
104
|
+
|
|
105
|
+
<main class="page">
|
|
106
|
+
<h1>Privadesa</h1>
|
|
107
|
+
<p class="lead">nio està fet perquè la teva feina no surti dels teus ordinadors. Aquí tens, sense lletra petita, què passa per nio.cat i què no.</p>
|
|
108
|
+
|
|
109
|
+
<section aria-labelledby="passa">
|
|
110
|
+
<h2 id="passa">Què passa per nio.cat</h2>
|
|
111
|
+
<ul class="list">
|
|
112
|
+
<li><strong>L’app.</strong> nio.cat serveix els fitxers de l’app web (com qualsevol web). No té comptes ni inicis de sessió.</li>
|
|
113
|
+
<li><strong>L’adreça de cada ordinador.</strong> Perquè el mòbil el trobi si l’adreça del túnel canvia, cada ordinador hi desa la seva adreça actual, signada amb una clau que només té ell. Només és «aquest ordinador → aquesta adreça». Caduca als 30 dies si no es renova, i es desactiva amb <code>nio directory off</code>.</li>
|
|
114
|
+
<li><strong>Res més.</strong> L’aparellament és directe entre el mòbil i l’ordinador: el codi va dins de l’enllaç, en una part que el navegador no envia a cap servidor.</li>
|
|
115
|
+
</ul>
|
|
116
|
+
</section>
|
|
117
|
+
|
|
118
|
+
<section aria-labelledby="no">
|
|
119
|
+
<h2 id="no">Què no hi passa mai</h2>
|
|
120
|
+
<ul class="list">
|
|
121
|
+
<li>Les teves converses amb els agents, els fitxers, la memòria i el registre d’accions: es queden a cada ordinador (<code>~/.nio</code>).</li>
|
|
122
|
+
<li>Les claus d’accés dels teus dispositius ni les claus d’API (Anthropic, OpenAI, ElevenLabs, Telegram): viuen als teus ordinadors.</li>
|
|
123
|
+
<li>L’àudio de la veu: va del mòbil al teu ordinador i d’allà al proveïdor que hagis triat.</li>
|
|
124
|
+
</ul>
|
|
125
|
+
</section>
|
|
126
|
+
|
|
127
|
+
<section aria-labelledby="altres">
|
|
128
|
+
<h2 id="altres">Altres serveis que hi intervenen</h2>
|
|
129
|
+
<ul class="list">
|
|
130
|
+
<li><strong>El túnel</strong> (Cloudflare, si el fas servir) porta el trànsit xifrat entre el mòbil i l’ordinador.</li>
|
|
131
|
+
<li><strong>Els avisos</strong> passen pel servei de notificacions del mòbil (Apple o Google), xifrats de punta a punta: ells no en veuen el text.</li>
|
|
132
|
+
<li><strong>Els proveïdors que tu triïs</strong> (Anthropic, OpenAI, ElevenLabs, Telegram) reben el que els envien els teus agents, amb les teves claus i segons les seves condicions.</li>
|
|
133
|
+
</ul>
|
|
134
|
+
</section>
|
|
135
|
+
|
|
136
|
+
<section aria-labelledby="control">
|
|
137
|
+
<h2 id="control">Tu tens el control</h2>
|
|
138
|
+
<p>Pots veure i revocar els dispositius amb accés des de l’app (Ordinadors), o tots alhora amb <code>nio reset-token</code>. Per esborrar-ho tot d’un ordinador, esborra la carpeta <code>~/.nio</code>.</p>
|
|
139
|
+
<p>Dubtes: <a href="mailto:hola@nio.cat">hola@nio.cat</a>.</p>
|
|
140
|
+
</section>
|
|
141
|
+
</main>
|
|
142
|
+
|
|
143
|
+
<footer class="footer">
|
|
144
|
+
<div class="page">© 2026 nio // un projecte de <a href="https://f.cat">f.cat</a> · <a href="mailto:hola@nio.cat">hola@nio.cat</a> · <a href="/privadesa">Privadesa</a></div>
|
|
145
|
+
</footer>
|
|
146
|
+
<script src="/bot.js" defer></script>
|
|
147
|
+
</body>
|
|
148
|
+
</html>
|
package/web/sw.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// nio · service worker: obre l'app encara que no hi hagi connexió i mostra els avisos.
|
|
2
|
-
const SHELL = 'nio-shell-
|
|
2
|
+
const SHELL = 'nio-shell-v20';
|
|
3
3
|
const FILES = ['/app', '/app/style.css', '/app/app.js', '/bot.css', '/bot.js', '/board.css', '/board.js', '/favicon.svg', '/manifest.webmanifest'];
|
|
4
4
|
|
|
5
5
|
self.addEventListener('install', (e) => {
|
|
@@ -37,16 +37,20 @@ self.addEventListener('message', (e) => {
|
|
|
37
37
|
}
|
|
38
38
|
});
|
|
39
39
|
|
|
40
|
-
//
|
|
40
|
+
// Normalment l'avís arriba xifrat de punta a punta amb el text dins (RFC 8291).
|
|
41
|
+
// Si arriba buit (nio antic), en demanem el detall a l'ordinador.
|
|
41
42
|
self.addEventListener('push', (e) => {
|
|
42
43
|
e.waitUntil((async () => {
|
|
43
|
-
let
|
|
44
|
-
try {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
let n = null;
|
|
45
|
+
try { n = e.data ? e.data.json() : null; } catch { n = null; }
|
|
46
|
+
if (!n) {
|
|
47
|
+
try {
|
|
48
|
+
const cfg = await (await (await caches.open('nio-cfg')).match('/cfg')).json();
|
|
49
|
+
[n] = await (await fetch(`${cfg.url}/api/notices`, { headers: { Authorization: `Bearer ${cfg.token}` } })).json();
|
|
50
|
+
} catch { /* sense detall: avís genèric */ }
|
|
51
|
+
}
|
|
52
|
+
const title = n?.title || 'nio', body = n?.body || 'Un agent et necessita', session = n?.session || null;
|
|
53
|
+
await self.registration.showNotification(title, { body, icon: '/icon-192.png', badge: '/badge.png', tag: session || n?.job || 'nio', renotify: true, data: { session, job: n?.job || null } });
|
|
50
54
|
})());
|
|
51
55
|
});
|
|
52
56
|
|