utn-mcp 0.8.3 → 0.9.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 (86) hide show
  1. package/README.md +30 -3
  2. package/build/aula-virtual/client/client.d.ts +97 -0
  3. package/build/aula-virtual/client/credentials.d.ts +10 -0
  4. package/build/aula-virtual/client/link.d.ts +21 -0
  5. package/build/aula-virtual/client/local-session.d.ts +23 -0
  6. package/build/aula-virtual/client/moodle-api.d.ts +25 -0
  7. package/build/aula-virtual/client/regionals.d.ts +47 -0
  8. package/build/aula-virtual/client/session.d.ts +12 -0
  9. package/build/aula-virtual/client/sso-login.d.ts +60 -0
  10. package/build/aula-virtual/client/stored-session.d.ts +32 -0
  11. package/build/aula-virtual/client/types.d.ts +173 -0
  12. package/build/aula-virtual/format/actions.d.ts +57 -0
  13. package/build/aula-virtual/format/activities.d.ts +86 -0
  14. package/build/aula-virtual/format/address.d.ts +36 -0
  15. package/build/aula-virtual/format/fields.d.ts +11 -0
  16. package/build/aula-virtual/format/files.d.ts +32 -0
  17. package/build/aula-virtual/format/navigate.d.ts +213 -0
  18. package/build/aula-virtual/format/study-pack.d.ts +33 -0
  19. package/build/aula-virtual/format/submission.d.ts +40 -0
  20. package/build/aula-virtual/login.d.ts +6 -0
  21. package/build/aula-virtual/tools.d.ts +4 -0
  22. package/build/autogestion/client/catalog.d.ts +20 -0
  23. package/build/autogestion/client/catalog.js +22 -0
  24. package/build/autogestion/client/connector.d.ts +71 -0
  25. package/build/autogestion/client/connector.js +31 -0
  26. package/build/autogestion/client/credentials.d.ts +5 -0
  27. package/build/autogestion/client/credentials.js +40 -0
  28. package/build/autogestion/client/install.d.ts +19 -0
  29. package/build/autogestion/client/install.js +91 -0
  30. package/build/autogestion/client/installed-systems.d.ts +7 -0
  31. package/build/autogestion/client/installed-systems.js +58 -0
  32. package/build/autogestion/client/local-session.d.ts +17 -0
  33. package/build/autogestion/client/local-session.js +37 -0
  34. package/build/autogestion/client/session.d.ts +18 -0
  35. package/build/autogestion/client/session.js +1 -0
  36. package/build/autogestion/client/stored-session.d.ts +42 -0
  37. package/build/autogestion/client/stored-session.js +126 -0
  38. package/build/autogestion/client/types.d.ts +49 -0
  39. package/build/autogestion/client/types.js +1 -0
  40. package/build/autogestion/format/correlatividades.d.ts +4 -0
  41. package/build/autogestion/format/correlatividades.js +4 -0
  42. package/build/autogestion/format/estado-academico.d.ts +4 -0
  43. package/build/autogestion/format/estado-academico.js +4 -0
  44. package/build/autogestion/format/examenes.d.ts +3 -0
  45. package/build/autogestion/format/examenes.js +8 -0
  46. package/build/autogestion/format/notas-parciales.d.ts +3 -0
  47. package/build/autogestion/format/notas-parciales.js +8 -0
  48. package/build/autogestion/link-page.d.ts +7 -0
  49. package/build/autogestion/link-page.js +62 -0
  50. package/build/autogestion/login.d.ts +11 -0
  51. package/build/autogestion/login.js +59 -0
  52. package/build/autogestion/tools.d.ts +3 -0
  53. package/build/autogestion/tools.js +73 -0
  54. package/build/http.d.ts +26 -0
  55. package/build/http.js +7 -2
  56. package/build/index.d.ts +2 -0
  57. package/build/index.js +35 -4
  58. package/build/oauth/access.d.ts +18 -0
  59. package/build/oauth/admin.d.ts +8 -0
  60. package/build/oauth/admin.js +1 -1
  61. package/build/oauth/pending-login.d.ts +20 -0
  62. package/build/oauth/redirects.d.ts +20 -0
  63. package/build/oauth/server.d.ts +22 -0
  64. package/build/oauth/server.js +1 -1
  65. package/build/server.d.ts +11 -0
  66. package/build/server.js +19 -2
  67. package/build/shared/documents/index.d.ts +33 -0
  68. package/build/shared/documents/merge.d.ts +32 -0
  69. package/build/shared/documents/office.d.ts +9 -0
  70. package/build/shared/documents/pdf.d.ts +2 -0
  71. package/build/shared/documents/png.d.ts +2 -0
  72. package/build/shared/documents/zip.d.ts +18 -0
  73. package/build/shared/expiring.d.ts +26 -0
  74. package/build/{oauth → shared}/expiring.js +4 -3
  75. package/build/shared/html.d.ts +6 -0
  76. package/build/shared/html.js +7 -1
  77. package/build/shared/http.d.ts +6 -0
  78. package/build/shared/input.d.ts +7 -0
  79. package/build/shared/load-env.d.ts +1 -0
  80. package/build/shared/logger.d.ts +5 -0
  81. package/build/shared/pages.d.ts +6 -0
  82. package/build/shared/paths.d.ts +10 -0
  83. package/build/shared/store/session-store.d.ts +77 -0
  84. package/build/shared/store/sqlite-store.d.ts +36 -0
  85. package/build/shared/uploads.d.ts +62 -0
  86. package/package.json +8 -1
@@ -0,0 +1,86 @@
1
+ import type { AulaSession } from "../client/session.js";
2
+ import type { ChoiceOption, CourseModule } from "../client/types.js";
3
+ /** La entrega del usuario (o la de su grupo): qué acepta la tarea, qué subió y la devolución si la hay. */
4
+ export declare function viewSubmission(session: AulaSession, courseId: number, module: CourseModule): Promise<{
5
+ estado: string;
6
+ grupal: true | undefined;
7
+ modificada: string | undefined;
8
+ abre: string | undefined;
9
+ vence: string | undefined;
10
+ prorroga: string | undefined;
11
+ cierreDefinitivo: string | undefined;
12
+ acepta: ({
13
+ tipo: string;
14
+ maximo: number | undefined;
15
+ tamanioMaximo: number | undefined;
16
+ extensiones: string | undefined;
17
+ limitePalabras?: undefined;
18
+ } | {
19
+ maximo?: undefined;
20
+ tamanioMaximo?: undefined;
21
+ extensiones?: undefined;
22
+ tipo: string;
23
+ limitePalabras: number | undefined;
24
+ })[] | undefined;
25
+ requiereConfirmarEnvio: true | undefined;
26
+ requiereDeclaracion: true | undefined;
27
+ declaracion: string | undefined;
28
+ puedeEditar: boolean;
29
+ puedeEnviar: true | undefined;
30
+ bloqueada: true | undefined;
31
+ devolucion: {
32
+ calificacion: string | undefined;
33
+ fecha: string | null;
34
+ archivos: {
35
+ nombre: string;
36
+ ruta: string | undefined;
37
+ tamanio: number;
38
+ mimetype: string | undefined;
39
+ modificado: string | null;
40
+ url: string;
41
+ }[] | undefined;
42
+ texto: string | undefined;
43
+ } | undefined;
44
+ archivos: {
45
+ nombre: string;
46
+ ruta: string | undefined;
47
+ tamanio: number;
48
+ mimetype: string | undefined;
49
+ modificado: string | null;
50
+ url: string;
51
+ }[] | undefined;
52
+ texto: string | undefined;
53
+ }>;
54
+ export declare function viewChoice(session: AulaSession, courseId: number, module: CourseModule): Promise<{
55
+ multiple: true | undefined;
56
+ puedeCambiar: boolean | undefined;
57
+ abre: string | undefined;
58
+ cierra: string | undefined;
59
+ opciones: {
60
+ id: number;
61
+ texto: string | undefined;
62
+ elegida: true | undefined;
63
+ deshabilitada: true | undefined;
64
+ cupo: number | undefined;
65
+ anotados: number | undefined;
66
+ }[];
67
+ }>;
68
+ /** El plugin no expone si deja cambiar o elegir varios grupos: eso lo dice la descripción o el error al elegir. */
69
+ export declare function viewChoicegroup(session: AulaSession, module: CourseModule): Promise<{
70
+ opciones: {
71
+ id: number;
72
+ texto: string | undefined;
73
+ elegida: true | undefined;
74
+ deshabilitada: true | undefined;
75
+ cupo: number | undefined;
76
+ anotados: number | undefined;
77
+ }[];
78
+ }>;
79
+ export declare function toOption(o: ChoiceOption): {
80
+ id: number;
81
+ texto: string | undefined;
82
+ elegida: true | undefined;
83
+ deshabilitada: true | undefined;
84
+ cupo: number | undefined;
85
+ anotados: number | undefined;
86
+ };
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Direcciones de navegación del aula: un path que el modelo recibe en una respuesta
3
+ * y devuelve tal cual para entrar ahí. No hay estado en el servidor; la dirección
4
+ * dice todo lo necesario, y el padre se deduce del texto (eso es el "volver").
5
+ */
6
+ export declare const AddressLevel: {
7
+ readonly Root: "root";
8
+ readonly Course: "course";
9
+ readonly Module: "module";
10
+ readonly Discussion: "discussion";
11
+ };
12
+ export type AddressLevel = (typeof AddressLevel)[keyof typeof AddressLevel];
13
+ export type Address = {
14
+ level: "root";
15
+ } | {
16
+ level: "course";
17
+ courseId: number;
18
+ } | {
19
+ level: "module";
20
+ courseId: number;
21
+ cmid: number;
22
+ } | {
23
+ level: "discussion";
24
+ courseId: number;
25
+ cmid: number;
26
+ discussionId: number;
27
+ };
28
+ export declare const ROOT = "/";
29
+ export declare const courseAddr: (courseId: number) => string;
30
+ export declare const moduleAddr: (courseId: number, cmid: number) => string;
31
+ export declare const discussionAddr: (courseId: number, cmid: number, discussionId: number) => string;
32
+ export declare function parseAddress(raw: string): Address;
33
+ /** La dirección en texto, tal como la ve el modelo. */
34
+ export declare function formatAddress(address: Address): string;
35
+ /** La dirección de un nivel más arriba, o null si ya estamos en la raíz. */
36
+ export declare function parentOf(address: Address): string | null;
@@ -0,0 +1,11 @@
1
+ import type { MoodleFile } from "../client/types.js";
2
+ /** Los timestamps de Moodle (segundos) en ISO; 0 o vacío es "sin fecha". */
3
+ export declare const isoDate: (seconds: number | null | undefined) => string | null;
4
+ export declare function toFile(f: MoodleFile): {
5
+ nombre: string;
6
+ ruta: string | undefined;
7
+ tamanio: number;
8
+ mimetype: string | undefined;
9
+ modificado: string | null;
10
+ url: string;
11
+ };
@@ -0,0 +1,32 @@
1
+ import { type Part } from "../../shared/documents/index.js";
2
+ import type { AulaSession } from "../client/session.js";
3
+ /** El más grande que hay hoy en los cursos pesa 12 MB. */
4
+ export declare const MAX_BYTES: number;
5
+ /**
6
+ * Moodle devuelve las URLs del web service, que piden token. Al usuario le sirve la misma ruta sin
7
+ * `/webservice`, que se abre con la sesión de su navegador. Es la única que mostramos: el token no
8
+ * sale nunca del servidor. `fileurl` viene de Moodle: ya es del aula del usuario.
9
+ */
10
+ export declare function userFacingUrl(fileurl: string): string;
11
+ /**
12
+ * Acepta cualquiera de las dos formas y devuelve la del web service. Solo archivos del aula del
13
+ * usuario (`baseUrl`): con cualquier otra URL estaríamos mandando su token a donde diga el modelo.
14
+ */
15
+ export declare function downloadUrl(baseUrl: string, url: string): URL;
16
+ /** Lo que ve el modelo de una lectura (las claves van en castellano) más las partes leídas. */
17
+ export interface FileRead {
18
+ archivo: string;
19
+ formato: string;
20
+ unidad: string;
21
+ total: number;
22
+ desde: number;
23
+ hasta: number;
24
+ /** El `desde` para pedir lo que sigue; no está si ya se leyó todo. */
25
+ siguiente?: number;
26
+ urlParaUsuario: string;
27
+ parts: (Part & {
28
+ number: number;
29
+ })[];
30
+ }
31
+ /** Baja un archivo del aula y devuelve desde la parte `from` tantas como entren en el presupuesto. */
32
+ export declare function readAulaFile(session: AulaSession, url: string, from?: number): Promise<FileRead>;
@@ -0,0 +1,213 @@
1
+ import type { AulaSession } from "../client/session.js";
2
+ import type { CourseModule } from "../client/types.js";
3
+ /**
4
+ * Entra a una dirección del aula y devuelve su contenido, el padre y las direcciones de lo que hay
5
+ * adentro. Es el único lugar que decide qué ve el modelo y cuánto texto le cuesta.
6
+ */
7
+ export declare function open(session: AulaSession, rawAddress: string, page?: number): Promise<{
8
+ direccion: string;
9
+ padre: string | null;
10
+ cursos: {
11
+ direccion: string;
12
+ id: number;
13
+ nombre: string;
14
+ nombreCorto: string;
15
+ visible: boolean;
16
+ progreso: number | null;
17
+ ultimoAcceso: string | null;
18
+ }[];
19
+ } | {
20
+ direccion: string;
21
+ padre: string | null;
22
+ secciones: {
23
+ nombre: string;
24
+ numero: number;
25
+ resumen: string | undefined;
26
+ modulos: {
27
+ direccion: string;
28
+ id: number;
29
+ nombre: string;
30
+ tipo: string;
31
+ url: string | undefined;
32
+ descripcion: string | undefined;
33
+ restriccion: string | undefined;
34
+ fechas: {
35
+ etiqueta: string;
36
+ fecha: string | null;
37
+ }[] | undefined;
38
+ archivos: {
39
+ nombre: string;
40
+ ruta: string | undefined;
41
+ tamanio: number;
42
+ mimetype: string | undefined;
43
+ modificado: string | null;
44
+ url: string;
45
+ }[] | undefined;
46
+ enlaces: string[] | undefined;
47
+ }[];
48
+ }[];
49
+ } | {
50
+ direccion: string;
51
+ padre: string | null;
52
+ seccion: string;
53
+ modulo: {
54
+ direccion: string;
55
+ id: number;
56
+ nombre: string;
57
+ tipo: string;
58
+ url: string | undefined;
59
+ descripcion: string | undefined;
60
+ restriccion: string | undefined;
61
+ fechas: {
62
+ etiqueta: string;
63
+ fecha: string | null;
64
+ }[] | undefined;
65
+ archivos: {
66
+ nombre: string;
67
+ ruta: string | undefined;
68
+ tamanio: number;
69
+ mimetype: string | undefined;
70
+ modificado: string | null;
71
+ url: string;
72
+ }[] | undefined;
73
+ enlaces: string[] | undefined;
74
+ };
75
+ } | {
76
+ direccion: string;
77
+ padre: string | null;
78
+ seccion: string;
79
+ modulo: {
80
+ direccion: string;
81
+ id: number;
82
+ nombre: string;
83
+ tipo: string;
84
+ url: string | undefined;
85
+ descripcion: string | undefined;
86
+ restriccion: string | undefined;
87
+ fechas: {
88
+ etiqueta: string;
89
+ fecha: string | null;
90
+ }[] | undefined;
91
+ archivos: {
92
+ nombre: string;
93
+ ruta: string | undefined;
94
+ tamanio: number;
95
+ mimetype: string | undefined;
96
+ modificado: string | null;
97
+ url: string;
98
+ }[] | undefined;
99
+ enlaces: string[] | undefined;
100
+ };
101
+ entrega: {
102
+ estado: string;
103
+ grupal: true | undefined;
104
+ modificada: string | undefined;
105
+ abre: string | undefined;
106
+ vence: string | undefined;
107
+ prorroga: string | undefined;
108
+ cierreDefinitivo: string | undefined;
109
+ acepta: ({
110
+ tipo: string;
111
+ maximo: number | undefined;
112
+ tamanioMaximo: number | undefined;
113
+ extensiones: string | undefined;
114
+ limitePalabras?: undefined;
115
+ } | {
116
+ maximo?: undefined;
117
+ tamanioMaximo?: undefined;
118
+ extensiones?: undefined;
119
+ tipo: string;
120
+ limitePalabras: number | undefined;
121
+ })[] | undefined;
122
+ requiereConfirmarEnvio: true | undefined;
123
+ requiereDeclaracion: true | undefined;
124
+ declaracion: string | undefined;
125
+ puedeEditar: boolean;
126
+ puedeEnviar: true | undefined;
127
+ bloqueada: true | undefined;
128
+ devolucion: {
129
+ calificacion: string | undefined;
130
+ fecha: string | null;
131
+ archivos: {
132
+ nombre: string;
133
+ ruta: string | undefined;
134
+ tamanio: number;
135
+ mimetype: string | undefined;
136
+ modificado: string | null;
137
+ url: string;
138
+ }[] | undefined;
139
+ texto: string | undefined;
140
+ } | undefined;
141
+ archivos: {
142
+ nombre: string;
143
+ ruta: string | undefined;
144
+ tamanio: number;
145
+ mimetype: string | undefined;
146
+ modificado: string | null;
147
+ url: string;
148
+ }[] | undefined;
149
+ texto: string | undefined;
150
+ };
151
+ } | {
152
+ direccion: string;
153
+ padre: string | null;
154
+ seccion: string;
155
+ modulo: {
156
+ direccion: string;
157
+ id: number;
158
+ nombre: string;
159
+ tipo: string;
160
+ url: string | undefined;
161
+ descripcion: string | undefined;
162
+ restriccion: string | undefined;
163
+ fechas: {
164
+ etiqueta: string;
165
+ fecha: string | null;
166
+ }[] | undefined;
167
+ archivos: {
168
+ nombre: string;
169
+ ruta: string | undefined;
170
+ tamanio: number;
171
+ mimetype: string | undefined;
172
+ modificado: string | null;
173
+ url: string;
174
+ }[] | undefined;
175
+ enlaces: string[] | undefined;
176
+ };
177
+ eleccion: {
178
+ opciones: {
179
+ id: number;
180
+ texto: string | undefined;
181
+ elegida: true | undefined;
182
+ deshabilitada: true | undefined;
183
+ cupo: number | undefined;
184
+ anotados: number | undefined;
185
+ }[];
186
+ };
187
+ } | {
188
+ direccion: string;
189
+ padre: string | null;
190
+ asunto: string;
191
+ publicaciones: {
192
+ autor: string;
193
+ autorId: number;
194
+ fecha: string | null;
195
+ respuestaA: number | undefined;
196
+ id: number;
197
+ mensaje: string;
198
+ adjuntos: {
199
+ nombre: string;
200
+ ruta: string | undefined;
201
+ tamanio: number;
202
+ mimetype: string | undefined;
203
+ modificado: string | null;
204
+ url: string;
205
+ }[] | undefined;
206
+ acciones: (string | false)[] | undefined;
207
+ }[];
208
+ }>;
209
+ /** Ubica un módulo por su cmid y se queda con la sección que lo contiene. */
210
+ export declare function findModule(session: AulaSession, courseId: number, cmid: number): Promise<{
211
+ sectionName: string;
212
+ module: CourseModule;
213
+ }>;
@@ -0,0 +1,33 @@
1
+ import type { AulaSession } from "../client/session.js";
2
+ /**
3
+ * Escribe el pack sin pisar packs anteriores: nombre con fecha-hora y, si ya existe (dos packs
4
+ * en el mismo segundo), un sufijo -2, -3... La escritura es atómica por nombre (bandera "wx").
5
+ */
6
+ export declare function writePackFile(dir: string, courseName: string, bytes: Uint8Array): Promise<string>;
7
+ /**
8
+ * Baja PDF de un curso y los une en un solo archivo con índice, guardado en exportsDir().
9
+ * Con `archivos` une solo las URL pedidas (tal como las devuelve aula_open, validadas con
10
+ * `downloadUrl` como en aula_read_file); sin `archivos`, todos los PDF del curso. El pack sigue
11
+ * el orden del aula, no el de la lista, para que el índice acompañe las unidades. Los duplicados
12
+ * exactos se descartan al armar, porque ahí se bajan igual. Todo lo omitido queda listado en
13
+ * `omitidos` con el motivo; en el atajo sin `archivos`, los archivos que no son PDF solo se
14
+ * cuentan en `archivosNoPdf` en vez de listarse uno por uno.
15
+ */
16
+ export declare function buildStudyPack(session: AulaSession, rawAddress: string, archivos?: string[]): Promise<{
17
+ archivo: string;
18
+ curso: string;
19
+ paginas: number;
20
+ materiales: {
21
+ nombre: string;
22
+ seccion: string;
23
+ modulo: string;
24
+ paginas: number;
25
+ empiezaEn: number;
26
+ }[];
27
+ omitidos: {
28
+ nombre: string;
29
+ seccion: string;
30
+ motivo: string;
31
+ }[] | undefined;
32
+ archivosNoPdf: number | undefined;
33
+ }>;
@@ -0,0 +1,40 @@
1
+ import { type FileSource, type UploadInbox } from "../../shared/uploads.js";
2
+ import type { AulaSession } from "../client/session.js";
3
+ export interface SubmissionRequest {
4
+ /** La lista final de archivos: rutas locales (solo STDIO) o la `url` de uno que ya está en la entrega. */
5
+ files?: string[];
6
+ /** Id de un link de subida (solo HTTP): sus archivos se suman a la lista. */
7
+ upload?: string;
8
+ /** Texto plano del modelo; reemplaza el texto en línea. */
9
+ text?: string;
10
+ /** Confirmar el envío si la tarea usa borradores. */
11
+ send: boolean;
12
+ acceptStatement: boolean;
13
+ }
14
+ /** Abre un link para recibir los archivos de una entrega en modo HTTP, con los límites de la tarea. */
15
+ export declare function createUploadLink(session: AulaSession, rawAddress: string, inbox: UploadInbox, owner: string): Promise<{
16
+ subida: string;
17
+ url: string;
18
+ vence: string;
19
+ acepta: {
20
+ maximo: number | undefined;
21
+ tamanioMaximo: number;
22
+ extensiones: string | undefined;
23
+ };
24
+ conTerminal: string;
25
+ }>;
26
+ /**
27
+ * Deja la entrega con los archivos y el texto pedidos, y la envía si hace falta. Lo que no se pasa
28
+ * se conserva: Moodle reemplaza cada plugin con lo que recibe, así que se le reenvía lo que ya tenía.
29
+ */
30
+ export declare function submitAssignment(session: AulaSession, rawAddress: string, request: SubmissionRequest, source: FileSource): Promise<{
31
+ direccion: string;
32
+ estado: string;
33
+ aviso: string | undefined;
34
+ archivos: {
35
+ nombre: string;
36
+ tamanio: number;
37
+ url: string;
38
+ }[] | undefined;
39
+ texto: string | undefined;
40
+ }>;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * `utn-mcp login`: pide la regional, usuario y contraseña una vez, los guarda en el keychain
3
+ * y obtiene el primer token. Después el servidor renueva solo.
4
+ * Con `--http` guarda la sesión en el store y emite el token para el cliente MCP.
5
+ */
6
+ export declare function runLogin(http: boolean): Promise<void>;
@@ -0,0 +1,4 @@
1
+ import type { McpServer } from "@modelcontextprotocol/server";
2
+ import type { FileSource } from "../shared/uploads.js";
3
+ import type { AulaSession } from "./client/session.js";
4
+ export declare function registerAulaVirtualTools(server: McpServer, session: AulaSession, files: FileSource): void;
@@ -0,0 +1,20 @@
1
+ /** Qué se sabe del conector en una regional: `verified` = alguien entró con una cuenta real; `candidate` = falta esa prueba. */
2
+ export type SystemSupport = "verified" | "candidate";
3
+ /**
4
+ * Un conector que `utn-mcp login` sabe instalar: el `.tgz` de `npm pack` adjunto a un release del repo.
5
+ * La URL fija la versión y `integrity` el contenido exacto: cargar un paquete es ejecutar su código,
6
+ * así que solo se instala lo que nombra este catálogo, nunca un nombre que venga del usuario o del entorno.
7
+ */
8
+ export interface CatalogEntry {
9
+ id: string;
10
+ name: string;
11
+ package: string;
12
+ version: string;
13
+ tarball: string;
14
+ /** sha512 del `.tgz` en base64, como el campo `integrity` de npm. */
15
+ integrity: string;
16
+ /** Las regionales que atiende y su soporte, para ofrecerlo en el login antes de bajar nada. */
17
+ regionales: Record<string, SystemSupport>;
18
+ }
19
+ export declare const CATALOG: CatalogEntry[];
20
+ export declare function catalogEntryForRegional(regionalId: string): CatalogEntry | null;
@@ -0,0 +1,22 @@
1
+ const RELEASES = "https://github.com/nialvape/utn-mcp/releases/download";
2
+ /** Las mismas regionales que `ASP_REGIONALS` del paquete; las que quedaron afuera dicen por qué ahí. */
3
+ const SYSACAD_ASP_REGIONALS = [
4
+ "frbb", "frch", "frcon", "frd", "frgp", "frh", "frlr", "frmdp", "frn",
5
+ "frp", "frrq", "frsf", "frsfco", "frsn", "frsr", "frt", "frtdf", "frvt",
6
+ ];
7
+ export const CATALOG = [
8
+ {
9
+ id: "sysacad-asp",
10
+ name: "Sysacad",
11
+ package: "utn-mcp-sysacad-asp",
12
+ version: "0.2.0",
13
+ tarball: `${RELEASES}/sysacad-asp-v0.2.0/utn-mcp-sysacad-asp-0.2.0.tgz`,
14
+ integrity: "sha512-p4j2fTpE3ZqSvcDQ3Rr6L32dOGNFut0GzyHBVpTpg8mqXrx4Un6CqkiJZG+eznQy+Risko+pcnPhWm+GIJ8haw==",
15
+ // FRT anduvo con cuenta real (oct 2026) con la versión anterior, que posteaba directo a `menuAlumno.asp`;
16
+ // esta lee el formulario y todavía no entró nadie con ella.
17
+ regionales: Object.fromEntries(SYSACAD_ASP_REGIONALS.map((id) => [id, "candidate"])),
18
+ },
19
+ ];
20
+ export function catalogEntryForRegional(regionalId) {
21
+ return CATALOG.find((entry) => regionalId in entry.regionales) ?? null;
22
+ }
@@ -0,0 +1,71 @@
1
+ import { HttpError } from "../../shared/http.js";
2
+ import { htmlToText } from "../../shared/html.js";
3
+ import type { Correlatividades, EstadoMateria, ExamenMateria, NotasParcialesMateria } from "./types.js";
4
+ /**
5
+ * Un sistema de autogestión académica (Sysacad, SIU Guaraní, etc.) logueado con un usuario.
6
+ * Cada sistema es un paquete aparte que exporta un `AutogestionSystem`; el core lo instala desde su
7
+ * catálogo y le pide un conector para la regional del usuario.
8
+ */
9
+ export interface AutogestionConnector {
10
+ /** Id del sistema, tal como lo declara el paquete que lo provee. */
11
+ readonly systemId: string;
12
+ /** Pide legajo/usuario y contraseña al sistema y deja la sesión lista. */
13
+ login(credentials: AutogestionCredentials): Promise<void>;
14
+ /** Materias del estado académico del alumno logueado, ya parseadas. */
15
+ estadoAcademico(): Promise<EstadoMateria[]>;
16
+ /** Correlatividades de la carrera del alumno logueado, para cursar y, si el sistema las publica, para rendir. */
17
+ correlatividades(): Promise<Correlatividades>;
18
+ /** Cursada actual, si el sistema la publica; no todos los conectores la tienen. */
19
+ notasParciales?(): Promise<NotasParcialesMateria[]>;
20
+ /** Historial de exámenes finales, si el sistema lo publica; no todos los conectores lo tienen. */
21
+ examenes?(): Promise<ExamenMateria[]>;
22
+ /**
23
+ * La sesión del sistema sin la contraseña (cookies, ids de sesión), para guardarla cifrada y retomarla
24
+ * en otro request. Sin esto, el conector sirve solo en local: el modo HTTP no guarda contraseñas.
25
+ */
26
+ exportSession?(): string;
27
+ /**
28
+ * Retoma una sesión de `exportSession`. Sin la contraseña no puede volver a entrar solo: si la sesión
29
+ * venció, las llamadas rechazan con `LoginRequiredError`.
30
+ */
31
+ restoreSession?(data: string): void;
32
+ }
33
+ /** Credenciales de un sistema de autogestión. En Sysacad `username` es el legajo. */
34
+ export interface AutogestionCredentials {
35
+ username: string;
36
+ password: string;
37
+ }
38
+ export declare class LoginRequiredError extends Error {
39
+ constructor(systemName: string);
40
+ }
41
+ export declare class InvalidCredentialsError extends Error {
42
+ constructor(systemName: string);
43
+ }
44
+ export declare class UnsupportedRegionalError extends Error {
45
+ constructor(regionalId: string);
46
+ }
47
+ /**
48
+ * Lo que el core le presta al conector en `create`. El paquete importa del core solo tipos: instalado
49
+ * en otra carpeta, un import en tiempo de ejecución cargaría otra copia de `utn-mcp`, con otras clases
50
+ * de error que el core no reconoce.
51
+ */
52
+ export declare const connectorKit: {
53
+ HttpError: typeof HttpError;
54
+ LoginRequiredError: typeof LoginRequiredError;
55
+ InvalidCredentialsError: typeof InvalidCredentialsError;
56
+ htmlToText: typeof htmlToText;
57
+ };
58
+ export type ConnectorKit = typeof connectorKit;
59
+ /** Lo que exporta un paquete de conector: qué regionales atiende y cómo crear el conector de cada una. */
60
+ export interface AutogestionSystem {
61
+ id: string;
62
+ name: string;
63
+ /** Ids de regional como en `regionals.ts` del aula (`frt`, `frd`...). */
64
+ regionales: string[];
65
+ /** Ayuda para el usuario de esa regional, si no es el legajo tal cual: "Legajo sin el 23-". */
66
+ usernameHint?(regionalId: string): string | undefined;
67
+ create(regionalId: string, kit: ConnectorKit): AutogestionConnector;
68
+ }
69
+ /** El sistema de una regional entre los conectores cargados, o null si ninguno la cubre. */
70
+ export declare function systemForRegional(regionalId: string, systems: AutogestionSystem[]): AutogestionSystem | null;
71
+ export type { CorrelatividadMateria, Correlatividades, EstadoMateria, ExamenMateria, NotasParcialesMateria } from "./types.js";
@@ -0,0 +1,31 @@
1
+ import { HttpError } from "../../shared/http.js";
2
+ import { htmlToText } from "../../shared/html.js";
3
+ export class LoginRequiredError extends Error {
4
+ constructor(systemName) {
5
+ super(`Falta iniciar sesión en ${systemName}: corré \`utn-mcp login autogestion\` una vez.`);
6
+ this.name = "LoginRequiredError";
7
+ }
8
+ }
9
+ export class InvalidCredentialsError extends Error {
10
+ constructor(systemName) {
11
+ super(`${systemName} rechazó el usuario o la contraseña. Revisalos con \`utn-mcp login autogestion\`.`);
12
+ this.name = "InvalidCredentialsError";
13
+ }
14
+ }
15
+ export class UnsupportedRegionalError extends Error {
16
+ constructor(regionalId) {
17
+ super(`La regional ${regionalId.toUpperCase()} todavía no tiene un conector de autogestión. ` +
18
+ "El aula virtual se puede seguir usando sin autogestión.");
19
+ this.name = "UnsupportedRegionalError";
20
+ }
21
+ }
22
+ /**
23
+ * Lo que el core le presta al conector en `create`. El paquete importa del core solo tipos: instalado
24
+ * en otra carpeta, un import en tiempo de ejecución cargaría otra copia de `utn-mcp`, con otras clases
25
+ * de error que el core no reconoce.
26
+ */
27
+ export const connectorKit = { HttpError, LoginRequiredError, InvalidCredentialsError, htmlToText };
28
+ /** El sistema de una regional entre los conectores cargados, o null si ninguno la cubre. */
29
+ export function systemForRegional(regionalId, systems) {
30
+ return systems.find((system) => system.regionales.includes(regionalId)) ?? null;
31
+ }
@@ -0,0 +1,5 @@
1
+ import type { AutogestionCredentials } from "./connector.js";
2
+ /** Primero <SISTEMA>_USER/<SISTEMA>_PASS (ej. SYSACAD_ASP_USER); si no están, el keychain. */
3
+ export declare function loadAutogestionCredentials(systemId: string): Promise<AutogestionCredentials | null>;
4
+ /** Guarda en el keychain. Devuelve false si no hay keychain (ej. Linux sin Secret Service). */
5
+ export declare function saveAutogestionCredentials(systemId: string, creds: AutogestionCredentials): Promise<boolean>;
@@ -0,0 +1,40 @@
1
+ import { logger } from "../../shared/logger.js";
2
+ const SERVICE = "utn-mcp";
3
+ /** Una entrada por sistema: la credencial es por sistema, no por regional. */
4
+ const account = (systemId) => `autogestion-${systemId}`;
5
+ // Import dinámico: si el binario nativo o el Secret Service (Linux) no están, seguimos con env vars.
6
+ async function entry(systemId) {
7
+ const { Entry } = await import("@napi-rs/keyring");
8
+ return new Entry(SERVICE, account(systemId));
9
+ }
10
+ function envNames(systemId) {
11
+ const prefix = systemId.toUpperCase().replace(/[^A-Z0-9]/g, "_");
12
+ return [`${prefix}_USER`, `${prefix}_PASS`];
13
+ }
14
+ /** Primero <SISTEMA>_USER/<SISTEMA>_PASS (ej. SYSACAD_ASP_USER); si no están, el keychain. */
15
+ export async function loadAutogestionCredentials(systemId) {
16
+ const [userEnv, passEnv] = envNames(systemId);
17
+ const username = process.env[userEnv];
18
+ const password = process.env[passEnv];
19
+ if (username && password)
20
+ return { username, password };
21
+ try {
22
+ const raw = (await entry(systemId)).getPassword();
23
+ return raw ? JSON.parse(raw) : null;
24
+ }
25
+ catch (e) {
26
+ logger.warn("Keychain no disponible:", e.message);
27
+ return null;
28
+ }
29
+ }
30
+ /** Guarda en el keychain. Devuelve false si no hay keychain (ej. Linux sin Secret Service). */
31
+ export async function saveAutogestionCredentials(systemId, creds) {
32
+ try {
33
+ (await entry(systemId)).setPassword(JSON.stringify(creds));
34
+ return true;
35
+ }
36
+ catch (e) {
37
+ logger.warn("No se pudo guardar en el keychain:", e.message);
38
+ return false;
39
+ }
40
+ }