aimeat 1.32.0 → 1.32.1

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 (59) hide show
  1. package/dist/locales/en.json +19 -1
  2. package/dist/locales/fi.json +19 -1
  3. package/dist/public/css/views/profile.css +44 -0
  4. package/dist/public/css/views/secretary.css +127 -0
  5. package/dist/public/js/services/organisms.js +32 -0
  6. package/dist/public/llms-template.txt +5 -0
  7. package/dist/public/spa.html +23 -0
  8. package/dist/public/views/profile/organisms/workspace.js +206 -28
  9. package/dist/public/views/secretary.js +75 -0
  10. package/dist/src/cli/connect/tool-call.d.ts.map +1 -1
  11. package/dist/src/cli/connect/tool-call.js +72 -0
  12. package/dist/src/cli/connect/tool-call.js.map +1 -1
  13. package/dist/src/generated/api-types.d.ts +25 -0
  14. package/dist/src/generated/api-types.d.ts.map +1 -1
  15. package/dist/src/mcp/catalog/definitions.d.ts.map +1 -1
  16. package/dist/src/mcp/catalog/definitions.js +11 -1
  17. package/dist/src/mcp/catalog/definitions.js.map +1 -1
  18. package/dist/src/mcp/catalog/scopes.d.ts +1 -0
  19. package/dist/src/mcp/catalog/scopes.d.ts.map +1 -1
  20. package/dist/src/mcp/catalog/scopes.js +6 -0
  21. package/dist/src/mcp/catalog/scopes.js.map +1 -1
  22. package/dist/src/mcp/catalog/surfaces.d.ts.map +1 -1
  23. package/dist/src/mcp/catalog/surfaces.js +2 -0
  24. package/dist/src/mcp/catalog/surfaces.js.map +1 -1
  25. package/dist/src/routes/catalogue.d.ts +11 -0
  26. package/dist/src/routes/catalogue.d.ts.map +1 -1
  27. package/dist/src/routes/catalogue.js +14 -1
  28. package/dist/src/routes/catalogue.js.map +1 -1
  29. package/dist/src/routes/openrouter.d.ts +1 -0
  30. package/dist/src/routes/openrouter.d.ts.map +1 -1
  31. package/dist/src/routes/openrouter.js +15 -0
  32. package/dist/src/routes/openrouter.js.map +1 -1
  33. package/dist/src/routes/organisms.d.ts.map +1 -1
  34. package/dist/src/routes/organisms.js +4 -2
  35. package/dist/src/routes/organisms.js.map +1 -1
  36. package/dist/src/routes/portal.d.ts.map +1 -1
  37. package/dist/src/routes/portal.js +1 -0
  38. package/dist/src/routes/portal.js.map +1 -1
  39. package/dist/src/services/handbooks/agent.d.ts +4 -1
  40. package/dist/src/services/handbooks/agent.d.ts.map +1 -1
  41. package/dist/src/services/handbooks/agent.js +9 -0
  42. package/dist/src/services/handbooks/agent.js.map +1 -1
  43. package/dist/src/services/kpi-rollup.d.ts +53 -0
  44. package/dist/src/services/kpi-rollup.d.ts.map +1 -0
  45. package/dist/src/services/kpi-rollup.js +64 -0
  46. package/dist/src/services/kpi-rollup.js.map +1 -0
  47. package/dist/src/services/manifest-schema.d.ts +8 -1
  48. package/dist/src/services/manifest-schema.d.ts.map +1 -1
  49. package/dist/src/services/manifest-schema.js +48 -1
  50. package/dist/src/services/manifest-schema.js.map +1 -1
  51. package/dist/src/services/secretary.d.ts +33 -0
  52. package/dist/src/services/secretary.d.ts.map +1 -0
  53. package/dist/src/services/secretary.js +63 -0
  54. package/dist/src/services/secretary.js.map +1 -0
  55. package/dist/src/services/structure-overview.d.ts +27 -0
  56. package/dist/src/services/structure-overview.d.ts.map +1 -1
  57. package/dist/src/services/structure-overview.js +89 -2
  58. package/dist/src/services/structure-overview.js.map +1 -1
  59. package/package.json +1 -1
@@ -544,7 +544,17 @@
544
544
  "howItWorks": "How it works",
545
545
  "business": "For your business",
546
546
  "myCompany": "My Company",
547
- "companies": "Companies"
547
+ "companies": "Companies",
548
+ "secretary": "Secretary"
549
+ },
550
+ "secretary": {
551
+ "title": "Secretary",
552
+ "desc": "Your personal AIMEAT secretary — set up and ready to help.",
553
+ "ready": "Secretary is set up",
554
+ "notReady": "Your Secretary isn't set up yet. Configure OpenRouter in your profile settings to enable it.",
555
+ "identity": "Identity",
556
+ "scopes": "Permissions",
557
+ "soon": "Chat, schedule, and decisions are coming soon."
548
558
  },
549
559
  "companies": {
550
560
  "title": "Companies",
@@ -6859,6 +6869,8 @@
6859
6869
  "orPaste": "…or paste / drag an image into the editor",
6860
6870
  "saving": "Saving…",
6861
6871
  "pageSaved": "Document saved",
6872
+ "color": "Color",
6873
+ "noColor": "No color",
6862
6874
  "section": "Section",
6863
6875
  "sectionName": "Section name",
6864
6876
  "unsorted": "Unsorted",
@@ -6913,6 +6925,12 @@
6913
6925
  "descSources": "References this workspace draws on: memory entries, files, and knowledge packages. Pointers only; nothing is copied.",
6914
6926
  "descReview": "Gated publishes waiting for a human decision before they go live.",
6915
6927
  "ovRecent": "What happened here",
6928
+ "objectivesTitle": "Objectives",
6929
+ "kpiTarget": "target {t}",
6930
+ "kpiDeclared": "self-reported",
6931
+ "kpiDeclaredHint": "Self-reported — not computed from records",
6932
+ "objStatusMet": "met",
6933
+ "objStatusAbandoned": "abandoned",
6916
6934
  "ovShowAll": "Show all {n} →",
6917
6935
  "unseenHint": "Changed since your last visit",
6918
6936
  "forAgentsHint": "Copy a ready prompt that teaches an AI to use this workspace",
@@ -544,7 +544,17 @@
544
544
  "howItWorks": "Miten se toimii",
545
545
  "business": "Yrityksellesi",
546
546
  "myCompany": "Yritykseni",
547
- "companies": "Yritykset"
547
+ "companies": "Yritykset",
548
+ "secretary": "Sihteeri"
549
+ },
550
+ "secretary": {
551
+ "title": "Sihteeri",
552
+ "desc": "Henkilökohtainen AIMEAT-sihteerisi — pystytetty ja valmiina auttamaan.",
553
+ "ready": "Sihteeri on pystytetty",
554
+ "notReady": "Sihteeriäsi ei ole vielä pystytetty. Ota se käyttöön määrittämällä OpenRouter profiilisi asetuksissa.",
555
+ "identity": "Identiteetti",
556
+ "scopes": "Oikeudet",
557
+ "soon": "Chat, kalenteri ja päätökset tulossa pian."
548
558
  },
549
559
  "companies": {
550
560
  "title": "Yritykset",
@@ -6859,6 +6869,8 @@
6859
6869
  "orPaste": "…tai liitä / vedä kuva editoriin",
6860
6870
  "saving": "Tallennetaan…",
6861
6871
  "pageSaved": "Dokumentti tallennettu",
6872
+ "color": "Väri",
6873
+ "noColor": "Ei väriä",
6862
6874
  "section": "Osio",
6863
6875
  "sectionName": "Osion nimi",
6864
6876
  "unsorted": "Lajittelematon",
@@ -6913,6 +6925,12 @@
6913
6925
  "descSources": "Lähteet, joihin työtila nojaa: muistimerkinnät, tiedostot ja tietopaketit. Vain viittauksia; mitään ei kopioida.",
6914
6926
  "descReview": "Portitetut julkaisut, jotka odottavat ihmisen päätöstä ennen julkaisua.",
6915
6927
  "ovRecent": "Mitä täällä tapahtui",
6928
+ "objectivesTitle": "Tavoitteet",
6929
+ "kpiTarget": "tavoite {t}",
6930
+ "kpiDeclared": "itse ilmoitettu",
6931
+ "kpiDeclaredHint": "Itse ilmoitettu — ei laskettu tietueista",
6932
+ "objStatusMet": "saavutettu",
6933
+ "objStatusAbandoned": "hylätty",
6916
6934
  "ovShowAll": "Näytä kaikki {n} →",
6917
6935
  "unseenHint": "Muuttunut edellisen käyntisi jälkeen",
6918
6936
  "forAgentsHint": "Kopioi valmis kehote, joka opettaa tekoälyn käyttämään tätä työtilaa",
@@ -123,6 +123,10 @@
123
123
  * bordered/bolder DRAFT chip and the inline Edit button gets the card-fill
124
124
  * treatment (both were nearly invisible). Expanded fields (.pj-rec-fields .kv-row)
125
125
  * gain a dotted leader between label and value so each value maps to its key.
126
+ * v1.14.0 — 2026-06-23 — Multi-part document series niputus: .pj-doc-series-head/-name/-parts
127
+ * (collapsed series row in the doc index).
128
+ * v1.14.1 — 2026-06-23 — Optional color tags: .pj-tag-{red…gray} (→ --pj-tag theme tokens),
129
+ * .pj-colored left-rail accent, and the .pj-cp/.pj-cp-dot/-pop/-sw/-none swatch picker.
126
130
  */
127
131
  .pf-hero{background:var(--pf-hero-gradient);border-bottom:1px solid var(--border,#E5E7EB)}
128
132
  .pf-hero-inner{max-width:1000px;margin:0 auto;padding:2rem 1.5rem 0}
@@ -2435,6 +2439,30 @@
2435
2439
  .pj-doc-item:hover { background: var(--bg-surface); }
2436
2440
  .pj-doc-item.active { background: var(--accent-subtle); box-shadow: inset 3px 0 0 var(--accent); }
2437
2441
  .pj-doc-item .pj-doc-link { flex: 1; font-size: 0.85rem; color: var(--text); }
2442
+ /* Multi-part document series collapsed under one row (niputus) — in the doc index. */
2443
+ .pj-doc-series-head { display: flex; align-items: center; gap: 0.3rem; width: 100%; padding: 0.15rem 0.3rem; border: 0; background: none; border-radius: 6px; cursor: pointer; text-align: left; font-size: 0.85rem; font-weight: 600; color: var(--text); }
2444
+ .pj-doc-series-head:hover { background: var(--bg-surface); }
2445
+ .pj-doc-series-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2446
+ .pj-doc-series-parts { margin-left: 0.85rem; border-left: 1px solid var(--border); padding-left: 0.4rem; }
2447
+ /* ── Optional color tags (preset palette → theme tokens; never a free hex). A .pj-tag-{key} class
2448
+ * sets --pj-tag; structural classes consume it, so colors flip correctly with the theme. ── */
2449
+ .pj-tag-red { --pj-tag: var(--danger); }
2450
+ .pj-tag-orange { --pj-tag: var(--orange); }
2451
+ .pj-tag-yellow { --pj-tag: var(--yellow); }
2452
+ .pj-tag-green { --pj-tag: var(--success); }
2453
+ .pj-tag-blue { --pj-tag: var(--blue); }
2454
+ .pj-tag-purple { --pj-tag: var(--purple); }
2455
+ .pj-tag-gray { --pj-tag: var(--badge-node-bg); }
2456
+ .pj-colored { border-left: 3px solid var(--pj-tag); padding-left: 0.45rem; }
2457
+ .pj-cp { position: relative; display: inline-flex; align-items: center; }
2458
+ .pj-cp-dot { width: 11px; height: 11px; border-radius: 50%; border: 0; padding: 0; cursor: pointer; flex-shrink: 0; opacity: 0.55; }
2459
+ .pj-cp-dot:hover { opacity: 1; }
2460
+ .pj-cp-dot.pj-colored { border-left: 0; padding-left: 0; background: var(--pj-tag); opacity: 1; }
2461
+ .pj-cp-empty { background: transparent; border: 1.5px solid var(--border); }
2462
+ .pj-cp-pop { position: absolute; top: 100%; left: 0; z-index: 30; display: flex; gap: 4px; padding: 5px; margin-top: 3px; background: var(--card); border: 1px solid var(--border); border-radius: 8px; box-shadow: var(--shadow-md); }
2463
+ .pj-cp-sw { width: 16px; height: 16px; border-radius: 50%; border: 0; padding: 0; cursor: pointer; }
2464
+ .pj-cp-sw.pj-colored { border-left: 0; padding-left: 0; background: var(--pj-tag); }
2465
+ .pj-cp-none { background: var(--bg-dim); color: var(--muted); border: 1px solid var(--border); font-size: 0.7rem; line-height: 14px; text-align: center; }
2438
2466
  .input-xs { padding: 0.15rem 0.35rem; font-size: 0.78rem; height: auto; }
2439
2467
  .pj-mini { font-size: 0.6rem; padding: 0 0.25rem; vertical-align: middle; }
2440
2468
  .pj-doc-toolbar { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.4rem; }
@@ -2477,6 +2505,22 @@
2477
2505
  .pj-ov { display: flex; flex-direction: column; gap: 0.6rem; }
2478
2506
  .pj-ov-strip { border: 1px solid var(--border); border-radius: 10px; padding: 0.5rem 0.6rem; background: var(--card); }
2479
2507
  .pj-ov-strip-title { font-weight: 600; font-size: 0.85rem; color: var(--muted); margin-bottom: 0.3rem; }
2508
+
2509
+ /* ── Measurability objectives card (workspace header) ── */
2510
+ .pj-obj { display: flex; flex-direction: column; gap: 0.5rem; margin: 0.75rem 0; }
2511
+ .pj-obj-title { font-weight: 700; font-size: 0.9rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.03em; }
2512
+ .pj-obj-card { border: 1px solid var(--border); border-radius: 10px; background: var(--card); padding: 0.6rem 0.75rem; }
2513
+ .pj-obj-statement { font-weight: 600; color: var(--text); display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
2514
+ .pj-obj-status { font-size: 0.7rem; }
2515
+ .pj-obj-why { color: var(--muted); font-size: 0.85rem; margin-top: 0.2rem; }
2516
+ .pj-obj-kpis { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 0.5rem; }
2517
+ .pj-obj-kpi { display: flex; align-items: baseline; gap: 0.4rem; padding: 0.3rem 0.55rem; border: 1px solid var(--border); border-left: 3px solid var(--border); border-radius: 8px; background: var(--bg-dim); font-size: 0.85rem; }
2518
+ .pj-obj-kpi.met { border-left-color: var(--success); }
2519
+ .pj-obj-kpi.off { border-left-color: var(--warn); }
2520
+ .pj-obj-kpi-name { font-weight: 600; color: var(--text); }
2521
+ .pj-obj-kpi-val { color: var(--text); font-variant-numeric: tabular-nums; }
2522
+ .pj-obj-kpi-target { color: var(--muted); font-size: 0.78rem; }
2523
+ .pj-obj-kpi-declared { color: var(--muted); font-size: 0.72rem; font-style: italic; }
2480
2524
  .pj-ov-event { display: flex; align-items: center; gap: 0.45rem; width: 100%; padding: 0.2rem 0.25rem; border: 0; background: none; border-radius: 6px; cursor: pointer; text-align: left; font-size: 0.85rem; color: var(--text); }
2481
2525
  .pj-ov-event:hover { background: var(--bg-dim); }
2482
2526
  .pj-ov-event-who { font-weight: 600; flex-shrink: 0; }
@@ -0,0 +1,127 @@
1
+ /**
2
+ * @file secretary.css
3
+ * @description Styles for the Secretary view (Phase 0 shell) and the header Secretary button.
4
+ * Prefix `sec-`. Dark/light safe — uses theme tokens only, no hardcoded colors.
5
+ * @version-history v0.1.0 — 2026-06-23 — Phase 0: hero + provisioning status card + header button/icon.
6
+ */
7
+
8
+ .sec {
9
+ max-width: 720px;
10
+ margin: 0 auto;
11
+ padding: 2rem 1rem;
12
+ }
13
+
14
+ .sec-hero {
15
+ display: flex;
16
+ align-items: center;
17
+ gap: 1rem;
18
+ margin-bottom: 1.5rem;
19
+ }
20
+
21
+ .sec-hero-icon {
22
+ color: var(--accent);
23
+ display: inline-flex;
24
+ flex: 0 0 auto;
25
+ }
26
+
27
+ .sec-title {
28
+ margin: 0;
29
+ font-size: 1.6rem;
30
+ color: var(--text);
31
+ }
32
+
33
+ .sec-desc {
34
+ margin: 0.25rem 0 0;
35
+ color: var(--text-dim);
36
+ }
37
+
38
+ .sec-empty {
39
+ color: var(--text-dim);
40
+ padding: 2rem 0;
41
+ text-align: center;
42
+ }
43
+
44
+ .sec-card {
45
+ background: var(--card);
46
+ border: 1px solid var(--border);
47
+ border-radius: var(--radius-md, 12px);
48
+ padding: 1.25rem 1.5rem;
49
+ }
50
+
51
+ .sec-status {
52
+ display: inline-flex;
53
+ align-items: center;
54
+ gap: 0.5rem;
55
+ font-weight: 600;
56
+ color: var(--text);
57
+ margin-bottom: 1rem;
58
+ }
59
+
60
+ .sec-dot {
61
+ width: 9px;
62
+ height: 9px;
63
+ border-radius: 50%;
64
+ background: var(--success, #3fb950);
65
+ box-shadow: 0 0 0 3px var(--accent-subtle);
66
+ }
67
+
68
+ .sec-meta {
69
+ display: grid;
70
+ grid-template-columns: auto 1fr;
71
+ gap: 0.5rem 1rem;
72
+ margin: 0 0 1rem;
73
+ }
74
+
75
+ .sec-meta dt {
76
+ color: var(--text-dim);
77
+ font-size: 0.85rem;
78
+ }
79
+
80
+ .sec-meta dd {
81
+ margin: 0;
82
+ }
83
+
84
+ .sec-meta code {
85
+ font-size: 0.85rem;
86
+ color: var(--text);
87
+ word-break: break-all;
88
+ }
89
+
90
+ .sec-scopes {
91
+ display: flex;
92
+ flex-wrap: wrap;
93
+ gap: 0.35rem;
94
+ }
95
+
96
+ .sec-scope {
97
+ font-size: 0.75rem;
98
+ padding: 0.1rem 0.5rem;
99
+ border-radius: var(--radius-xs, 6px);
100
+ background: var(--bg-dim);
101
+ border: 1px solid var(--border);
102
+ color: var(--text-dim);
103
+ }
104
+
105
+ .sec-soon {
106
+ margin: 0;
107
+ color: var(--text-dim);
108
+ font-size: 0.9rem;
109
+ }
110
+
111
+ /* Header Secretary button (in .topnav) */
112
+ .sec-nav-link {
113
+ display: inline-flex;
114
+ align-items: center;
115
+ gap: 0.35rem;
116
+ }
117
+
118
+ .sec-nav-icon {
119
+ display: inline-flex;
120
+ width: 18px;
121
+ height: 18px;
122
+ }
123
+
124
+ .sec-nav-icon svg {
125
+ width: 18px;
126
+ height: 18px;
127
+ }
@@ -6,6 +6,8 @@
6
6
  * resolve gate approvals.
7
7
  * @usage import * as orgService from '/js/services/organisms.js';
8
8
  * @version-history
9
+ * v1.4.0 — 2026-06-23 — Optional color tags: getAllColors()/saveColors() read/write the per-item
10
+ * color map at …w.{ws}.meta.colors.{type} (mirrors getAllSections/saveSections).
9
11
  * v1.3.0 — 2026-06-22 — Batch endpoints: discoverWorkspaces({include:'enrichment'}) folds the
10
12
  * per-workspace fan-out; new getWaiting() (home widget) and listCommentsBatch() (workspace comments).
11
13
  * v1.2.1 — 2026-06-21 — Decode HTML entities in workspace names on read (listWorkspaces +
@@ -552,6 +554,17 @@ export async function getWorkspaceOverview(orgId, wsId) {
552
554
  } catch { return ''; }
553
555
  }
554
556
 
557
+ /** Full workspace overview payload in ONE call: the OKF markdown PLUS the measurability `objectives`
558
+ * (each KPI with its resolved `current` — computed from records where the source allows, else the
559
+ * declared value). Lets a caller render both the table-of-contents seed and the objectives card
560
+ * without two requests to the same endpoint. Returns safe empties on failure. */
561
+ export async function getWorkspaceOverviewFull(orgId, wsId) {
562
+ try {
563
+ const r = await apiGet(`/v1/organisms/${encodeURIComponent(orgId)}/workspace/overview?ws=${encodeURIComponent(wsId)}`);
564
+ return { markdown: r?.data?.markdown || '', objectives: Array.isArray(r?.data?.objectives) ? r.data.objectives : [], readable: r?.data?.readable !== false };
565
+ } catch { return { markdown: '', objectives: [], readable: false }; }
566
+ }
567
+
555
568
  /** Deterministic GRAPH of the whole organism (workspaces → spaces + counts/last-activity, members,
556
569
  * agents) — the data behind the interactive mindmap. Returns null on failure. */
557
570
  export async function getOrganismGraph(orgId) {
@@ -1228,6 +1241,25 @@ export async function saveSections(orgId, wsId, typeName, sections) {
1228
1241
  return apiPost('/v1/memory', { key: `${wsRoot(orgId, wsId)}.meta.sections.${typeName}`, value: { sections }, visibility: 'private' });
1229
1242
  }
1230
1243
 
1244
+ /** Read the optional per-item color tags for every space → { typeName: { instanceId: colorKey } }.
1245
+ * Mirrors getAllSections; stored at …w.{wsId}.meta.colors.{typeName} = { colors: { id: key } }. */
1246
+ export async function getAllColors(orgId, wsId) {
1247
+ const out = {};
1248
+ try {
1249
+ const resp = await apiGet(`/v1/memory?prefix=${encodeURIComponent(`${wsRoot(orgId, wsId)}.meta.colors.`)}`);
1250
+ for (const it of (resp?.data?.items || [])) {
1251
+ const m = String(it.key || '').match(/\.meta\.colors\.(.+)$/);
1252
+ if (m) out[m[1]] = (it.value && typeof it.value.colors === 'object' && it.value.colors) || {};
1253
+ }
1254
+ } catch { /* none yet */ }
1255
+ return out;
1256
+ }
1257
+
1258
+ /** Persist the per-item color map for one space (…w.{wsId}.meta.colors.{typeName}). */
1259
+ export async function saveColors(orgId, wsId, typeName, colors) {
1260
+ return apiPost('/v1/memory', { key: `${wsRoot(orgId, wsId)}.meta.colors.${typeName}`, value: { colors }, visibility: 'private' });
1261
+ }
1262
+
1231
1263
  /* ── Sources: references the workspace draws on (memory / storage / knowledge). Pointers only —
1232
1264
  * the referenced data is never copied or moved, it stays where it lives. Stored at
1233
1265
  * organism.{id}.meta.sources = { sources: [{ id, type, label, key?, ownerGaii?, packageId?, external }] }. */
@@ -401,6 +401,11 @@ for completeness but are NOT needed for typical app building:
401
401
  ALL its content. To build an agent that PROCESSES a workspace (reads requests -> writes results),
402
402
  it carries a "contract" (inputs/outputs/lifecycle) — see the guide at
403
403
  GET /v1/agents/me/handbook/appdev (Workspace contracts section), full text in
404
+ docs/agent-workspace-contracts.md. An organism can also state WHY it exists and HOW success is
405
+ measured via an optional, domain-agnostic measurability convention — a manifest-level objectives[]
406
+ with KPIs (kind value/cost/roi/outcome/quality; source can sum/count the organism's own records),
407
+ objectType servesObjective, and a per-record _meta update note. Units are the domain's (€, viable
408
+ plots, confirmed hypotheses, closed deals); all optional. See "Recording purpose & value" in
404
409
  docs/agent-workspace-contracts.md.
405
410
  - Micro-memory, App store, Matches, Push notifications
406
411
 
@@ -102,6 +102,7 @@
102
102
  <link rel="stylesheet" href="/css/views/public-knowledge-viewer.css">
103
103
  <link rel="stylesheet" href="/css/views/public-workspace-viewer.css">
104
104
  <link rel="stylesheet" href="/css/views/start.css">
105
+ <link rel="stylesheet" href="/css/views/secretary.css">
105
106
  <script type="importmap">
106
107
  {
107
108
  "imports": {
@@ -344,6 +345,7 @@
344
345
  '/v1/how-it-works': () => import('/views/how-it-works.js' + B),
345
346
  '/v1/business': () => import('/views/business.js' + B),
346
347
  '/v1/profile': () => import('/views/profile.js' + B),
348
+ '/v1/secretary': () => import('/views/secretary.js' + B),
347
349
  '/v1/my-company': () => import('/views/my-company.js' + B),
348
350
  '/v1/companies': () => import('/views/companies.js' + B),
349
351
  '/v1/app-grant': () => import('/views/app-grant.js' + B),
@@ -527,6 +529,7 @@
527
529
  const [session, setSession] = useState(null);
528
530
  const [morsels, setMorsels] = useState(null);
529
531
  const [menuOpen, setMenuOpen] = useState(false);
532
+ const [secretaryAvailable, setSecretaryAvailable] = useState(false);
530
533
  // Header nav config: { order, hidden }. Defaults to all visible in declared order
531
534
  // until the public /v1/site/header-nav fetch resolves (no flash of empty nav).
532
535
  const [navConfig, setNavConfig] = useState({ order: PUBLIC_NAV_LINKS.map(l => l.id), hidden: [] });
@@ -573,6 +576,16 @@
573
576
  .catch(e => console.warn('Wallet fetch failed:', e.message));
574
577
  }, [session]);
575
578
 
579
+ // The Secretary becomes available once the owner has configured OpenRouter (the key powers
580
+ // the Secretary's brain). The button is pinned in the header while that holds.
581
+ useEffect(() => {
582
+ if (!session) { setSecretaryAvailable(false); return; }
583
+ fetch('/v1/openrouter/settings', { headers: { 'Authorization': 'Bearer ' + session.jwt } })
584
+ .then(r => r.json())
585
+ .then(d => { if (d.ok) setSecretaryAvailable(!!(d.data && d.data.hasApiKey)); })
586
+ .catch(() => {});
587
+ }, [session]);
588
+
576
589
  const handleLangSwitch = (lang) => {
577
590
  switchLocale(lang);
578
591
  };
@@ -617,6 +630,16 @@
617
630
  class="${link.matchActive !== false && currentPath === link.path ? 'active' : ''}">${t(link.key) || link.fallback || link.key}</a>
618
631
  `)}
619
632
  ${session && html`
633
+ ${secretaryAvailable && html`
634
+ <a href="/v1/secretary" class="sec-nav-link ${currentPath === '/v1/secretary' ? 'active' : ''}"
635
+ title=${t('nav.secretary')} onClick=${(e) => { handleNav(e, '/v1/secretary'); setMenuOpen(false); }}>
636
+ <span class="sec-nav-icon"><svg viewBox="0 0 24 24" aria-hidden="true">
637
+ <path d="M12 20.6C6.4 16.7 3.4 13.2 3.4 9.2 3.4 6.5 5.4 4.6 7.9 4.6c1.7 0 3.2.9 4.1 2.4.9-1.5 2.4-2.4 4.1-2.4 2.5 0 4.5 1.9 4.5 4.6 0 4-3 7.5-8.6 11.4z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/>
638
+ <path d="M16 8l-5.6 5.6M10.4 13.6l-1.5 2.4 2.4-1.5" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
639
+ </svg></span>
640
+ ${t('nav.secretary')}
641
+ </a>
642
+ `}
620
643
  <a href="/app-catalog.html" target="_blank" onClick=${() => setMenuOpen(false)} class="${currentPath === '/app-catalog.html' ? 'active' : ''}">${t('nav.apps') || 'Apps'}</a>
621
644
  <a href="/v1/profile" class="profile-link visible ${currentPath === '/v1/profile' ? 'active' : ''}" onClick=${(e) => { handleNav(e, '/v1/profile'); setMenuOpen(false); }}>
622
645
  ${t('nav.profile')}