@deveye/types 0.15.2 → 0.16.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/package.json +6 -6
- package/src/domain/device.ts +4 -29
- package/src/domain/featureRegistry.ts +40 -108
- package/src/domain/home.ts +40 -101
- package/src/domain/live.ts +36 -87
- package/src/domain/metrics.ts +10 -14
- package/src/domain/notifications.ts +39 -110
- package/src/domain/project.ts +3 -162
- package/src/domain/report.ts +46 -95
- package/src/domain/secrecy.ts +3 -5
- package/src/domain/sharing.ts +33 -70
- package/src/domain/syncProtocol.ts +5 -12
- package/src/domain/user.ts +8 -14
- package/src/domain/workspace.ts +0 -3
- package/src/domain/workspaceRole.ts +34 -82
- package/src/features/agent.ts +306 -0
- package/src/features/live.ts +17 -37
- package/src/features/notify.ts +19 -57
- package/src/features/registry.ts +7 -44
- package/src/features/secrecy.ts +4 -9
- package/src/features/sharing.ts +12 -26
- package/src/features/user.ts +6 -11
- package/src/features/workspace.ts +6 -11
- package/src/http/auth.ts +6 -13
- package/src/http/device.ts +13 -17
- package/src/http/status.ts +6 -10
- package/src/index.ts +29 -932
- package/src/protocol/agent.ts +40 -70
- package/src/protocol/envelope.ts +3 -10
- package/src/sdk/client-ambient.d.ts +228 -21
- package/src/sdk/client.ts +277 -17
- package/src/sdk/devb.ts +120 -0
- package/src/sdk/manifest.test.ts +0 -1
- package/src/sdk/manifest.ts +94 -25
- package/src/sdk/providers.ts +310 -5
- package/src/sdk/server.ts +483 -19
- package/src/sdk/testing.test.ts +2 -2
- package/src/sdk/testing.ts +302 -26
- package/src/utils/version.ts +5 -8
- package/src/domain/audience.ts +0 -549
- package/src/domain/backup.ts +0 -355
- package/src/domain/credential.ts +0 -55
- package/src/domain/database.ts +0 -467
- package/src/domain/deploy.ts +0 -231
- package/src/domain/finance.ts +0 -477
- package/src/domain/git.ts +0 -419
- package/src/domain/mail.ts +0 -394
- package/src/domain/note.ts +0 -202
- package/src/domain/password.ts +0 -36
- package/src/domain/projectBoard.ts +0 -130
- package/src/domain/projectChat.ts +0 -46
- package/src/domain/projectHistory.ts +0 -82
- package/src/domain/projectLink.ts +0 -87
- package/src/domain/projectPlan.ts +0 -68
- package/src/domain/sentinel.ts +0 -623
- package/src/domain/uptime.ts +0 -216
- package/src/features/audience.ts +0 -275
- package/src/features/backup.ts +0 -230
- package/src/features/database.ts +0 -461
- package/src/features/deploy.ts +0 -245
- package/src/features/device.ts +0 -292
- package/src/features/deviceFiles.ts +0 -83
- package/src/features/deviceLogs.ts +0 -36
- package/src/features/deviceTerminal.ts +0 -57
- package/src/features/finance.ts +0 -360
- package/src/features/git.ts +0 -368
- package/src/features/mail.ts +0 -374
- package/src/features/metrics.ts +0 -185
- package/src/features/note.ts +0 -189
- package/src/features/password.ts +0 -67
- package/src/features/project.ts +0 -709
- package/src/features/sentinel.ts +0 -233
- package/src/features/uptime.ts +0 -186
|
@@ -22,11 +22,6 @@ export const workspaceCapabilitySchema = z.enum([
|
|
|
22
22
|
'workspace.appearance',
|
|
23
23
|
/** Modifier la disposition de l'accueil de l'espace. */
|
|
24
24
|
'workspace.layout'
|
|
25
|
-
// « Gérer les canaux d'alerte » a vécu ici (`workspace.notifications`)
|
|
26
|
-
// puis est passée PAR FONCTIONNALITÉ (migration 093) : depuis que chaque
|
|
27
|
-
// émetteur possède ses canaux (091), une capacité d'espace accordait d'un
|
|
28
|
-
// bloc l'astreinte d'Uptime et le salon des sauvegardes. Voir le champ
|
|
29
|
-
// `channels` du grant de feature.
|
|
30
25
|
]);
|
|
31
26
|
|
|
32
27
|
export type WorkspaceCapability = z.infer<typeof workspaceCapabilitySchema>;
|
|
@@ -34,14 +29,9 @@ export type WorkspaceCapability = z.infer<typeof workspaceCapabilitySchema>;
|
|
|
34
29
|
export const WORKSPACE_CAPABILITIES = workspaceCapabilitySchema.options;
|
|
35
30
|
|
|
36
31
|
/**
|
|
37
|
-
* Features qu'un rôle peut ouvrir.
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* approuver, renommer, supprimer).
|
|
41
|
-
*
|
|
42
|
-
* `monitoring` n'y figure pas : la carte d'agrégat du même nom est réservée à
|
|
43
|
-
* l'administrateur global dans son espace personnel, donc aucun rôle d'espace
|
|
44
|
-
* ne peut l'accorder. Les vues d'appareil, elles, relèvent de `devices`.
|
|
32
|
+
* Features qu'un rôle peut ouvrir. `devices` en fait partie : voir la flotte
|
|
33
|
+
* d'un espace est un droit comme un autre (lecture = voir les appareils et leur
|
|
34
|
+
* supervision, écriture = les appairer, approuver, renommer, supprimer).
|
|
45
35
|
*/
|
|
46
36
|
export const workspaceFeatureIdSchema = z.enum([
|
|
47
37
|
'devices',
|
|
@@ -61,56 +51,32 @@ export const workspaceFeatureIdSchema = z.enum([
|
|
|
61
51
|
'projects',
|
|
62
52
|
'git',
|
|
63
53
|
/**
|
|
64
|
-
* Déploiement. `read` = voir les cibles
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
* ⚠️ Le droit le plus lourd de conséquences hors de DevEye : c'est le seul
|
|
69
|
-
* qui pousse quelque chose chez un tiers. Distinct de `projects` exprès —
|
|
70
|
-
* piloter le travail et livrer ne se confondent pas, et tout le monde n'a
|
|
71
|
-
* pas à pouvoir faire les deux.
|
|
54
|
+
* Déploiement. `read` = voir les cibles et leur historique, `write` =
|
|
55
|
+
* déclarer une cible, poser la clé d'API et déclencher une mise en
|
|
56
|
+
* production : le seul droit qui pousse quelque chose chez un tiers, d'où
|
|
57
|
+
* sa séparation de `projects`.
|
|
72
58
|
*/
|
|
73
59
|
'deploy',
|
|
74
60
|
'database',
|
|
75
61
|
/**
|
|
76
|
-
* Sauvegardes.
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
* déclarer une destination, poser sa clé secrète, créer un travail et le
|
|
81
|
-
* déclencher.
|
|
82
|
-
*
|
|
83
|
-
* ⚠️ Le droit le plus lourd en lecture après `finance`, et pour une raison
|
|
84
|
-
* différente: la liste des destinations dit **où sont les copies de tout**.
|
|
85
|
-
* Qui la lit sait quel bucket viser pour obtenir la base entière sans jamais
|
|
86
|
-
* toucher à DevEye. Distinct de `database` exprès — superviser une base et
|
|
87
|
-
* savoir où en dorment les vidages ne se confondent pas.
|
|
62
|
+
* Sauvegardes. `read` = voir destinations, travaux et historique, `write` =
|
|
63
|
+
* déclarer une destination, poser sa clé, créer et déclencher un travail.
|
|
64
|
+
* La lecture est lourde : la liste des destinations dit où sont les copies
|
|
65
|
+
* de tout, d'où sa séparation de `database`.
|
|
88
66
|
*/
|
|
89
67
|
'backup',
|
|
90
68
|
/**
|
|
91
|
-
* Finances.
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
* `
|
|
95
|
-
* corriger des opérations, tenir comptes, catégories, budgets et échéances.
|
|
96
|
-
*
|
|
97
|
-
* ⚠️ Le droit dont la lecture seule est déjà lourde: un livre de comptes dit
|
|
98
|
-
* ce qu'une structure gagne, ce qu'elle doit et à qui elle paie quoi. Le
|
|
99
|
-
* distinguer de `projects` n'est donc pas une commodité de rangement, c'est
|
|
100
|
-
* la raison d'être de la séparation.
|
|
69
|
+
* Finances. `read` = consulter soldes, journal et tableau de bord, `write` =
|
|
70
|
+
* saisir et corriger des opérations, tenir comptes, catégories, budgets et
|
|
71
|
+
* échéances. La lecture seule est déjà lourde, d'où sa séparation de
|
|
72
|
+
* `projects`.
|
|
101
73
|
*/
|
|
102
74
|
'finance',
|
|
103
75
|
/**
|
|
104
|
-
* Audience.
|
|
105
|
-
*
|
|
106
|
-
* qu'un projet ne fait que
|
|
107
|
-
*
|
|
108
|
-
* `read` = consulter les statistiques, `write` = déclarer un site, changer
|
|
109
|
-
* ses origines autorisées, sa rétention, le supprimer.
|
|
110
|
-
*
|
|
111
|
-
* ⚠️ Distinct de `projects` exprès, comme `git` l'est déjà : voir les
|
|
112
|
-
* chiffres d'un site livré et piloter le travail qui le produit ne se
|
|
113
|
-
* confondent pas, et tout le monde n'a pas à voir les deux.
|
|
76
|
+
* Audience. `read` = consulter les statistiques, `write` = déclarer un site,
|
|
77
|
+
* changer ses origines autorisées, sa rétention, le supprimer. Distinct de
|
|
78
|
+
* `projects`, comme `git` : un objet de l'espace qu'un projet ne fait que
|
|
79
|
+
* pointer.
|
|
114
80
|
*/
|
|
115
81
|
'audience',
|
|
116
82
|
/**
|
|
@@ -125,15 +91,11 @@ export type WorkspaceFeatureId = z.infer<typeof workspaceFeatureIdSchema>;
|
|
|
125
91
|
export const WORKSPACE_FEATURE_IDS = workspaceFeatureIdSchema.options;
|
|
126
92
|
|
|
127
93
|
/**
|
|
128
|
-
* Identifiant d'une feature
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
* d'appareil dans une disposition d'accueil (un UUID commence par un chiffre
|
|
134
|
-
* hexadécimal, jamais par `x`), et un tri visuel immédiat dans un grant ou un
|
|
135
|
-
* journal. Pas de tiret intérieur : l'id sert tel quel de préfixe de commande
|
|
136
|
-
* (`x-crypto.list`) et de valeur de segment live.
|
|
94
|
+
* Identifiant d'une feature externe (module tiers compilé dans l'app). Le
|
|
95
|
+
* préfixe `x-` exclut toute collision avec les ids natifs, les sujets réservés
|
|
96
|
+
* (`workspace`, `home`, `account`, `notify`) et les UUID d'appareil d'une
|
|
97
|
+
* disposition (un UUID commence par un chiffre hexadécimal). Pas de tiret
|
|
98
|
+
* intérieur : l'id sert tel quel de préfixe de commande (`x-crypto.list`).
|
|
137
99
|
*/
|
|
138
100
|
export const EXTERNAL_FEATURE_ID_PATTERN = /^x-[a-z][a-z0-9]{1,24}$/;
|
|
139
101
|
|
|
@@ -173,29 +135,19 @@ export const workspaceFeatureGrantSchema = z.object({
|
|
|
173
135
|
feature: featureIdSchema,
|
|
174
136
|
access: featureAccessSchema,
|
|
175
137
|
/**
|
|
176
|
-
* Gérer les
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
* le salon des sauvegardes ne se confient pas d'un bloc. Distinct de
|
|
182
|
-
* `access` exprès : régler où Uptime écrit relève de `access: write`, et
|
|
183
|
-
* se donne sans livrer les destinations elles-mêmes. Sans effet sur une
|
|
184
|
-
* fonctionnalité qui n'émet pas de notifications.
|
|
138
|
+
* Gérer les canaux d'alerte de cette fonctionnalité : en déclarer, corriger
|
|
139
|
+
* une adresse ou une URL, en supprimer, lire leurs destinations. Distinct de
|
|
140
|
+
* `access` : régler où Uptime écrit relève de `access: write` et se donne
|
|
141
|
+
* sans livrer les destinations. Sans effet sur une fonctionnalité qui
|
|
142
|
+
* n'émet pas de notifications.
|
|
185
143
|
*/
|
|
186
144
|
channels: z.boolean(),
|
|
187
145
|
/**
|
|
188
|
-
* Permissions
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
* Fermeture par défaut, une seule règle : une clé **absente** vaut « refusé »
|
|
194
|
-
* pour un toggle et « valeur par défaut du manifest » (la moins privilégiée)
|
|
195
|
-
* pour un choix. Le propriétaire, qui a tout, reçoit `true` / la valeur
|
|
196
|
-
* `ownerValue`. Une clé inconnue du manifest courant est rejetée à
|
|
197
|
-
* l'écriture du rôle et ignorée à la lecture ; un grant survivant à la
|
|
198
|
-
* dépose d'un module reste donc inerte, jamais dangereux.
|
|
146
|
+
* Permissions déclarées par la feature elle-même (`extraPermissions` du
|
|
147
|
+
* manifest) : booléen pour un `toggle`, valeur d'un `choice`. Une clé
|
|
148
|
+
* absente vaut « refusé » pour un toggle et « valeur par défaut du manifest »
|
|
149
|
+
* pour un choix ; le propriétaire reçoit `true` / `ownerValue`. Une clé
|
|
150
|
+
* inconnue du manifest est rejetée à l'écriture et ignorée à la lecture.
|
|
199
151
|
*/
|
|
200
152
|
extras: z.record(z.string().max(24), z.union([z.boolean(), z.string().max(32)])).default({})
|
|
201
153
|
});
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { deviceSchema } from '../domain/device';
|
|
3
|
+
import { fileMutateOpSchema, fileSearchFilterSchema } from '../domain/deviceFiles';
|
|
4
|
+
import { deviceLogFilterSchema, DEVICE_LOG_PAGE_MAX } from '../domain/deviceLogs';
|
|
5
|
+
import { packageManagerIdSchema } from '../domain/packages';
|
|
6
|
+
import { agentLifecycleActionSchema, agentPowerActionSchema } from '../protocol/agent';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Le transport des agents : les commandes WS qui relaient un ordre du
|
|
10
|
+
* `MonitorHub` à l'agent d'un appareil (abonnement aux métriques, fichiers,
|
|
11
|
+
* terminal, journaux, paquets, alimentation, service, mise à jour).
|
|
12
|
+
* Infrastructure native ; un module s'en sert par la capacité `agents`.
|
|
13
|
+
*
|
|
14
|
+
* Deux espaces de noms `agent.*` coexistent : les TRAMES du protocole agent
|
|
15
|
+
* (`protocol/agent.ts`, entre le serveur et le binaire) et les COMMANDES
|
|
16
|
+
* ci-dessous, envoyées par le client sur la socket de session. Quatre noms
|
|
17
|
+
* existent des deux côtés (`agent.collect`, `agent.update`, `agent.lifecycle`,
|
|
18
|
+
* `agent.power`) : la commande est ce que demande l'utilisateur, la trame ce
|
|
19
|
+
* que reçoit l'agent.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const deviceId = z.uuid();
|
|
23
|
+
|
|
24
|
+
/** Subscribe to live metric pushes for one or more devices. */
|
|
25
|
+
export const agentSubscribe = {
|
|
26
|
+
command: 'agent.subscribe' as const,
|
|
27
|
+
input: z.object({ deviceIds: z.array(deviceId).min(1).max(50) }),
|
|
28
|
+
output: z.object({ deviceIds: z.array(deviceId) })
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const agentUnsubscribe = {
|
|
32
|
+
command: 'agent.unsubscribe' as const,
|
|
33
|
+
input: z.object({ deviceIds: z.array(deviceId).min(1).max(50) }),
|
|
34
|
+
output: z.object({ deviceIds: z.array(deviceId) })
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** Ask an online device to push a fresh sample + report right now. */
|
|
38
|
+
export const agentCollect = {
|
|
39
|
+
command: 'agent.collect' as const,
|
|
40
|
+
input: z.object({ deviceId }),
|
|
41
|
+
/** `requested` is false when the device isn't currently connected. */
|
|
42
|
+
output: z.object({ deviceId, requested: z.boolean() })
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Run a system power action on the device (shutdown / reboot / suspend / hibernate
|
|
47
|
+
* / lock). Owner-or-admin; the agent must be online. The command only acknowledges
|
|
48
|
+
* the request — the agent applies it best-effort and the outcome streams back as a
|
|
49
|
+
* `device.powerResult` push event (the caller must be subscribed to the device).
|
|
50
|
+
*/
|
|
51
|
+
export const agentPower = {
|
|
52
|
+
command: 'agent.power' as const,
|
|
53
|
+
input: z.object({ deviceId, action: agentPowerActionSchema }),
|
|
54
|
+
output: z.object({ ok: z.boolean() })
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Stop or cleanly restart the agent process on the device (not the machine).
|
|
59
|
+
* `stop`: a supervised agent is relaunched by its manager, a standalone one
|
|
60
|
+
* stays offline until relaunched on the machine. `restart`: exit-and-relaunch.
|
|
61
|
+
* Owner-or-admin; the agent must be online. Fire-and-forget: the outcome is
|
|
62
|
+
* observed through presence.
|
|
63
|
+
*/
|
|
64
|
+
export const agentLifecycle = {
|
|
65
|
+
command: 'agent.lifecycle' as const,
|
|
66
|
+
input: z.object({ deviceId, action: agentLifecycleActionSchema }),
|
|
67
|
+
output: z.object({ ok: z.boolean() })
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Enable/disable the agent's per-user autostart (survives reboot, no privilege).
|
|
72
|
+
* Pushes `agent.service` to the connected agent (`install-user`/`uninstall-user`).
|
|
73
|
+
*/
|
|
74
|
+
export const agentSetAutostart = {
|
|
75
|
+
command: 'agent.setAutostart' as const,
|
|
76
|
+
input: z.object({ deviceId, enabled: z.boolean() }),
|
|
77
|
+
output: z.object({ device: deviceSchema })
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Ask the agent to become a root/system service. Hybrid: the agent pops an OS auth
|
|
82
|
+
* prompt if it has an interactive session, else replies `needsManualCommand` and the
|
|
83
|
+
* UI shows `manualCommand` (always returned, deterministic per platform) to run on
|
|
84
|
+
* the device. The new privilege/scope is observed on the agent's next report.
|
|
85
|
+
*/
|
|
86
|
+
export const agentElevate = {
|
|
87
|
+
command: 'agent.elevate' as const,
|
|
88
|
+
input: z.object({ deviceId }),
|
|
89
|
+
output: z.object({ device: deviceSchema, manualCommand: z.string() })
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/** Ask a root/system agent to drop back to a per-user service. */
|
|
93
|
+
export const agentDropPrivileges = {
|
|
94
|
+
command: 'agent.dropPrivileges' as const,
|
|
95
|
+
input: z.object({ deviceId }),
|
|
96
|
+
output: z.object({ device: deviceSchema, manualCommand: z.string() })
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Push a self-update to a connected device's agent: the server resolves the newer
|
|
101
|
+
* signed binary for the device's build target and sends the `agent.update` frame.
|
|
102
|
+
* Admin-only (Appareils page). Fails if the agent is offline, has no known target,
|
|
103
|
+
* the binary is missing/unsigned, or it's already up to date.
|
|
104
|
+
*/
|
|
105
|
+
export const agentUpdate = {
|
|
106
|
+
command: 'agent.update' as const,
|
|
107
|
+
input: z.object({ deviceId }),
|
|
108
|
+
output: z.object({ device: deviceSchema })
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Ask the agent to enumerate its package managers + pending updates. The result
|
|
113
|
+
* arrives asynchronously as a `package.list` push event (the caller must be
|
|
114
|
+
* subscribed to the device). The command itself only acknowledges the request.
|
|
115
|
+
*/
|
|
116
|
+
export const agentListPackages = {
|
|
117
|
+
command: 'agent.listPackages' as const,
|
|
118
|
+
input: z.object({ deviceId }),
|
|
119
|
+
output: z.object({ ok: z.boolean() })
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Apply all pending updates of one manager. Progress streams as `package.progress`
|
|
124
|
+
* events, ending with `package.done`. Fails if the agent is offline or the manager
|
|
125
|
+
* needs root and the agent isn't privileged (elevate it first, see `agent.elevate`).
|
|
126
|
+
*/
|
|
127
|
+
export const agentUpgradePackages = {
|
|
128
|
+
command: 'agent.upgradePackages' as const,
|
|
129
|
+
input: z.object({ deviceId, manager: packageManagerIdSchema }),
|
|
130
|
+
output: z.object({ ok: z.boolean() })
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Ask the agent to enumerate the log sources present on the device (system journal,
|
|
135
|
+
* Docker containers, log files…). Owner-or-admin + agent online. The command only
|
|
136
|
+
* acknowledges; the list arrives as a `device.logSources` push event (the caller
|
|
137
|
+
* must be subscribed to the device).
|
|
138
|
+
*/
|
|
139
|
+
export const agentLogSources = {
|
|
140
|
+
command: 'agent.logSources' as const,
|
|
141
|
+
input: z.object({ deviceId }),
|
|
142
|
+
output: z.object({ ok: z.boolean() })
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Query one source with an advanced filter. `queryId` correlates the streamed
|
|
147
|
+
* result back to this request (results have no requestId). Lines arrive as one or
|
|
148
|
+
* more `device.logLines` push events, the last carrying `done: true`.
|
|
149
|
+
*/
|
|
150
|
+
export const agentLogQuery = {
|
|
151
|
+
command: 'agent.logQuery' as const,
|
|
152
|
+
input: z.object({
|
|
153
|
+
deviceId,
|
|
154
|
+
sourceId: z.string().min(1).max(512),
|
|
155
|
+
/** Client-generated id echoed back on every `device.logLines` for this query. */
|
|
156
|
+
queryId: z.string().min(1).max(64),
|
|
157
|
+
filter: deviceLogFilterSchema.optional(),
|
|
158
|
+
limit: z.number().int().positive().max(DEVICE_LOG_PAGE_MAX).optional()
|
|
159
|
+
}),
|
|
160
|
+
output: z.object({ ok: z.boolean() })
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const sessionId = z.string().min(1).max(64);
|
|
164
|
+
const cols = z.number().int().min(1).max(2000);
|
|
165
|
+
const rows = z.number().int().min(1).max(2000);
|
|
166
|
+
/** Base64-encoded terminal bytes, capped per frame (~1.5 MB). */
|
|
167
|
+
const termData = z.string().max(2_000_000);
|
|
168
|
+
/**
|
|
169
|
+
* Optional OS account to open the session under. Restricted to safe username
|
|
170
|
+
* characters (no shell metacharacters), since it reaches a `su` on the device.
|
|
171
|
+
*/
|
|
172
|
+
export const terminalUser = z
|
|
173
|
+
.string()
|
|
174
|
+
.regex(/^[A-Za-z0-9._-]+$/, 'Nom d’utilisateur invalide')
|
|
175
|
+
.max(32);
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Open an interactive terminal (PTY) on the device. Owner-or-admin + agent online.
|
|
179
|
+
* `sessionId` is client-generated and ties every later input/resize/close and the
|
|
180
|
+
* streamed `device.termOutput` / `device.termExit` push events together (the caller
|
|
181
|
+
* must be subscribed). `user` runs the shell under that account (`su -l`); omitted
|
|
182
|
+
* → the account the agent itself runs as.
|
|
183
|
+
*/
|
|
184
|
+
export const agentTermOpen = {
|
|
185
|
+
command: 'agent.termOpen' as const,
|
|
186
|
+
input: z.object({ deviceId, sessionId, cols, rows, user: terminalUser.optional() }),
|
|
187
|
+
output: z.object({ ok: z.boolean() })
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
/** Send input (keystrokes / paste) to a terminal session. `data` is base64 bytes. */
|
|
191
|
+
export const agentTermInput = {
|
|
192
|
+
command: 'agent.termInput' as const,
|
|
193
|
+
input: z.object({ deviceId, sessionId, data: termData }),
|
|
194
|
+
output: z.object({ ok: z.boolean() })
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
/** Resize a terminal session's PTY to match the client viewport. */
|
|
198
|
+
export const agentTermResize = {
|
|
199
|
+
command: 'agent.termResize' as const,
|
|
200
|
+
input: z.object({ deviceId, sessionId, cols, rows }),
|
|
201
|
+
output: z.object({ ok: z.boolean() })
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
/** Close a terminal session (kills the shell and frees the PTY). */
|
|
205
|
+
export const agentTermClose = {
|
|
206
|
+
command: 'agent.termClose' as const,
|
|
207
|
+
input: z.object({ deviceId, sessionId }),
|
|
208
|
+
output: z.object({ ok: z.boolean() })
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
/** Correlates a request to its streamed result (results carry no requestId). */
|
|
212
|
+
const opId = z.string().min(1).max(64);
|
|
213
|
+
const path = z.string().min(1).max(4096);
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* List a directory. Owner-or-admin + agent online. The listing arrives as a
|
|
217
|
+
* `device.filesListing` push event keyed by `opId` (the caller must be subscribed).
|
|
218
|
+
*/
|
|
219
|
+
export const agentFilesList = {
|
|
220
|
+
command: 'agent.filesList' as const,
|
|
221
|
+
input: z.object({ deviceId, opId, path }),
|
|
222
|
+
output: z.object({ ok: z.boolean() })
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Analyse a directory's recursive disk usage (ncdu-style): the total size of each
|
|
227
|
+
* immediate child. Result arrives as a `device.filesUsage` push event.
|
|
228
|
+
*/
|
|
229
|
+
export const agentFilesAnalyze = {
|
|
230
|
+
command: 'agent.filesAnalyze' as const,
|
|
231
|
+
input: z.object({ deviceId, opId, path }),
|
|
232
|
+
output: z.object({ ok: z.boolean() })
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Recursively search a directory with an advanced filter (name/extension/content,
|
|
237
|
+
* date & size windows). Matches arrive as a `device.filesMatches` push event.
|
|
238
|
+
*/
|
|
239
|
+
export const agentFilesSearch = {
|
|
240
|
+
command: 'agent.filesSearch' as const,
|
|
241
|
+
input: z.object({ deviceId, opId, path, filter: fileSearchFilterSchema }),
|
|
242
|
+
output: z.object({ ok: z.boolean() })
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Mutate the filesystem: `delete` (file or directory, recursive), `mkdir`, or
|
|
247
|
+
* `rename` (needs `dest`). The outcome arrives as a `device.filesOp` push event.
|
|
248
|
+
*/
|
|
249
|
+
export const agentFilesMutate = {
|
|
250
|
+
command: 'agent.filesMutate' as const,
|
|
251
|
+
input: z.object({ deviceId, opId, op: fileMutateOpSchema, path, dest: path.optional() }),
|
|
252
|
+
output: z.object({ ok: z.boolean() })
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Download a file. Bytes stream back as `device.filesChunk` push events keyed by
|
|
257
|
+
* `opId` (base64, the last with `done: true`); the client reassembles them.
|
|
258
|
+
*/
|
|
259
|
+
export const agentFilesDownload = {
|
|
260
|
+
command: 'agent.filesDownload' as const,
|
|
261
|
+
input: z.object({ deviceId, opId, path }),
|
|
262
|
+
output: z.object({ ok: z.boolean() })
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Upload one chunk of a file at `offset` (0 truncates/creates it). The completion
|
|
267
|
+
* (on `done`) arrives as a `device.filesOp` push event (op `upload`).
|
|
268
|
+
*/
|
|
269
|
+
export const agentFilesUpload = {
|
|
270
|
+
command: 'agent.filesUpload' as const,
|
|
271
|
+
input: z.object({
|
|
272
|
+
deviceId,
|
|
273
|
+
opId,
|
|
274
|
+
path,
|
|
275
|
+
offset: z.number().int().nonnegative(),
|
|
276
|
+
data: z.string().max(1_400_000),
|
|
277
|
+
done: z.boolean()
|
|
278
|
+
}),
|
|
279
|
+
output: z.object({ ok: z.boolean() })
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
export const agentCommands = [
|
|
283
|
+
agentSubscribe,
|
|
284
|
+
agentUnsubscribe,
|
|
285
|
+
agentCollect,
|
|
286
|
+
agentPower,
|
|
287
|
+
agentLifecycle,
|
|
288
|
+
agentSetAutostart,
|
|
289
|
+
agentElevate,
|
|
290
|
+
agentDropPrivileges,
|
|
291
|
+
agentUpdate,
|
|
292
|
+
agentListPackages,
|
|
293
|
+
agentUpgradePackages,
|
|
294
|
+
agentLogSources,
|
|
295
|
+
agentLogQuery,
|
|
296
|
+
agentTermOpen,
|
|
297
|
+
agentTermInput,
|
|
298
|
+
agentTermResize,
|
|
299
|
+
agentTermClose,
|
|
300
|
+
agentFilesList,
|
|
301
|
+
agentFilesAnalyze,
|
|
302
|
+
agentFilesSearch,
|
|
303
|
+
agentFilesMutate,
|
|
304
|
+
agentFilesDownload,
|
|
305
|
+
agentFilesUpload
|
|
306
|
+
] as const;
|
package/src/features/live.ts
CHANGED
|
@@ -2,16 +2,11 @@ import { z } from 'zod';
|
|
|
2
2
|
import { liveCursorSchema, livePathSchema, livePeerSchema, liveTopicSchema } from '../domain/live';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Déclare où je suis, et récupère l'état de la salle.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* gratuitement, par le même chemin que tout le reste.
|
|
11
|
-
*
|
|
12
|
-
* La réponse porte l'instantané de la salle — même motif que
|
|
13
|
-
* `cloudSync.subscribe` : aucun trou entre l'inscription et la première
|
|
14
|
-
* diffusion, et une reconnexion se resynchronise par ce seul appel.
|
|
5
|
+
* Déclare où je suis, et récupère l'état de la salle. L'espace n'est pas dans
|
|
6
|
+
* l'entrée : il voyage sur l'enveloppe, résolu et vérifié par le dispatcheur.
|
|
7
|
+
* La réponse porte l'instantané de la salle : aucun trou entre l'inscription
|
|
8
|
+
* et la première diffusion, et une reconnexion se resynchronise par ce seul
|
|
9
|
+
* appel.
|
|
15
10
|
*/
|
|
16
11
|
export const liveHere = {
|
|
17
12
|
command: 'live.here' as const,
|
|
@@ -22,42 +17,27 @@ export const liveHere = {
|
|
|
22
17
|
export const liveCommands = [liveHere] as const;
|
|
23
18
|
|
|
24
19
|
/**
|
|
25
|
-
* Les positions de curseur,
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* pour une trame émise vingt fois par seconde dont on n'attend aucune réponse.
|
|
30
|
-
* Le client la poste par `ws.post`, le serveur la traite sur une voie rapide
|
|
31
|
-
* avant la recherche de commande.
|
|
20
|
+
* Les positions de curseur, hors du registre des commandes : `ws.send` y
|
|
21
|
+
* ouvrirait une promesse et armerait un délai pour une trame émise vingt fois
|
|
22
|
+
* par seconde sans réponse. Le client la poste par `ws.post`, le serveur la
|
|
23
|
+
* traite sur une voie rapide.
|
|
32
24
|
*/
|
|
33
25
|
export const LIVE_CURSOR_COMMAND = 'live.cursor' as const;
|
|
34
26
|
|
|
35
27
|
/**
|
|
36
|
-
* Ce que porte une trame de curseur : des coordonnées,
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* d'autorisation, elle ne peut donc rien accepter du client qui déciderait de
|
|
40
|
-
* *qui verra* la trame. Le lieu vient du dernier `live.here`, lui passé par le
|
|
41
|
-
* dispatcheur. `cursor: null` = le pointeur a quitté la surface.
|
|
28
|
+
* Ce que porte une trame de curseur : des coordonnées, rien d'autre. Ni chemin
|
|
29
|
+
* ni espace : la voie rapide court-circuite l'autorisation, le lieu vient du
|
|
30
|
+
* dernier `live.here`. `cursor: null` = le pointeur a quitté la surface.
|
|
42
31
|
*/
|
|
43
32
|
export const liveCursorFrameSchema = z.object({ cursor: liveCursorSchema.nullable() });
|
|
44
33
|
export type LiveCursorFrame = z.infer<typeof liveCursorFrameSchema>;
|
|
45
34
|
|
|
46
35
|
/**
|
|
47
|
-
* « Untel est en train d'écrire… », sur la même voie rapide que les curseurs
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* Volontairement **générique** : la trame ne dit pas *quoi* est en train d'être
|
|
54
|
-
* écrit. Le lieu vient du dernier `live.here`, comme pour les curseurs, donc
|
|
55
|
-
* n'importe quelle feature peut s'en servir sans toucher au moteur — un fil de
|
|
56
|
-
* discussion de projet aujourd'hui, une note à plusieurs demain.
|
|
57
|
-
*
|
|
58
|
-
* Le serveur applique une péremption : sans rafraîchissement, un pair cesse
|
|
59
|
-
* d'être « en train d'écrire » tout seul. C'est ce qui empêche un onglet fermé
|
|
60
|
-
* brutalement de laisser un fantôme à l'écran.
|
|
36
|
+
* « Untel est en train d'écrire… », sur la même voie rapide que les curseurs et
|
|
37
|
+
* hors du registre pour la même raison. Générique : la trame ne dit pas quoi,
|
|
38
|
+
* le lieu vient du dernier `live.here`. Le serveur applique une péremption :
|
|
39
|
+
* sans rafraîchissement, un pair cesse d'écrire tout seul (pas de fantôme
|
|
40
|
+
* après un onglet fermé brutalement).
|
|
61
41
|
*/
|
|
62
42
|
export const LIVE_TYPING_COMMAND = 'live.typing' as const;
|
|
63
43
|
export const liveTypingFrameSchema = z.object({ typing: z.boolean() });
|
package/src/features/notify.ts
CHANGED
|
@@ -12,31 +12,15 @@ import {
|
|
|
12
12
|
} from '../domain/notifications';
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* Les canaux d'alerte de l'espace, et les routes qui pointent vers eux.
|
|
15
|
+
* Les canaux d'alerte de l'espace, et les routes qui pointent vers eux. Un
|
|
16
|
+
* module à part : la fonctionnalité est un argument, pas un préfixe de
|
|
17
|
+
* commande, et un émetteur de plus ne coûte qu'une valeur dans
|
|
18
|
+
* `notificationFeatureSchema`.
|
|
16
19
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* dialogue client les reconstituait déjà par concaténation
|
|
22
|
-
* (`` `${feature}.getSettings` ``), ce qui disait tout : la fonctionnalité
|
|
23
|
-
* n'était pas dans la commande, elle était dans un **argument**. Elle l'est
|
|
24
|
-
* désormais pour de bon.
|
|
25
|
-
*
|
|
26
|
-
* Conséquence directe : brancher un sixième émetteur ne coûte plus trois
|
|
27
|
-
* commandes, trois entrées de registre et trois handlers, mais une valeur de
|
|
28
|
-
* plus dans `notificationFeatureSchema`.
|
|
29
|
-
*
|
|
30
|
-
* ## Deux étages d'autorisation, et ils ne sont pas les mêmes
|
|
31
|
-
*
|
|
32
|
-
* Gérer les **canaux** d'une fonctionnalité relève du champ `channels` de son
|
|
33
|
-
* grant de rôle (migration 093) : un canal appartient à une fonctionnalité
|
|
34
|
-
* (091), et son adresse ne se livre qu'à qui gère les canaux de celle-ci. Les
|
|
35
|
-
* **routes**, elles, relèvent de la fonctionnalité visée (`{ feature, level:
|
|
36
|
-
* 'write' }`) : décider où Uptime écrit fait partie du réglage d'Uptime, et n'a
|
|
37
|
-
* pas à ouvrir la gestion des destinations. C'est la séparation qui permet de
|
|
38
|
-
* confier le routage d'une fonctionnalité sans confier l'adresse de
|
|
39
|
-
* l'astreinte.
|
|
20
|
+
* Deux étages d'autorisation : gérer les canaux d'une fonctionnalité relève du
|
|
21
|
+
* champ `channels` de son grant ; les routes relèvent de la fonctionnalité
|
|
22
|
+
* visée (`{ feature, level: 'write' }`). On peut confier le routage sans
|
|
23
|
+
* confier l'adresse de l'astreinte.
|
|
40
24
|
*/
|
|
41
25
|
|
|
42
26
|
const channelId = z.number().int().positive();
|
|
@@ -62,11 +46,8 @@ export const notifyChannelUpdate = {
|
|
|
62
46
|
};
|
|
63
47
|
|
|
64
48
|
/**
|
|
65
|
-
* Ce qu'une suppression emporterait,
|
|
66
|
-
*
|
|
67
|
-
* Lue par la confirmation pour nommer les routes qui vont cesser de prévenir.
|
|
68
|
-
* Séparée de la suppression elle-même parce qu'un écran qui demande « êtes-vous
|
|
69
|
-
* sûr ? » sans dire de quoi ne fait pas confirmer, il fait cliquer.
|
|
49
|
+
* Ce qu'une suppression emporterait, sans rien supprimer : lue par la
|
|
50
|
+
* confirmation pour nommer les routes qui vont cesser de prévenir.
|
|
70
51
|
*/
|
|
71
52
|
export const notifyChannelUsage = {
|
|
72
53
|
command: 'notify.channelUsage' as const,
|
|
@@ -86,14 +67,7 @@ export const notifyChannelReorder = {
|
|
|
86
67
|
output: z.object({ ok: z.literal(true) })
|
|
87
68
|
};
|
|
88
69
|
|
|
89
|
-
/**
|
|
90
|
-
* Un envoi d'essai **sur un seul canal**, tel qu'il est enregistré.
|
|
91
|
-
*
|
|
92
|
-
* L'ancien dialogue devait enregistrer avant de tester, faute de quoi l'essai
|
|
93
|
-
* partait sur les réglages précédents. Un canal étant une entité à part entière,
|
|
94
|
-
* l'essai vise directement son identifiant : plus d'enregistrement forcé, et
|
|
95
|
-
* plus de doute sur ce qui vient d'être éprouvé.
|
|
96
|
-
*/
|
|
70
|
+
/** Un envoi d'essai sur un seul canal, tel qu'il est enregistré. */
|
|
97
71
|
export const notifyChannelTest = {
|
|
98
72
|
command: 'notify.channelTest' as const,
|
|
99
73
|
input: z.object({ id: channelId }),
|
|
@@ -107,31 +81,19 @@ export const notifyRouteGet = {
|
|
|
107
81
|
output: z.object({
|
|
108
82
|
route: notificationRouteSchema,
|
|
109
83
|
/**
|
|
110
|
-
* Les canaux de la route qui
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
* dans son espace d'origine. Sans cette liste, l'écran afficherait
|
|
114
|
-
* « aucun canal » sur un élément qui prévient bel et bien — le mensonge
|
|
115
|
-
* exact que la projection devait éviter.
|
|
116
|
-
*
|
|
117
|
-
* Rendus **masqués** : leur genre (« Salon Discord d'un autre espace »),
|
|
118
|
-
* jamais leur identité ni leur adresse.
|
|
84
|
+
* Les canaux de la route qui n'appartiennent pas à cet espace (élément
|
|
85
|
+
* projeté depuis ailleurs). Rendus masqués : leur genre, jamais leur
|
|
86
|
+
* identité ni leur adresse.
|
|
119
87
|
*/
|
|
120
88
|
foreign: z.array(notificationChannelSchema),
|
|
121
89
|
/**
|
|
122
|
-
* Cette route se règle-t-elle
|
|
123
|
-
*
|
|
124
|
-
* Faux sur un élément projeté depuis un autre espace : ses canaux
|
|
125
|
-
* appartiennent à cet espace-là, et l'ordonnanceur qui le sonde y
|
|
126
|
-
* tourne. Laisser l'écran proposer le réglage produirait un geste que
|
|
127
|
-
* le serveur refuse — un écran qui ment, pas une garde.
|
|
90
|
+
* Cette route se règle-t-elle d'ici ? Faux sur un élément projeté
|
|
91
|
+
* depuis un autre espace : ses canaux appartiennent à cet espace-là.
|
|
128
92
|
*/
|
|
129
93
|
managedHere: z.boolean(),
|
|
130
94
|
/**
|
|
131
|
-
* L'espace
|
|
132
|
-
*
|
|
133
|
-
* qui permet à l'écran, sur un élément projeté, de proposer d'aller
|
|
134
|
-
* régler chez lui plutôt que d'expliquer un refus.
|
|
95
|
+
* L'espace où cette route se règle : le domicile de l'élément (égal à
|
|
96
|
+
* l'espace de l'enveloppe quand `managedHere` est vrai).
|
|
135
97
|
*/
|
|
136
98
|
homeWorkspaceId: z.number().int().positive()
|
|
137
99
|
})
|
|
@@ -143,7 +105,7 @@ export const notifyRouteSet = {
|
|
|
143
105
|
output: z.object({ route: notificationRouteSchema })
|
|
144
106
|
};
|
|
145
107
|
|
|
146
|
-
/** Un essai sur la route entière
|
|
108
|
+
/** Un essai sur la route entière : ce que verrait une vraie alerte. */
|
|
147
109
|
export const notifyRouteTest = {
|
|
148
110
|
command: 'notify.routeTest' as const,
|
|
149
111
|
input: notificationRouteTargetSchema,
|