@nosserb/prmt 0.0.0-stage → 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 (166) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +191 -2
  3. package/bin/prmt.mjs +174 -0
  4. package/dist/assets/AuditModule-BOO_iRy8.js +1 -0
  5. package/dist/assets/Audits-yUBm0EpW.js +1 -0
  6. package/dist/assets/BrainPage-FIWO_9Nc.js +3 -0
  7. package/dist/assets/BrainPage-XQCEGRc5.css +1 -0
  8. package/dist/assets/Carte-CPuiR1Ha.css +1 -0
  9. package/dist/assets/Carte-xycu1mQn.js +1 -0
  10. package/dist/assets/Dashboard-DKvCQcX8.js +2 -0
  11. package/dist/assets/Dashboard-XdhC6_hM.css +1 -0
  12. package/dist/assets/Field-C7c2mCbb.js +2 -0
  13. package/dist/assets/Journal-BLIIlh80.css +1 -0
  14. package/dist/assets/Journal-BaelFjwV.js +1 -0
  15. package/dist/assets/Landing-B2jxV3zs.css +1 -0
  16. package/dist/assets/Landing-Dr612Zdu.js +26 -0
  17. package/dist/assets/Register-DA_Aq5f8.js +1 -0
  18. package/dist/assets/Reports-8wfDNSxH.js +1 -0
  19. package/dist/assets/Reports-B8GXN95N.css +1 -0
  20. package/dist/assets/SavoirPage-BTxbH3LY.css +1 -0
  21. package/dist/assets/SavoirPage-Dp3eUCHp.js +1 -0
  22. package/dist/assets/SettingsPage-BElIP46c.css +1 -0
  23. package/dist/assets/SettingsPage-BiQbnw3o.js +1 -0
  24. package/dist/assets/Shell-BAyHVljp.css +1 -0
  25. package/dist/assets/Shell-CQRlx9-V.js +2 -0
  26. package/dist/assets/Table-DUmGel5e.js +1 -0
  27. package/dist/assets/Tabs-CfhbELZk.js +1 -0
  28. package/dist/assets/Terminal-Bv1IJfi9.js +3 -0
  29. package/dist/assets/Terminal-CEfj38NV.css +1 -0
  30. package/dist/assets/arrow-right-Cx3pAEhl.js +1 -0
  31. package/dist/assets/auditViews-BMza2Sdz.js +1 -0
  32. package/dist/assets/auditViews-BePWj26h.css +1 -0
  33. package/dist/assets/bricolage-grotesque-latin-ext-opsz-normal-IcJDqblK.woff2 +0 -0
  34. package/dist/assets/bricolage-grotesque-latin-opsz-normal-Cre6nC2_.woff2 +0 -0
  35. package/dist/assets/bricolage-grotesque-vietnamese-opsz-normal-D9N6E8K1.woff2 +0 -0
  36. package/dist/assets/charts-BcijSJ4J.js +1 -0
  37. package/dist/assets/circle-stop-C33BAcKW.js +1 -0
  38. package/dist/assets/command-ChA49Xk7.js +1 -0
  39. package/dist/assets/command-Ck4pdQCv.css +1 -0
  40. package/dist/assets/download-CeuKOenD.js +1 -0
  41. package/dist/assets/eye-CXXHBBpr.js +1 -0
  42. package/dist/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  43. package/dist/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  44. package/dist/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  45. package/dist/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  46. package/dist/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  47. package/dist/assets/habillage-B5mMBadn.js +1 -0
  48. package/dist/assets/index-BH1etemR.css +1 -0
  49. package/dist/assets/index-COSiM-R9.js +14 -0
  50. package/dist/assets/jetbrains-mono-cyrillic-ext-wght-normal-EocZY2iu.woff2 +0 -0
  51. package/dist/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
  52. package/dist/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
  53. package/dist/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  54. package/dist/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  55. package/dist/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
  56. package/dist/assets/layers-2x-Bpkbi35X.png +0 -0
  57. package/dist/assets/layers-BWBAp2CZ.png +0 -0
  58. package/dist/assets/markdown-DF87LQh1.css +1 -0
  59. package/dist/assets/markdown-o5sMhPHN.js +4 -0
  60. package/dist/assets/marker-icon-hN30_KVU.png +0 -0
  61. package/dist/assets/play-Daw4K_73.js +1 -0
  62. package/dist/assets/plus-D_QHm8ud.js +1 -0
  63. package/dist/assets/trash-DLpHM-s8.js +1 -0
  64. package/dist/assets/web-6j1-aY67.js +1 -0
  65. package/dist/favicon.svg +1 -0
  66. package/dist/index.html +18 -0
  67. package/docs/ANALYSE.md +87 -0
  68. package/docs/SECURITE.md +188 -0
  69. package/lib/scripts/lier.js +15 -0
  70. package/lib/scripts/searxng.js +91 -0
  71. package/lib/server/app.js +444 -0
  72. package/lib/server/audits/modules/local.js +429 -0
  73. package/lib/server/audits/modules/network.js +331 -0
  74. package/lib/server/audits/modules/web.js +302 -0
  75. package/lib/server/audits/net.js +141 -0
  76. package/lib/server/audits/registry.js +202 -0
  77. package/lib/server/audits/types.js +58 -0
  78. package/lib/server/brain/brain.js +624 -0
  79. package/lib/server/brain/conversations.js +82 -0
  80. package/lib/server/brain/ollama.js +197 -0
  81. package/lib/server/brain/prompt.js +188 -0
  82. package/lib/server/brain/provider.js +1 -0
  83. package/lib/server/cli/bridge.js +292 -0
  84. package/lib/server/cli/pont.js +62 -0
  85. package/lib/server/config.js +68 -0
  86. package/lib/server/engine/analyzer.js +205 -0
  87. package/lib/server/engine/catalog.js +229 -0
  88. package/lib/server/engine/decoder.js +91 -0
  89. package/lib/server/engine/engine.js +194 -0
  90. package/lib/server/engine/paths.js +129 -0
  91. package/lib/server/engine/policy.js +397 -0
  92. package/lib/server/engine/relax.js +10 -0
  93. package/lib/server/engine/runner.js +193 -0
  94. package/lib/server/errors.js +23 -0
  95. package/lib/server/http/guard.js +75 -0
  96. package/lib/server/http/router.js +154 -0
  97. package/lib/server/http/static.js +76 -0
  98. package/lib/server/index.js +40 -0
  99. package/lib/server/journal.js +139 -0
  100. package/lib/server/lien.js +33 -0
  101. package/lib/server/savoir/base.js +284 -0
  102. package/lib/server/savoir/lecture.js +406 -0
  103. package/lib/server/savoir/memoire.js +115 -0
  104. package/lib/server/search/categories.js +179 -0
  105. package/lib/server/search/fiche.js +326 -0
  106. package/lib/server/search/lieux.js +134 -0
  107. package/lib/server/search/overpass.js +216 -0
  108. package/lib/server/search/searxng.js +223 -0
  109. package/lib/server/search/tiles.js +103 -0
  110. package/lib/server/status.js +157 -0
  111. package/lib/server/stores.js +141 -0
  112. package/lib/server/telephone/acces.js +457 -0
  113. package/lib/server/telephone/certificat.js +79 -0
  114. package/lib/server/telephone/qr.js +308 -0
  115. package/lib/shared/analysis.js +40 -0
  116. package/lib/shared/schemas.js +105 -0
  117. package/lib/shared/types.js +10 -0
  118. package/package.json +81 -4
  119. package/searxng/settings.yml +35 -0
  120. package/tui/Modelfile +23 -0
  121. package/tui/README.md +103 -0
  122. package/tui/prmt_tui.py +65 -0
  123. package/tui/promethee/__init__.py +1 -0
  124. package/tui/promethee/__main__.py +4 -0
  125. package/tui/promethee/accueil.py +66 -0
  126. package/tui/promethee/agents.py +220 -0
  127. package/tui/promethee/app/__init__.py +4 -0
  128. package/tui/promethee/app/application.py +299 -0
  129. package/tui/promethee/app/clavier.py +99 -0
  130. package/tui/promethee/app/commandes_slash.py +164 -0
  131. package/tui/promethee/app/contexte.py +188 -0
  132. package/tui/promethee/app/definitions.py +518 -0
  133. package/tui/promethee/app/ecran.py +177 -0
  134. package/tui/promethee/app/envoi.py +141 -0
  135. package/tui/promethee/app/historique.py +373 -0
  136. package/tui/promethee/app/modele.py +108 -0
  137. package/tui/promethee/app/notes.py +63 -0
  138. package/tui/promethee/app/outils.py +589 -0
  139. package/tui/promethee/app/plateforme.py +409 -0
  140. package/tui/promethee/app/saisie.py +100 -0
  141. package/tui/promethee/app/taches.py +92 -0
  142. package/tui/promethee/app/tour.py +539 -0
  143. package/tui/promethee/app/validation.py +210 -0
  144. package/tui/promethee/appels.py +35 -0
  145. package/tui/promethee/commandes.py +154 -0
  146. package/tui/promethee/completion.py +140 -0
  147. package/tui/promethee/config.py +12 -0
  148. package/tui/promethee/consignes.py +278 -0
  149. package/tui/promethee/conversation.py +94 -0
  150. package/tui/promethee/etat.py +253 -0
  151. package/tui/promethee/fichiers.py +354 -0
  152. package/tui/promethee/journal.py +116 -0
  153. package/tui/promethee/logo.py +132 -0
  154. package/tui/promethee/memoire.py +50 -0
  155. package/tui/promethee/ollama.py +52 -0
  156. package/tui/promethee/plan.py +91 -0
  157. package/tui/promethee/politique.py +429 -0
  158. package/tui/promethee/preferences.py +65 -0
  159. package/tui/promethee/presse_papiers.py +44 -0
  160. package/tui/promethee/recherche.py +581 -0
  161. package/tui/promethee/securite.py +204 -0
  162. package/tui/promethee/shell.py +190 -0
  163. package/tui/promethee/textes.py +190 -0
  164. package/tui/promethee/theme.py +92 -0
  165. package/tui/promethee/widgets.py +493 -0
  166. package/tui/requirements.txt +1 -0
@@ -0,0 +1,308 @@
1
+ /**
2
+ * QR code, sans dépendance : octets, correction d'erreur M (15 %), versions 1 à 10, masque choisi par pénalité.
3
+ * Assez pour l'adresse d'appairage du téléphone (une soixantaine d'octets, version 4 ou 5). Suit la norme ISO/IEC 18004
4
+ * dans l'ordre de la construction de référence de Project Nayuki : motifs fixes, données et correction entrelacées, masque.
5
+ */
6
+ const MAX_VERSION = 10;
7
+ /** Correction M : codes de correction par bloc et nombre de blocs, par version (index 0 inutilisé). */
8
+ const ECC_PER_BLOCK = [-1, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26];
9
+ const BLOCKS = [-1, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5];
10
+ /** Bits de format du niveau M. */
11
+ const FORMAT_M = 0;
12
+ /** Modules utilisables pour les données et la correction, motifs fixes retirés. */
13
+ export function rawDataModules(version) {
14
+ let n = (16 * version + 128) * version + 64;
15
+ if (version >= 2) {
16
+ const align = Math.floor(version / 7) + 2;
17
+ n -= (25 * align - 10) * align - 55;
18
+ if (version >= 7)
19
+ n -= 36;
20
+ }
21
+ return n;
22
+ }
23
+ export const dataCodewords = (version) => Math.floor(rawDataModules(version) / 8) - (ECC_PER_BLOCK[version] ?? 0) * (BLOCKS[version] ?? 0);
24
+ /** Multiplication dans GF(256), polynôme 0x11D. */
25
+ function gfMul(x, y) {
26
+ let z = 0;
27
+ for (let i = 7; i >= 0; i--) {
28
+ z = (z << 1) ^ ((z >>> 7) * 0x11d);
29
+ z ^= ((y >>> i) & 1) * x;
30
+ }
31
+ return z & 0xff;
32
+ }
33
+ function rsDivisor(degree) {
34
+ const result = new Array(degree).fill(0);
35
+ result[degree - 1] = 1;
36
+ let root = 1;
37
+ for (let i = 0; i < degree; i++) {
38
+ for (let j = 0; j < degree; j++) {
39
+ result[j] = gfMul(result[j] ?? 0, root);
40
+ if (j + 1 < degree)
41
+ result[j] = (result[j] ?? 0) ^ (result[j + 1] ?? 0);
42
+ }
43
+ root = gfMul(root, 0x02);
44
+ }
45
+ return result;
46
+ }
47
+ /** Codes de correction Reed-Solomon d'un bloc. */
48
+ export function rsRemainder(data, degree) {
49
+ const divisor = rsDivisor(degree);
50
+ const result = new Array(degree).fill(0);
51
+ for (const b of data) {
52
+ const factor = b ^ (result.shift() ?? 0);
53
+ result.push(0);
54
+ divisor.forEach((coef, i) => {
55
+ result[i] = (result[i] ?? 0) ^ gfMul(coef, factor);
56
+ });
57
+ }
58
+ return result;
59
+ }
60
+ /** Les 15 bits de format (niveau et masque), code BCH et masque fixe compris. */
61
+ export function formatBits(level, mask) {
62
+ const data = (level << 3) | mask;
63
+ let rem = data;
64
+ for (let i = 0; i < 10; i++)
65
+ rem = (rem << 1) ^ ((rem >>> 9) * 0x537);
66
+ return ((data << 10) | (rem & 0x3ff)) ^ 0x5412;
67
+ }
68
+ /** Les 18 bits de version (7 et plus). */
69
+ export function versionBits(version) {
70
+ let rem = version;
71
+ for (let i = 0; i < 12; i++)
72
+ rem = (rem << 1) ^ ((rem >>> 11) * 0x1f25);
73
+ return (version << 12) | (rem & 0xfff);
74
+ }
75
+ export function alignmentPositions(version) {
76
+ if (version === 1)
77
+ return [];
78
+ const count = Math.floor(version / 7) + 2;
79
+ const size = version * 4 + 17;
80
+ const step = Math.floor((version * 8 + count * 3 + 5) / (count * 4 - 4)) * 2;
81
+ const result = [6];
82
+ for (let pos = size - 7; result.length < count; pos -= step)
83
+ result.splice(1, 0, pos);
84
+ return result;
85
+ }
86
+ /** Données en mode octet, bourrage compris, puis blocs de correction entrelacés. */
87
+ function codewords(bytes, version) {
88
+ const capacity = dataCodewords(version) * 8;
89
+ const bits = [];
90
+ const put = (value, length) => {
91
+ for (let i = length - 1; i >= 0; i--)
92
+ bits.push((value >>> i) & 1);
93
+ };
94
+ put(0b0100, 4);
95
+ put(bytes.length, version <= 9 ? 8 : 16);
96
+ for (const b of bytes)
97
+ put(b, 8);
98
+ put(0, Math.min(4, capacity - bits.length));
99
+ put(0, (8 - (bits.length % 8)) % 8);
100
+ for (let pad = 0xec; bits.length < capacity; pad ^= 0xec ^ 0x11)
101
+ put(pad, 8);
102
+ const data = [];
103
+ for (let i = 0; i < bits.length; i += 8)
104
+ data.push(bits.slice(i, i + 8).reduce((a, b) => (a << 1) | b, 0));
105
+ const blocks = BLOCKS[version] ?? 1;
106
+ const eccLength = ECC_PER_BLOCK[version] ?? 0;
107
+ const raw = Math.floor(rawDataModules(version) / 8);
108
+ const shortBlocks = blocks - (raw % blocks);
109
+ const shortLength = Math.floor(raw / blocks);
110
+ const all = [];
111
+ for (let i = 0, k = 0; i < blocks; i++) {
112
+ const block = data.slice(k, k + shortLength - eccLength + (i < shortBlocks ? 0 : 1));
113
+ k += block.length;
114
+ const ecc = rsRemainder(block, eccLength);
115
+ if (i < shortBlocks)
116
+ block.push(0);
117
+ all.push([...block, ...ecc]);
118
+ }
119
+ const result = [];
120
+ for (let i = 0; i < (all[0]?.length ?? 0); i++)
121
+ all.forEach((block, j) => {
122
+ if (i !== shortLength - eccLength || j >= shortBlocks)
123
+ result.push(block[i] ?? 0);
124
+ });
125
+ return result;
126
+ }
127
+ const MASKS = [
128
+ (x, y) => (x + y) % 2 === 0,
129
+ (_x, y) => y % 2 === 0,
130
+ (x) => x % 3 === 0,
131
+ (x, y) => (x + y) % 3 === 0,
132
+ (x, y) => (Math.floor(x / 3) + Math.floor(y / 2)) % 2 === 0,
133
+ (x, y) => ((x * y) % 2) + ((x * y) % 3) === 0,
134
+ (x, y) => (((x * y) % 2) + ((x * y) % 3)) % 2 === 0,
135
+ (x, y) => (((x + y) % 2) + ((x * y) % 3)) % 2 === 0,
136
+ ];
137
+ class Grid {
138
+ size;
139
+ dark;
140
+ fixed;
141
+ version;
142
+ constructor(version) {
143
+ this.version = version;
144
+ this.size = version * 4 + 17;
145
+ this.dark = Array.from({ length: this.size }, () => new Array(this.size).fill(false));
146
+ this.fixed = Array.from({ length: this.size }, () => new Array(this.size).fill(false));
147
+ }
148
+ set(x, y, dark) {
149
+ const row = this.dark[y];
150
+ const fixed = this.fixed[y];
151
+ if (!row || !fixed || x < 0 || x >= this.size)
152
+ return;
153
+ row[x] = dark;
154
+ fixed[x] = true;
155
+ }
156
+ functionPatterns() {
157
+ const { size } = this;
158
+ for (let i = 0; i < size; i++) {
159
+ this.set(6, i, i % 2 === 0);
160
+ this.set(i, 6, i % 2 === 0);
161
+ }
162
+ for (const [cx, cy] of [
163
+ [3, 3],
164
+ [size - 4, 3],
165
+ [3, size - 4],
166
+ ])
167
+ for (let dy = -4; dy <= 4; dy++)
168
+ for (let dx = -4; dx <= 4; dx++) {
169
+ const d = Math.max(Math.abs(dx), Math.abs(dy));
170
+ const x = cx + dx;
171
+ const y = cy + dy;
172
+ if (x >= 0 && x < size && y >= 0 && y < size)
173
+ this.set(x, y, d !== 2 && d !== 4);
174
+ }
175
+ const align = alignmentPositions(this.version);
176
+ const n = align.length;
177
+ for (let i = 0; i < n; i++)
178
+ for (let j = 0; j < n; j++) {
179
+ if ((i === 0 && j === 0) || (i === 0 && j === n - 1) || (i === n - 1 && j === 0))
180
+ continue;
181
+ for (let dy = -2; dy <= 2; dy++)
182
+ for (let dx = -2; dx <= 2; dx++)
183
+ this.set((align[i] ?? 0) + dx, (align[j] ?? 0) + dy, Math.max(Math.abs(dx), Math.abs(dy)) !== 1);
184
+ }
185
+ this.format(0);
186
+ if (this.version >= 7) {
187
+ const bits = versionBits(this.version);
188
+ for (let i = 0; i < 18; i++) {
189
+ const bit = ((bits >>> i) & 1) === 1;
190
+ const a = size - 11 + (i % 3);
191
+ const b = Math.floor(i / 3);
192
+ this.set(a, b, bit);
193
+ this.set(b, a, bit);
194
+ }
195
+ }
196
+ }
197
+ format(mask) {
198
+ const bits = formatBits(FORMAT_M, mask);
199
+ const bit = (i) => ((bits >>> i) & 1) === 1;
200
+ const { size } = this;
201
+ for (let i = 0; i <= 5; i++)
202
+ this.set(8, i, bit(i));
203
+ this.set(8, 7, bit(6));
204
+ this.set(8, 8, bit(7));
205
+ this.set(7, 8, bit(8));
206
+ for (let i = 9; i < 15; i++)
207
+ this.set(14 - i, 8, bit(i));
208
+ for (let i = 0; i < 8; i++)
209
+ this.set(size - 1 - i, 8, bit(i));
210
+ for (let i = 8; i < 15; i++)
211
+ this.set(8, size - 15 + i, bit(i));
212
+ this.set(8, size - 8, true);
213
+ }
214
+ place(data) {
215
+ const { size } = this;
216
+ let i = 0;
217
+ for (let right = size - 1; right >= 1; right -= 2) {
218
+ if (right === 6)
219
+ right = 5;
220
+ for (let vert = 0; vert < size; vert++)
221
+ for (let j = 0; j < 2; j++) {
222
+ const x = right - j;
223
+ const upward = ((right + 1) & 2) === 0;
224
+ const y = upward ? size - 1 - vert : vert;
225
+ const row = this.dark[y];
226
+ if (row && !this.fixed[y]?.[x] && i < data.length * 8) {
227
+ row[x] = (((data[i >>> 3] ?? 0) >>> (7 - (i & 7))) & 1) === 1;
228
+ i++;
229
+ }
230
+ }
231
+ }
232
+ }
233
+ mask(mask) {
234
+ const test = MASKS[mask];
235
+ if (!test)
236
+ return;
237
+ for (let y = 0; y < this.size; y++)
238
+ for (let x = 0; x < this.size; x++) {
239
+ const row = this.dark[y];
240
+ if (row && !this.fixed[y]?.[x] && test(x, y))
241
+ row[x] = !row[x];
242
+ }
243
+ }
244
+ /** Pénalité de la norme : longues suites, carrés 2 × 2, faux motifs de repérage, déséquilibre foncé / clair. */
245
+ penalty() {
246
+ const { size, dark } = this;
247
+ let score = 0;
248
+ const line = (get) => {
249
+ let run = 1;
250
+ for (let i = 1; i <= size; i++) {
251
+ if (i < size && get(i) === get(i - 1))
252
+ run++;
253
+ else {
254
+ if (run >= 5)
255
+ score += 3 + (run - 5);
256
+ run = 1;
257
+ }
258
+ }
259
+ const cells = Array.from({ length: size }, (_, i) => (get(i) ? 1 : 0));
260
+ const text = cells.join('');
261
+ for (const motif of ['10111010000', '00001011101'])
262
+ for (let at = text.indexOf(motif); at >= 0; at = text.indexOf(motif, at + 1))
263
+ score += 40;
264
+ };
265
+ for (let y = 0; y < size; y++)
266
+ line((x) => dark[y]?.[x] === true);
267
+ for (let x = 0; x < size; x++)
268
+ line((y) => dark[y]?.[x] === true);
269
+ for (let y = 0; y < size - 1; y++)
270
+ for (let x = 0; x < size - 1; x++) {
271
+ const c = dark[y]?.[x];
272
+ if (c === dark[y]?.[x + 1] && c === dark[y + 1]?.[x] && c === dark[y + 1]?.[x + 1])
273
+ score += 3;
274
+ }
275
+ const total = dark.reduce((n, row) => n + row.filter(Boolean).length, 0);
276
+ const k = Math.ceil(Math.abs(total * 20 - size * size * 10) / (size * size)) - 1;
277
+ score += Math.max(0, k) * 10;
278
+ return score;
279
+ }
280
+ }
281
+ /**
282
+ * Le QR code d'un texte : une ligne par rangée, « 1 » pour un module foncé, sans marge. La plus petite version qui le
283
+ * contient. Lève une erreur au-delà de la version 10 (213 octets).
284
+ */
285
+ export function qrRows(text) {
286
+ const bytes = new TextEncoder().encode(text);
287
+ let version = 1;
288
+ while (version <= MAX_VERSION && dataCodewords(version) * 8 < 4 + (version <= 9 ? 8 : 16) + bytes.length * 8)
289
+ version++;
290
+ if (version > MAX_VERSION)
291
+ throw new Error('Texte trop long pour le QR code.');
292
+ const data = codewords(bytes, version);
293
+ let best = null;
294
+ let bestScore = Infinity;
295
+ for (let m = 0; m < 8; m++) {
296
+ const grid = new Grid(version);
297
+ grid.functionPatterns();
298
+ grid.place(data);
299
+ grid.mask(m);
300
+ grid.format(m);
301
+ const score = grid.penalty();
302
+ if (score < bestScore) {
303
+ best = grid;
304
+ bestScore = score;
305
+ }
306
+ }
307
+ return (best?.dark ?? []).map((row) => row.map((d) => (d ? '1' : '0')).join(''));
308
+ }
@@ -0,0 +1,40 @@
1
+ /** Résultat de l'analyse d'une commande par le parseur de PowerShell (sans exécution). */
2
+ import { z } from 'zod';
3
+ const element = z.lazy(() => z.object({
4
+ k: z.enum(['p', 's', 'n', 'x', 'b', 'v', 'h', 'l', 'e']),
5
+ n: z.string().nullish().transform((v) => v ?? undefined),
6
+ v: z.string().nullish().transform((v) => v ?? undefined),
7
+ t: z.string().nullish().transform((v) => v ?? undefined),
8
+ bare: z.boolean().optional(),
9
+ splat: z.boolean().optional(),
10
+ a: element.optional(),
11
+ i: z.array(element).optional(),
12
+ }));
13
+ const list = (item) => z.array(item).nullish().transform((v) => v ?? []);
14
+ const analyzedCommand = z.object({
15
+ name: z.string().nullable(),
16
+ resolved: z.string().nullable(),
17
+ kind: z.string(),
18
+ source: z.string().nullable(),
19
+ module: z.string().nullable(),
20
+ alias: z.boolean().default(false),
21
+ op: z.string(),
22
+ text: z.string(),
23
+ /** Éléments bruts, dans l'ordre (utile pour les applications natives). */
24
+ elements: list(element),
25
+ /** Paramètres liés par le lieur statique de PowerShell : noms complets, positions résolues. */
26
+ bound: list(z.object({ name: z.string(), el: element.nullable() })),
27
+ bindErrors: list(z.string()),
28
+ });
29
+ export const analysisSchema = z.object({
30
+ ok: z.boolean(),
31
+ errors: list(z.string()),
32
+ nodes: list(z.string()),
33
+ commands: list(analyzedCommand),
34
+ members: list(z.object({ member: z.string().nullable(), call: z.boolean(), static: z.boolean(), type: z.string().nullable() })),
35
+ types: list(z.string()),
36
+ redirections: list(z.object({ target: element, append: z.boolean(), stream: z.string() })),
37
+ variables: list(z.object({ name: z.string(), drive: z.string().nullable(), assigned: z.boolean().default(false), splat: z.boolean().default(false) })),
38
+ /** Affectations dont la cible n'est pas une simple variable : propriété ($x.P = …) ou élément ($x[0] = …). */
39
+ assignTargets: list(z.string()),
40
+ });
@@ -0,0 +1,105 @@
1
+ /** Validation des données reçues par le serveur (zod). Les types d'entrée en sont déduits. */
2
+ import { z } from 'zod';
3
+ import { fr } from 'zod/locales';
4
+ // Messages de validation en français.
5
+ z.config(fr());
6
+ export const MAX_COMMAND_LENGTH = 4000;
7
+ const command = z
8
+ .string()
9
+ .min(1, 'La commande est vide.')
10
+ .max(MAX_COMMAND_LENGTH, `La commande dépasse ${MAX_COMMAND_LENGTH} caractères.`)
11
+ .refine((s) => !s.includes('\0'), 'La commande contient un caractère nul.');
12
+ export const previewInput = z.object({ command, cwd: z.string().min(1).max(400).optional() });
13
+ export const runInput = z.object({
14
+ ticket: z.string().regex(/^[a-f0-9]{32}$/),
15
+ /** La confirmation humaine est obligatoire : le serveur refuse toute autre valeur. */
16
+ confirm: z.literal(true),
17
+ /** Deuxième geste exigé pour une commande sensible. */
18
+ acknowledge: z.boolean().optional(),
19
+ /** « auto » : commande de lecture tapée dans le terminal quand le réglage le permet. */
20
+ confirmation: z.enum(['human', 'auto']).default('human'),
21
+ });
22
+ export const cancelInput = z.object({ execId: z.string().regex(/^[a-f0-9]{16}$/) });
23
+ export const cwdInput = z.object({ from: z.string().min(1).max(400), to: z.string().min(1).max(400) });
24
+ const host = z
25
+ .string()
26
+ .trim()
27
+ .toLowerCase()
28
+ .min(1)
29
+ .max(253)
30
+ .regex(/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)(?:\.(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?))*$|^[0-9a-f:]+$/, 'Nom d’hôte ou adresse IP invalide.');
31
+ export const scopeInput = z.object({
32
+ host,
33
+ note: z.string().trim().max(200).default(''),
34
+ includeSubdomains: z.boolean().default(false),
35
+ /** Attestation obligatoire : la cible t'appartient ou tu as une autorisation écrite. */
36
+ attest: z.literal(true),
37
+ });
38
+ export const moduleId = z.enum(['surface', 'ports', 'headers', 'tls', 'web', 'exposure', 'config', 'files']);
39
+ export const auditInput = z.object({
40
+ module: moduleId,
41
+ input: z.record(z.string(), z.string().max(2000)).default({}),
42
+ confirm: z.literal(true),
43
+ });
44
+ const customCommand = z.object({
45
+ name: z.string().trim().regex(/^[A-Za-z][\w.-]{0,63}$/, 'Nom de commande invalide.'),
46
+ level: z.enum(['read', 'sensitive']),
47
+ });
48
+ export const settingsPatch = z
49
+ .object({
50
+ model: z.string().max(200).nullable(),
51
+ engine: z
52
+ .object({
53
+ timeoutSec: z.number().int().min(5).max(300),
54
+ constrainedLanguage: z.boolean(),
55
+ confirmRead: z.boolean(),
56
+ roots: z.array(z.string().min(2).max(400)).min(1).max(12),
57
+ })
58
+ .partial(),
59
+ allowlist: z
60
+ .object({ custom: z.array(customCommand).max(60), unlocked: z.array(z.string().max(64)).max(60) })
61
+ .partial(),
62
+ ollama: z
63
+ .object({
64
+ baseUrl: z.string().url().max(200).refine((u) => /^https?:\/\//i.test(u), 'URL http(s) attendue.'),
65
+ replyTimeoutSec: z.number().int().min(10).max(900),
66
+ })
67
+ .partial(),
68
+ // L'adresse de SearXNG reste sur cette machine : le serveur ne s'en sert pas pour joindre un autre hôte.
69
+ search: z
70
+ .object({
71
+ enabled: z.boolean(),
72
+ url: z.string().url().max(200).refine((u) => /^http:\/\/(127\.0\.0\.1|localhost|\[::1\])(:\d{1,5})?\/?$/i.test(u), 'Adresse locale attendue, par exemple http://127.0.0.1:8888.'),
73
+ learn: z.boolean(),
74
+ })
75
+ .partial(),
76
+ fullCyber: z.boolean(),
77
+ autoRun: z.boolean(),
78
+ })
79
+ .partial();
80
+ /** Caractères au plus dans un message, texte tapé et pièces jointes réunis. Le petit modèle local n'en lit pas davantage. */
81
+ export const MAX_MESSAGE_CHARS = 16000;
82
+ const promptAttachment = z.object({
83
+ name: z.string().trim().min(1).max(120),
84
+ text: z.string().min(1).max(MAX_MESSAGE_CHARS),
85
+ });
86
+ export const sendInput = z
87
+ .object({
88
+ text: z.string().max(MAX_MESSAGE_CHARS).default(''),
89
+ /** De longs textes collés, rangés hors du champ de saisie. Leur contenu est donné au modèle à la suite du texte tapé. */
90
+ attachments: z.array(promptAttachment).max(6).optional(),
91
+ })
92
+ .refine((d) => d.text.trim().length > 0 || (d.attachments?.length ?? 0) > 0, { error: 'Message vide.' })
93
+ .refine((d) => d.text.length + (d.attachments?.reduce((n, a) => n + a.text.length, 0) ?? 0) <= MAX_MESSAGE_CHARS, {
94
+ error: `Le message dépasse ${MAX_MESSAGE_CHARS} caractères.`,
95
+ });
96
+ export const decisionInput = z.object({
97
+ proposalId: z.string().regex(/^[a-f0-9]{16}$/),
98
+ decision: z.enum(['approve', 'deny']),
99
+ acknowledge: z.boolean().optional(),
100
+ });
101
+ export const newConversationInput = z.object({ model: z.string().max(200).optional() });
102
+ /** Ouvrir ou fermer l'accès depuis le téléphone (depuis le PC seulement). */
103
+ export const phoneToggleInput = z.object({ active: z.boolean() }).strict();
104
+ /** Le code d'appairage tapé ou scanné sur le téléphone. */
105
+ export const pairInput = z.object({ code: z.string().min(1).max(32) }).strict();
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Contrats partagés entre le serveur local et l'interface.
3
+ * Des types, et une seule constante (SEVERITIES).
4
+ */
5
+ export const SEVERITIES = ['critical', 'high', 'medium', 'low', 'info'];
6
+ /**
7
+ * Réglages qui se changent sur le PC seulement : depuis le téléphone, la politique ne peut pas être élargie. Le serveur
8
+ * refuse (403, pc_only) tout changement de ces champs venu du téléphone, l'interface les y montre désactivés.
9
+ */
10
+ export const PC_ONLY_SETTINGS = ['engine.roots', 'engine.constrainedLanguage', 'engine.confirmRead', 'allowlist.custom', 'allowlist.unlocked', 'ollama.baseUrl', 'search.url', 'fullCyber', 'autoRun'];
package/package.json CHANGED
@@ -1,6 +1,83 @@
1
1
  {
2
2
  "name": "@nosserb/prmt",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "2.0.0",
4
+ "description": "Prométhée : plateforme locale de cybersécurité (console web + TUI + moteur PowerShell + Ollama).",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/nosserb/prom-th-e.git"
10
+ },
11
+ "homepage": "https://github.com/nosserb/prom-th-e#readme",
12
+ "engines": {
13
+ "node": ">=24"
14
+ },
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "keywords": [
19
+ "cybersecurity",
20
+ "cybersecurite",
21
+ "ollama",
22
+ "llm",
23
+ "powershell",
24
+ "tui",
25
+ "cli",
26
+ "pentest",
27
+ "ctf"
28
+ ],
29
+ "bin": {
30
+ "prmt": "bin/prmt.mjs"
31
+ },
32
+ "files": [
33
+ "bin/",
34
+ "lib/",
35
+ "dist/",
36
+ "tui/",
37
+ "searxng/",
38
+ "docs/",
39
+ "README.md",
40
+ "LICENSE"
41
+ ],
42
+ "scripts": {
43
+ "dev": "node scripts/dev.ts",
44
+ "build": "npm run typecheck && vite build",
45
+ "build:statique": "npm run typecheck && vite build --mode statique --base ./ --outDir dist-statique",
46
+ "start": "node server/index.ts",
47
+ "prmt": "node bin/prmt.mjs",
48
+ "lier": "node scripts/lier.ts",
49
+ "searxng": "node scripts/searxng.ts",
50
+ "pack:tui": "node scripts/pack-tui.mjs",
51
+ "build:server": "tsc -p tsconfig.pack.json",
52
+ "prepack": "npm run pack:tui && npm run build && npm run build:server",
53
+ "typecheck": "tsc -p tsconfig.web.json && tsc -p tsconfig.server.json && tsc -p tsconfig.e2e.json",
54
+ "test": "vitest run",
55
+ "test:e2e": "npm run build && playwright test",
56
+ "verify": "npm run typecheck && npm test && npm run test:e2e"
57
+ },
58
+ "dependencies": {
59
+ "zod": "^4.6.7"
60
+ },
61
+ "devDependencies": {
62
+ "@fontsource-variable/bricolage-grotesque": "^5.3.0",
63
+ "@fontsource-variable/geist": "^5.3.0",
64
+ "@fontsource-variable/jetbrains-mono": "^5.3.0",
65
+ "leaflet": "^1.9.4",
66
+ "lucide-react": "^1.72.0",
67
+ "react": "^19.3.1",
68
+ "react-dom": "^19.3.1",
69
+ "@playwright/test": "^1.65.0",
70
+ "@testing-library/jest-dom": "^7.0.1",
71
+ "@testing-library/react": "^16.3.3",
72
+ "@testing-library/user-event": "^14.6.10",
73
+ "@types/leaflet": "^1.9.22",
74
+ "@types/node": "^26.6.6",
75
+ "@types/react": "^19.3.0",
76
+ "@types/react-dom": "^19.3.0",
77
+ "@vitejs/plugin-react": "^6.3.0",
78
+ "jsdom": "^31.0.0",
79
+ "typescript": "^7.0.2",
80
+ "vite": "^8.3.8",
81
+ "vitest": "^5.1.0"
82
+ }
83
+ }
@@ -0,0 +1,35 @@
1
+ # SearXNG pour Prométhée : le moteur de recherche web, installé chez toi, sans clé ni compte.
2
+ # Il interroge plusieurs moteurs (Google, Bing, DuckDuckGo, Brave, Wikipédia…) et rend leurs résultats en JSON.
3
+ # Cartes : la catégorie « map » passe par Photon (données d'OpenStreetMap) et donne les coordonnées d'un lieu.
4
+ # Les moteurs openstreetmap (Nominatim) et wikidata exigent une adresse de contact (general.contact_url), envoyée
5
+ # à leurs serveurs. Elle n'est pas remplie : ils ne se chargent pas, et les cartes viennent de Photon seul.
6
+ # Photon suspend un moment après une rafale de requêtes. Une question de lieu à la fois ne pose pas de problème.
7
+ #
8
+ # Copié par scripts/searxng.ts dans Documents\Promethee\searxng\, puis monté dans le conteneur.
9
+ # Le secret n'est pas ici : scripts/searxng.ts le tire au hasard et le passe par SEARXNG_SECRET.
10
+ # Le conteneur n'écoute que sur 127.0.0.1:8888 : rien d'autre sur le réseau ne peut s'en servir.
11
+
12
+ use_default_settings: true
13
+
14
+ general:
15
+ instance_name: "Prométhée"
16
+ enable_metrics: false
17
+
18
+ search:
19
+ safe_search: 1
20
+ autocomplete: ""
21
+ default_lang: "fr-FR"
22
+ # json : la sortie que lit la console. html : la page de SearXNG, utile pour vérifier à la main.
23
+ formats:
24
+ - html
25
+ - json
26
+
27
+ server:
28
+ # Le limiteur protège une instance publique et demande Valkey : inutile pour un seul utilisateur, en local.
29
+ limiter: false
30
+ public_instance: false
31
+ image_proxy: false
32
+ method: "GET"
33
+
34
+ ui:
35
+ default_locale: "fr"
package/tui/Modelfile ADDED
@@ -0,0 +1,23 @@
1
+ FROM hf.co/mradermacher/RedSage-Qwen3-8B-DPO-GGUF:Q4_K_M
2
+ # Prométhée : basé sur RedSage-Qwen3-8B-DPO (RISys-Lab, ICLR 2026), Q4_K_M.
3
+ # Meilleur modèle au banc d'essai (C:\Users\<utilisateur>\ollama\eval\).
4
+ # Template ChatML hérité du GGUF (détecté automatiquement par Ollama).
5
+ # Lancement : commande "prmt" (C:\Users\<utilisateur>\.local\bin\prmt.cmd)
6
+
7
+ SYSTEM """Tu es Prométhée, une IA spécialisée en cybersécurité (offensive et défensive), en administration système et en programmation. Tu tournes en local sur la machine de l'utilisateur et tu n'as pas accès à Internet.
8
+ L'utilisateur est un étudiant en cybersécurité.
9
+ - Réponds toujours en français, sauf si l'utilisateur écrit dans une autre langue.
10
+ - Sois clair et concis : adapte la longueur de la réponse à la question.
11
+ - Sois pédagogique : explique le pourquoi, pas seulement le comment.
12
+ - Distingue bien Windows et Linux : donne des commandes adaptées au système concerné.
13
+ - Quand tu donnes du code ou des commandes, utilise des blocs de code et explique brièvement ce qu'ils font.
14
+ - Si tu n'es pas sûr d'une information (numéro de CVE, version, option de commande), dis-le clairement au lieu d'inventer."""
15
+
16
+ PARAMETER stop "<|im_end|>"
17
+ PARAMETER stop "<|endoftext|>"
18
+ PARAMETER temperature 0.5
19
+ PARAMETER top_p 0.9
20
+ PARAMETER repeat_penalty 1.05
21
+ PARAMETER num_predict 2048
22
+ # Avec OLLAMA_KV_CACHE_TYPE=q8_0 : ~72 Ko/token -> 8192 de contexte ≈ 600 Mo
23
+ PARAMETER num_ctx 8192