@runeya/runeya 2.0.144 → 2.0.146

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 (148) hide show
  1. package/agent/index.js +33 -22
  2. package/agent/index.js.map +1 -1
  3. package/agent-manager-R7BPQO6D.js +11 -0
  4. package/chat-LPML22J5.js +24 -0
  5. package/{chunk-7L6AQUBR.js → chunk-4LJQIQMN.js} +6 -2
  6. package/{chunk-7L6AQUBR.js.map → chunk-4LJQIQMN.js.map} +1 -1
  7. package/{chunk-7HIPFSOA.js → chunk-57P45FNY.js} +11 -3
  8. package/chunk-57P45FNY.js.map +1 -0
  9. package/chunk-6IBPK6UE.js +1063 -0
  10. package/chunk-6IBPK6UE.js.map +1 -0
  11. package/{chunk-GXE5TLLJ.js → chunk-KV4GJKXU.js} +17 -17
  12. package/{chunk-XAFOP677.js → chunk-MZSDTA4A.js} +480 -233
  13. package/chunk-MZSDTA4A.js.map +1 -0
  14. package/{chunk-UR3HL373.js → chunk-NPMSBYXC.js} +8 -8
  15. package/{chunk-P73PTVII.js → chunk-SEPAURTQ.js} +369 -1282
  16. package/chunk-SEPAURTQ.js.map +1 -0
  17. package/{chunk-NSS47H26.js → chunk-UD42LACH.js} +348 -78
  18. package/chunk-UD42LACH.js.map +1 -0
  19. package/{chunk-IXO5KIO5.js → chunk-VPSQJOSN.js} +23 -2
  20. package/{chunk-IXO5KIO5.js.map → chunk-VPSQJOSN.js.map} +1 -1
  21. package/{companion-2LHKU577.js → companion-W3UMZWGH.js} +11 -7
  22. package/{desktop-installer-page-6JDSXK2E.js → desktop-installer-page-I5FHFDES.js} +11 -8
  23. package/desktop-installer-page-I5FHFDES.js.map +1 -0
  24. package/{desktop-launcher-YPNRQMIS.js → desktop-launcher-GD4QPM66.js} +3 -3
  25. package/external-NMKQPROK.js +387 -0
  26. package/external-NMKQPROK.js.map +1 -0
  27. package/index.js +34 -12
  28. package/index.js.map +1 -1
  29. package/{install-gate-2NNH7RM2.js → install-gate-PVMGJDWE.js} +2 -2
  30. package/mcp-server/external.d.ts +44 -0
  31. package/mcp-server/external.js +2679 -0
  32. package/mcp-server/external.js.map +1 -0
  33. package/mcp-server/index.js +142 -40
  34. package/mcp-server/index.js.map +1 -1
  35. package/mcp-server/launch.d.ts +19 -1
  36. package/mcp-server/launch.js +20 -0
  37. package/mcp-server/launch.js.map +1 -1
  38. package/package.json +1 -1
  39. package/settings-manager-XPLBO35P.js +10 -0
  40. package/src-DCCEUWE6.js +385 -0
  41. package/{src-5TEXF3SU.js → src-LEDGW5KP.js} +1029 -536
  42. package/src-LEDGW5KP.js.map +1 -0
  43. package/{vault-resolver-VK6W5J6W.js → vault-resolver-E7KPKA5C.js} +3 -2
  44. package/vault-resolver-E7KPKA5C.js.map +1 -0
  45. package/web/assets/{AgentScalarChatInterface.vue-B-SjOsdj.js → AgentScalarChatInterface.vue-BYszptaB.js} +1 -1
  46. package/web/assets/{AgentTerminalPage-JzC8DUgu.js → AgentTerminalPage-C3TJ1NL9.js} +1 -1
  47. package/web/assets/{AiChatModals-D1928AxG.js → AiChatModals-D3scRE3S.js} +1 -1
  48. package/web/assets/AppLayout-BMQUjPKe.js +1 -0
  49. package/web/assets/{AppLayout-8sBPr3od.css → AppLayout-tsw5Ow0w.css} +1 -1
  50. package/web/assets/{AuditPage-BZIJ3_Kr.js → AuditPage-CKcExBws.js} +1 -1
  51. package/web/assets/{CloudAccountPage-DBv470sT.js → CloudAccountPage-B6oPg0BZ.js} +1 -1
  52. package/web/assets/{CloudMessagingPage-qCI8-UDL.js → CloudMessagingPage-C5fwVe-F.js} +1 -1
  53. package/web/assets/DashboardPage-9RV7sx3e.js +1 -0
  54. package/web/assets/{DashboardPage-D9-PpC3F.css → DashboardPage-FvNuC_e4.css} +1 -1
  55. package/web/assets/{DirectoryPickerDialog-B5DvDfQP.js → DirectoryPickerDialog-C06BXyfJ.js} +1 -1
  56. package/web/assets/{EnvironmentForm-Bw8Djnqm.js → EnvironmentForm-CAvDoira.js} +1 -1
  57. package/web/assets/{EnvironmentFormPage-B59uiYtp.js → EnvironmentFormPage-BmAxhN-m.js} +1 -1
  58. package/web/assets/{EnvironmentListPage-W9zLDZAg.js → EnvironmentListPage-B8uATPBI.js} +1 -1
  59. package/web/assets/KanbanBoardPage-CQsOkRUf.js +4 -0
  60. package/web/assets/KanbanBoardPage-eHmsrFGA.css +1 -0
  61. package/web/assets/{KanbanListPage-Bn9k8Rpc.js → KanbanListPage-CWZqkjD6.js} +1 -1
  62. package/web/assets/{LaunchProjectPage-Dc7v1UjQ.js → LaunchProjectPage-saSmC7Pr.js} +1 -1
  63. package/web/assets/LaunchServicesPage-BkKyLMEz.js +1 -0
  64. package/web/assets/LaunchServicesPage-DoLDPqQq.css +1 -0
  65. package/web/assets/{LaunchShell-LonTJpb3.js → LaunchShell-DUrSunB4.js} +1 -1
  66. package/web/assets/{LogViewer-DkuqWYkQ.js → LogViewer-CZBYkH9J.js} +1 -1
  67. package/web/assets/{OrgKeyPrompt-By1VjlSN.js → OrgKeyPrompt-DzQ7pwbs.js} +1 -1
  68. package/web/assets/{ProcessControls-C0skzKGL.js → ProcessControls-BEeoNlNV.js} +1 -1
  69. package/web/assets/{ProcessDetailPage-BvPX6z_X.js → ProcessDetailPage-CQgu5uKm.js} +1 -1
  70. package/web/assets/{ProjectCreateDialog-C4o9u15L.js → ProjectCreateDialog-BolrhRD-.js} +1 -1
  71. package/web/assets/{ProjectLocationGate-BJ1CSQt0.js → ProjectLocationGate-DhMVdIjN.js} +1 -1
  72. package/web/assets/{ProjectPublishDialog-Cp-zXdGm.js → ProjectPublishDialog-DsZNWrfz.js} +1 -1
  73. package/web/assets/{RandomJobTestPage-DF4a1U13.js → RandomJobTestPage-BUS2Ty6-.js} +1 -1
  74. package/web/assets/ScenarioEditorPage-MIAGh2us.js +14 -0
  75. package/web/assets/{SendToChatDialog-Dg2SHkVD.js → SendToChatDialog-BxWsyv29.js} +1 -1
  76. package/web/assets/{ServiceControlsBar-q2VHWaxK.js → ServiceControlsBar-DqDPAZt7.js} +1 -1
  77. package/web/assets/ServiceDetailPage-hkSAzZ-l.js +2 -0
  78. package/web/assets/{ServiceFormPage-tT_n7WUy.js → ServiceFormPage-DXqhqXg8.js} +1 -1
  79. package/web/assets/SettingsPage-4pKUgKPm.css +1 -0
  80. package/web/assets/SettingsPage-DS2YhMIM.js +2086 -0
  81. package/web/assets/{UnlockPage-BKAdMJkb.js → UnlockPage-s8SgVcKk.js} +1 -1
  82. package/web/assets/{VaultMigrationPage-D9KUTTss.js → VaultMigrationPage-s0b44MDM.js} +1 -1
  83. package/web/assets/{VersionBadge-moKP5bAv.js → VersionBadge-BmArk24p.js} +1 -1
  84. package/web/assets/{WikiPage-hLUndDxo.js → WikiPage-DADu8WUA.js} +1 -1
  85. package/web/assets/{WikiView-rrjXRR72.js → WikiView-DtGwIt_Q.js} +1 -1
  86. package/web/assets/{add-scalar-classes-CBfRr8B2.js → add-scalar-classes-dWEWPlFD.js} +2 -2
  87. package/web/assets/{agent.store-B3dVemmT.js → agent.store-CKLAkF9a.js} +1 -1
  88. package/web/assets/app-mode-BVpTuh0i.js +1 -0
  89. package/web/assets/auth-Cg9BWTfK.js +2 -0
  90. package/web/assets/{cloud-chat.store-Wg1poFem.js → cloud-chat.store-b-BHGz7I.js} +1 -1
  91. package/web/assets/{cloud-client-DQlUaoAw.js → cloud-client-D_zCiW0s.js} +2 -2
  92. package/web/assets/{cloud-session-DRUqcSGP.js → cloud-session-DOaDpZTy.js} +2 -2
  93. package/web/assets/dist-N4nrnbnA.js +1 -0
  94. package/web/assets/{editor.store-SoH48ux2.js → editor.store-DCAZSeVG.js} +1 -1
  95. package/web/assets/i18n-D0afQEGs.js +1 -0
  96. package/web/assets/{index-D_x2s8Ut.js → index-D8VRbyoP.js} +101 -101
  97. package/web/assets/index-DsezNbSM.css +1 -0
  98. package/web/assets/{modal-CpsVK_dA.js → modal-qYgTMkYb.js} +1 -1
  99. package/web/assets/{project-launch.store-C3fZeRGb.js → project-launch.store-y7JBMNiE.js} +1 -1
  100. package/web/assets/{project.store-BEk4fciH.js → project.store-DGJr0Qr9.js} +1 -1
  101. package/web/assets/{scenario.store-D-JrRiVO.js → scenario.store-YWHL2_wu.js} +3 -3
  102. package/web/assets/{service.store-B5uYZtyM.js → service.store-5jWsJxPx.js} +1 -1
  103. package/web/assets/{settings-scope-Blnwj3rR.js → settings-scope-Bv7QGPrt.js} +1 -1
  104. package/web/assets/{settings.store-Bo3FYs1o.js → settings.store-bEsB7oKb.js} +1 -1
  105. package/web/assets/{src-CAwAaB5j.js → src-C-D_y7ou.js} +1 -1
  106. package/web/assets/{src-DValZNJU.js → src-Dnwk6NpV.js} +5 -5
  107. package/web/assets/src-g1DRo8xZ.css +1 -0
  108. package/web/assets/{trpc-DbsE5GvQ.js → trpc-Drabzwpd.js} +3 -3
  109. package/web/assets/{useCloudOrg-DygUBG1a.js → useCloudOrg-BAQEtFPl.js} +1 -1
  110. package/web/assets/{useEnvVarAutocomplete-DmYNOXgd.js → useEnvVarAutocomplete-BqGTODUv.js} +1 -1
  111. package/web/assets/{useProjectCatalog-D2mHB7dg.js → useProjectCatalog-yocjIKt6.js} +1 -1
  112. package/web/assets/{useProjectCloudOrg-avTCVold.js → useProjectCloudOrg-CEN9-_OA.js} +1 -1
  113. package/web/assets/{useServiceUrlHost-Q3fzGox3.js → useServiceUrlHost-BuoPEXZX.js} +1 -1
  114. package/web/assets/{vue-draggable-plus-WgrMXXmE.js → vue-draggable-plus-DX4rTBiu.js} +1 -1
  115. package/web/index.html +19 -18
  116. package/agent-manager-M4NA5YFD.js +0 -11
  117. package/chat-X3X527EX.js +0 -23
  118. package/chunk-7HIPFSOA.js.map +0 -1
  119. package/chunk-NSS47H26.js.map +0 -1
  120. package/chunk-P73PTVII.js.map +0 -1
  121. package/chunk-XAFOP677.js.map +0 -1
  122. package/desktop-installer-page-6JDSXK2E.js.map +0 -1
  123. package/settings-manager-32SVMAFN.js +0 -10
  124. package/src-5TEXF3SU.js.map +0 -1
  125. package/web/assets/AppLayout-CVQIBYqg.js +0 -1
  126. package/web/assets/DashboardPage-D0UZM0mr.js +0 -1
  127. package/web/assets/KanbanBoardPage-DRACMk_D.css +0 -1
  128. package/web/assets/KanbanBoardPage-DwPqoJDJ.js +0 -4
  129. package/web/assets/LaunchServicesPage-CXUS9O3y.js +0 -1
  130. package/web/assets/LaunchServicesPage-DJDIN8HU.css +0 -1
  131. package/web/assets/ScenarioEditorPage-Dl-gEkJI.js +0 -14
  132. package/web/assets/ServiceDetailPage-CJ8WaFod.js +0 -2
  133. package/web/assets/SettingsPage-CWakkv0V.js +0 -2086
  134. package/web/assets/SettingsPage-aGSxZw1n.css +0 -1
  135. package/web/assets/auth-BCrvnP1U.js +0 -2
  136. package/web/assets/dist-DoChtT9t.js +0 -1
  137. package/web/assets/i18n-BszFg0ko.js +0 -1
  138. package/web/assets/index-C_JV7fYy.css +0 -1
  139. package/web/assets/src-DuBXN1hu.css +0 -1
  140. /package/{agent-manager-M4NA5YFD.js.map → agent-manager-R7BPQO6D.js.map} +0 -0
  141. /package/{chat-X3X527EX.js.map → chat-LPML22J5.js.map} +0 -0
  142. /package/{chunk-GXE5TLLJ.js.map → chunk-KV4GJKXU.js.map} +0 -0
  143. /package/{chunk-UR3HL373.js.map → chunk-NPMSBYXC.js.map} +0 -0
  144. /package/{companion-2LHKU577.js.map → companion-W3UMZWGH.js.map} +0 -0
  145. /package/{desktop-launcher-YPNRQMIS.js.map → desktop-launcher-GD4QPM66.js.map} +0 -0
  146. /package/{install-gate-2NNH7RM2.js.map → install-gate-PVMGJDWE.js.map} +0 -0
  147. /package/{settings-manager-32SVMAFN.js.map → settings-manager-XPLBO35P.js.map} +0 -0
  148. /package/{vault-resolver-VK6W5J6W.js.map → src-DCCEUWE6.js.map} +0 -0
@@ -0,0 +1,387 @@
1
+ import {
2
+ RUNEYA_CAPABILITIES,
3
+ applyProjectAnchor,
4
+ buildMcpShape,
5
+ callTrpc,
6
+ runCapability,
7
+ toRawParams
8
+ } from "./chunk-6IBPK6UE.js";
9
+ import "./chunk-MZSDTA4A.js";
10
+
11
+ // ../../packages/mcp-server/src/external.ts
12
+ import { homedir } from "os";
13
+ import { join as join2 } from "path";
14
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
15
+ import { z as z2 } from "zod";
16
+
17
+ // ../../packages/mcp-server/src/project-anchors.ts
18
+ function createProjectAnchors(env, cwd, containing) {
19
+ const anchors = /* @__PURE__ */ new Map();
20
+ return (key, baseUrl, authToken) => {
21
+ if (env.projectId) return Promise.resolve(env);
22
+ let anchor = anchors.get(key);
23
+ if (!anchor) {
24
+ anchor = containing(baseUrl, authToken, cwd).then((found) => {
25
+ const p = found;
26
+ return p ? { projectId: p.projectId, ...p.orgId ? { orgId: p.orgId } : {} } : {};
27
+ }).catch((err) => {
28
+ anchors.delete(key);
29
+ throw new Error(`Impossible de savoir \xE0 quel projet Runeya appartient le dossier ${cwd} (${err.message}). Nomme le projet : appelle list_projects, puis passe son projectId.`);
30
+ });
31
+ anchors.set(key, anchor);
32
+ }
33
+ return anchor;
34
+ };
35
+ }
36
+
37
+ // ../../packages/mcp-server/src/session-token.ts
38
+ var RETRY_DELAY_MS = 1e3;
39
+ function jwtExpiresAt(token) {
40
+ try {
41
+ const payload = JSON.parse(Buffer.from(token.split(".")[1] ?? "", "base64url").toString("utf8"));
42
+ return typeof payload.exp === "number" ? payload.exp : null;
43
+ } catch {
44
+ return null;
45
+ }
46
+ }
47
+ function startSessionToken(initial, baseUrl, fetchImpl = fetch) {
48
+ let token = initial;
49
+ let timer = null;
50
+ let stopped = false;
51
+ const arm = (delayMs) => {
52
+ if (stopped) return;
53
+ timer = setTimeout(() => {
54
+ void refresh();
55
+ }, delayMs);
56
+ timer.unref?.();
57
+ };
58
+ const schedule = () => {
59
+ const exp = jwtExpiresAt(token);
60
+ if (exp === null) return;
61
+ const remainingMs = exp * 1e3 - Date.now();
62
+ if (remainingMs > 0) arm(remainingMs / 2);
63
+ };
64
+ const retry = () => {
65
+ const exp = jwtExpiresAt(token);
66
+ if (exp !== null && exp * 1e3 - Date.now() > RETRY_DELAY_MS) arm(RETRY_DELAY_MS);
67
+ };
68
+ async function refresh() {
69
+ timer = null;
70
+ let res;
71
+ try {
72
+ res = await fetchImpl(`${baseUrl}/api/auth/refresh`, {
73
+ method: "POST",
74
+ headers: { Authorization: `Bearer ${token}` },
75
+ signal: AbortSignal.timeout(5e3)
76
+ });
77
+ } catch {
78
+ retry();
79
+ return;
80
+ }
81
+ if (res.status === 401) return;
82
+ if (!res.ok) {
83
+ retry();
84
+ return;
85
+ }
86
+ const body = await res.json().catch(() => null);
87
+ if (typeof body?.token !== "string") {
88
+ retry();
89
+ return;
90
+ }
91
+ token = body.token;
92
+ schedule();
93
+ }
94
+ if (initial) schedule();
95
+ return {
96
+ current: () => token,
97
+ isInitial: (candidate) => candidate === initial,
98
+ stop: () => {
99
+ stopped = true;
100
+ if (timer) clearTimeout(timer);
101
+ timer = null;
102
+ }
103
+ };
104
+ }
105
+
106
+ // ../../packages/mcp-server/src/instance-selector.ts
107
+ function instanceUrl(i) {
108
+ return `http://localhost:${i.port}`;
109
+ }
110
+ function describe(i) {
111
+ return `pid ${i.pid} \u2014 port ${i.port} \u2014 ${i.cwd}${i.version ? ` (v${i.version})` : ""}`;
112
+ }
113
+ function createInstanceSelector(listInstances) {
114
+ return {
115
+ async instance(pid) {
116
+ const instances = await listInstances();
117
+ if (pid !== void 0) {
118
+ const named = instances.find((i) => i.pid === pid);
119
+ if (named) return named;
120
+ throw new Error(`Aucune instance Runeya lanc\xE9e avec le pid ${pid}. Instances en cours :
121
+ ${instances.map((i) => `- ${describe(i)}`).join("\n") || "(aucune)"}`);
122
+ }
123
+ if (instances.length === 0) {
124
+ throw new Error("Runeya n'est pas lanc\xE9 sur cette machine : aucune instance \xE0 contacter.");
125
+ }
126
+ if (instances.length === 1) return instances[0];
127
+ throw new Error(
128
+ `Plusieurs instances Runeya sont lanc\xE9es. Demande \xE0 l'utilisateur laquelle utiliser, puis passe son pid dans le param\xE8tre runeyaInstance, \xE0 chaque appel de cette conversation :
129
+ ${instances.map((i) => `- ${describe(i)}`).join("\n")}`
130
+ );
131
+ },
132
+ list: listInstances
133
+ };
134
+ }
135
+
136
+ // ../../packages/mcp-server/src/pairing-client.ts
137
+ import { createHash } from "crypto";
138
+ import { mkdir, readFile, rm, writeFile, chmod } from "fs/promises";
139
+ import { join } from "path";
140
+ function clientDisplayName(info) {
141
+ const title = info.title?.trim();
142
+ if (title) return title.slice(0, 100);
143
+ const words = info.name.split(/[-_\s]+/).filter(Boolean);
144
+ const pretty = words.map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
145
+ return pretty.slice(0, 100) || "Client MCP";
146
+ }
147
+ var EXPIRY_MARGIN_S = 30;
148
+ var PairingClient = class {
149
+ constructor(dir, clientInfo, cwd, fetchImpl = fetch) {
150
+ this.dir = dir;
151
+ this.clientInfo = clientInfo;
152
+ this.cwd = cwd;
153
+ this.fetchImpl = fetchImpl;
154
+ }
155
+ tokens = /* @__PURE__ */ new Map();
156
+ pending = /* @__PURE__ */ new Map();
157
+ /** Le nom technique du client : il distingue les secrets, il ne s'affiche pas. */
158
+ name() {
159
+ return this.clientInfo().name.slice(0, 100) || "Client MCP";
160
+ }
161
+ /**
162
+ * Un secret par instance ET par client : accepter Claude Code ne donne pas
163
+ * accès à Cursor, et chaque outil se révoque à part.
164
+ */
165
+ secretFile(key) {
166
+ const id = createHash("sha256").update(`${key}\0${this.name()}`).digest("hex").slice(0, 32);
167
+ return join(this.dir, `${id}.json`);
168
+ }
169
+ async readSecret(key) {
170
+ try {
171
+ const { secret } = JSON.parse(await readFile(this.secretFile(key), "utf8"));
172
+ return typeof secret === "string" ? secret : null;
173
+ } catch {
174
+ return null;
175
+ }
176
+ }
177
+ async writeSecret(key, secret) {
178
+ await mkdir(this.dir, { recursive: true, mode: 448 });
179
+ const file = this.secretFile(key);
180
+ await writeFile(file, JSON.stringify({ secret }), { mode: 384 });
181
+ await chmod(file, 384);
182
+ }
183
+ async call(target, route, method, input) {
184
+ try {
185
+ return await callTrpc(target.baseUrl, route, method, input, "", this.fetchImpl);
186
+ } catch (err) {
187
+ if (/NOT_FOUND/.test(err.message)) {
188
+ throw new Error(`L'instance Runeya de ${target.baseUrl} est trop ancienne pour accepter ce client : mettez Runeya \xE0 jour, ou passez une cl\xE9 d'API dans RUNEYA_API_TOKEN.`);
189
+ }
190
+ throw err;
191
+ }
192
+ }
193
+ /**
194
+ * Oublie le jeton en mémoire. Le serveur l'a refusé, typiquement après un
195
+ * redémarrage (la clé de signature change) : le prochain `token()` le
196
+ * réobtient avec le secret, sans nouvelle demande d'accès.
197
+ */
198
+ forget(target) {
199
+ this.tokens.delete(target.key);
200
+ }
201
+ /** Un jeton pour cette instance, ou une erreur qui dit à l'utilisateur quoi faire. */
202
+ async token(target) {
203
+ const cached = this.tokens.get(target.key);
204
+ const exp = cached ? jwtExpiresAt(cached) : null;
205
+ if (cached && exp !== null && exp - EXPIRY_MARGIN_S > Date.now() / 1e3) return cached;
206
+ this.tokens.delete(target.key);
207
+ const secret = await this.readSecret(target.key);
208
+ if (secret) {
209
+ try {
210
+ const { token } = await this.call(target, "mcpPairing.exchange", "POST", { secret });
211
+ this.tokens.set(target.key, token);
212
+ return token;
213
+ } catch (err) {
214
+ if (!/UNAUTHORIZED/.test(err.message)) throw err;
215
+ await rm(this.secretFile(target.key), { force: true });
216
+ }
217
+ }
218
+ return this.pair(target);
219
+ }
220
+ async pair(target) {
221
+ const pending = this.pending.get(target.key);
222
+ if (pending) {
223
+ const { status, secret } = await this.call(target, "mcpPairing.status", "GET", { requestId: pending.requestId });
224
+ if (status === "approved" && secret) {
225
+ this.pending.delete(target.key);
226
+ await this.writeSecret(target.key, secret);
227
+ return this.token(target);
228
+ }
229
+ if (status === "pending") {
230
+ throw new Error(`Acc\xE8s en attente : l'utilisateur doit accepter la demande dans Runeya (code ${pending.code}), puis relancer l'action.`);
231
+ }
232
+ this.pending.delete(target.key);
233
+ if (status === "denied") throw new Error("L'utilisateur a refus\xE9 l'acc\xE8s dans Runeya.");
234
+ }
235
+ const { id, code } = await this.call(target, "mcpPairing.request", "POST", { name: clientDisplayName(this.clientInfo()), cwd: this.cwd });
236
+ this.pending.set(target.key, { requestId: id, code });
237
+ throw new Error(`Runeya doit autoriser ce client. Une demande d'acc\xE8s vient de s'afficher dans Runeya avec le code ${code} : demande \xE0 l'utilisateur de v\xE9rifier le code et d'accepter, puis relance l'action.`);
238
+ }
239
+ };
240
+
241
+ // ../../packages/mcp-server/src/server.ts
242
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
243
+ import { z } from "zod";
244
+ function needsAnchor(cap, params) {
245
+ const accepts = (name) => cap.params.some((p) => p.name === name);
246
+ if (params["orgId"] || params["organizationId"]) return false;
247
+ if (accepts("projectId")) return !params["projectId"];
248
+ return ["orgId", "organizationId"].some((name) => accepts(name) && !params[name]);
249
+ }
250
+ var RETRY = /* @__PURE__ */ Symbol("retry");
251
+ function text(value) {
252
+ return { content: [{ type: "text", text: typeof value === "string" ? value : JSON.stringify(value, null, 2) }] };
253
+ }
254
+ function error(err) {
255
+ return { content: [{ type: "text", text: `Error: ${err.message}` }], isError: true };
256
+ }
257
+ function createRuneyaMcpServer(opts) {
258
+ const server = new McpServer({ name: "runeya", version: "1.0.0" });
259
+ async function callCapability(cap, input) {
260
+ const rawParams = toRawParams(input, cap);
261
+ const raw = rawParams["authToken"];
262
+ const explicit = raw && !raw.startsWith("$") && !opts.sessionToken.isInitial(raw) ? raw : null;
263
+ const call = async () => {
264
+ const target = await opts.target(input);
265
+ const authToken = explicit ?? (target.authToken || opts.sessionToken.current() || opts.internalToken || void 0);
266
+ if (!authToken) throw new Error("No auth token available");
267
+ const anchor = target.projectAnchor && needsAnchor(cap, rawParams) ? await target.projectAnchor() : opts.projectAnchor;
268
+ const params = applyProjectAnchor(cap, rawParams, anchor);
269
+ try {
270
+ return await runCapability(cap, target.baseUrl, params, authToken);
271
+ } catch (err) {
272
+ if (!explicit && target.forgetToken && /UNAUTHORIZED/.test(err.message)) {
273
+ target.forgetToken();
274
+ return RETRY;
275
+ }
276
+ throw err;
277
+ }
278
+ };
279
+ let result = await call();
280
+ if (result === RETRY) result = await call();
281
+ if (result === RETRY) throw new Error("Runeya refuse le jeton de ce client, m\xEAme renouvel\xE9.");
282
+ return result;
283
+ }
284
+ for (const cap of RUNEYA_CAPABILITIES) {
285
+ server.tool(cap.name, cap.description, { ...buildMcpShape(cap), ...opts.extraShape }, async (input) => {
286
+ try {
287
+ return text(await callCapability(cap, input));
288
+ } catch (err) {
289
+ return error(err);
290
+ }
291
+ });
292
+ }
293
+ registerWorkOnCardPrompt(server, (input) => callCapability(CARD_CONTEXT, input), opts.extraShape !== void 0);
294
+ return server;
295
+ }
296
+ var CARD_CONTEXT = RUNEYA_CAPABILITIES.find((c) => c.name === "kanban_get_card_context");
297
+ function registerWorkOnCardPrompt(server, cardContext, withInstance) {
298
+ const args = {
299
+ boardId: z.string().max(128).describe("The kanban board ID"),
300
+ cardId: z.string().max(128).describe("The card ID"),
301
+ ...withInstance ? { runeyaInstance: z.string().max(16).optional().describe("pid of the Runeya instance, when several are running") } : {}
302
+ };
303
+ server.prompt(
304
+ "work_on_card",
305
+ "Work on a Runeya kanban card: loads the card, the board guidelines and the expected ending.",
306
+ args,
307
+ async (input) => {
308
+ const { boardId, cardId, runeyaInstance } = input;
309
+ const pid = runeyaInstance ? Number(runeyaInstance) : void 0;
310
+ const { context } = await cardContext({ boardId, cardId, ...pid ? { runeyaInstance: pid } : {} });
311
+ return {
312
+ description: `Carte ${cardId}`,
313
+ messages: [{
314
+ role: "user",
315
+ content: {
316
+ type: "text",
317
+ text: `Je veux travailler sur cette carte du kanban Runeya. Voici son contexte, tel que Runeya le donne \xE0 une session lanc\xE9e depuis la carte : suis ces consignes, en particulier la fin attendue (d\xE9placer la carte si besoin, puis la commenter).
318
+
319
+ ${context}`
320
+ }
321
+ }]
322
+ };
323
+ }
324
+ );
325
+ }
326
+ function registerInstanceTools(server, selector) {
327
+ server.tool(
328
+ "list_local_runeya_instances",
329
+ "List the Runeya instances running on this machine, with their pid. When several are running, ask the user which one to use, then pass its pid as runeyaInstance on every call of this conversation.",
330
+ {},
331
+ async () => {
332
+ try {
333
+ return text(await selector.list());
334
+ } catch (err) {
335
+ return error(err);
336
+ }
337
+ }
338
+ );
339
+ }
340
+
341
+ // ../../packages/mcp-server/src/external.ts
342
+ async function runExternalMcp(opts) {
343
+ const fixedUrl = process.env["RUNEYA_BASE_URL"];
344
+ const selector = createInstanceSelector(async () => (await opts.listInstances()).map(({ pid, port, cwd, dataDir, version }) => ({ pid, port, cwd, dataDir, version })));
345
+ const sessionToken = startSessionToken(process.env["RUNEYA_API_TOKEN"] ?? "", fixedUrl ?? "");
346
+ const pairing = new PairingClient(
347
+ // Même règle que `machineRoot()` du serveur : `RUNEYA_HOME` déplace la racine machine.
348
+ join2(opts.machineRoot ?? join2(process.env["RUNEYA_HOME"] || homedir(), ".runeya"), "mcp"),
349
+ () => server.server.getClientVersion() ?? { name: "" },
350
+ process.cwd()
351
+ );
352
+ const envAnchor = {
353
+ projectId: process.env["RUNEYA_PROJECT_ID"] || void 0,
354
+ orgId: process.env["RUNEYA_ORG_ID"] || void 0
355
+ };
356
+ const anchorFor = createProjectAnchors(envAnchor, process.cwd(), (baseUrl, authToken, path) => callTrpc(baseUrl, "project.containing", "GET", { path }, authToken));
357
+ const server = createRuneyaMcpServer({
358
+ target: async (input) => {
359
+ const pid = typeof input["runeyaInstance"] === "number" ? input["runeyaInstance"] : void 0;
360
+ const { baseUrl, key } = fixedUrl ? { baseUrl: fixedUrl, key: fixedUrl } : await selector.instance(pid).then((i) => ({ baseUrl: instanceUrl(i), key: i.dataDir }));
361
+ if (sessionToken.current()) {
362
+ const authToken2 = sessionToken.current();
363
+ return { baseUrl, authToken: authToken2, projectAnchor: () => anchorFor(key, baseUrl, authToken2) };
364
+ }
365
+ const authToken = await pairing.token({ baseUrl, key });
366
+ return {
367
+ baseUrl,
368
+ authToken,
369
+ projectAnchor: () => anchorFor(key, baseUrl, authToken),
370
+ forgetToken: () => pairing.forget({ baseUrl, key })
371
+ };
372
+ },
373
+ sessionToken,
374
+ projectAnchor: envAnchor,
375
+ extraShape: fixedUrl ? void 0 : {
376
+ runeyaInstance: z2.number().int().positive().optional().describe(
377
+ "The pid of the Runeya instance to call, from list_local_runeya_instances. Needed only when several instances are running: then pass it on every call of this conversation."
378
+ )
379
+ }
380
+ });
381
+ if (!fixedUrl) registerInstanceTools(server, selector);
382
+ await server.connect(new StdioServerTransport());
383
+ }
384
+ export {
385
+ runExternalMcp
386
+ };
387
+ //# sourceMappingURL=external-NMKQPROK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../packages/mcp-server/src/external.ts","../../../packages/mcp-server/src/project-anchors.ts","../../../packages/mcp-server/src/session-token.ts","../../../packages/mcp-server/src/instance-selector.ts","../../../packages/mcp-server/src/pairing-client.ts","../../../packages/mcp-server/src/server.ts"],"sourcesContent":["import { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { callTrpc } from '@runeya/packages-ai-capabilities';\nimport { z } from 'zod';\nimport { createProjectAnchors } from './project-anchors.js';\nimport { startSessionToken } from './session-token.js';\nimport { createInstanceSelector, instanceUrl, type McpInstance } from './instance-selector.js';\nimport { PairingClient } from './pairing-client.js';\nimport { createRuneyaMcpServer, registerInstanceTools } from './server.js';\n\nexport interface ExternalMcpOptions {\n listInstances: () => Promise<McpInstance[]>;\n /** La racine machine de Runeya (`~/.runeya`) : le secret d'appairage y est rangé. */\n machineRoot?: string;\n}\n\n/**\n * Le MCP lancé par un client externe (`runeya mcp`) : Claude Code, Codex,\n * Cursor… configuré une fois, il suit les mises à jour du CLI.\n *\n * L'instance à appeler vient du registre machine (voir `instance-selector.ts`),\n * sauf si `RUNEYA_BASE_URL` la fixe. Le jeton vient de l'appairage avec cette\n * instance (voir `pairing-client.ts`), sauf si `RUNEYA_API_TOKEN` en donne un.\n */\nexport async function runExternalMcp(opts: ExternalMcpOptions): Promise<void> {\n const fixedUrl = process.env['RUNEYA_BASE_URL'];\n // Le registre porte aussi de quoi relancer une instance (argv, environnement) :\n // rien de cela n'a à entrer dans le contexte du modèle.\n const selector = createInstanceSelector(async () =>\n (await opts.listInstances()).map(({ pid, port, cwd, dataDir, version }) => ({ pid, port, cwd, dataDir, version })));\n // Une clé d'API n'est pas un JWT : `startSessionToken` ne la renouvelle pas,\n // et l'URL de renouvellement ne sert donc jamais.\n const sessionToken = startSessionToken(process.env['RUNEYA_API_TOKEN'] ?? '', fixedUrl ?? '');\n // `server` est déclaré plus bas : le nom n'est lu qu'au premier appel d'outil,\n // une fois la connexion MCP établie.\n const pairing = new PairingClient(\n // Même règle que `machineRoot()` du serveur : `RUNEYA_HOME` déplace la racine machine.\n join(opts.machineRoot ?? join(process.env['RUNEYA_HOME'] || homedir(), '.runeya'), 'mcp'),\n () => server.server.getClientVersion() ?? { name: '' },\n process.cwd(),\n );\n\n const envAnchor = {\n projectId: process.env['RUNEYA_PROJECT_ID'] || undefined,\n orgId: process.env['RUNEYA_ORG_ID'] || undefined,\n };\n const anchorFor = createProjectAnchors(envAnchor, process.cwd(), (baseUrl, authToken, path) =>\n callTrpc(baseUrl, 'project.containing', 'GET', { path }, authToken));\n\n const server = createRuneyaMcpServer({\n target: async (input) => {\n // Une URL fixée n'a pas de dossier de données connu : l'URL sert alors de clé\n // au secret d'appairage.\n const pid = typeof input['runeyaInstance'] === 'number' ? input['runeyaInstance'] : undefined;\n const { baseUrl, key } = fixedUrl\n ? { baseUrl: fixedUrl, key: fixedUrl }\n : await selector.instance(pid).then((i) => ({ baseUrl: instanceUrl(i), key: i.dataDir }));\n // Un jeton donné explicitement passe devant l'appairage.\n if (sessionToken.current()) {\n const authToken = sessionToken.current();\n return { baseUrl, authToken, projectAnchor: () => anchorFor(key, baseUrl, authToken) };\n }\n const authToken = await pairing.token({ baseUrl, key });\n return {\n baseUrl,\n authToken,\n projectAnchor: () => anchorFor(key, baseUrl, authToken),\n forgetToken: () => pairing.forget({ baseUrl, key }),\n };\n },\n sessionToken,\n projectAnchor: envAnchor,\n extraShape: fixedUrl ? undefined : {\n runeyaInstance: z.number().int().positive().optional().describe(\n 'The pid of the Runeya instance to call, from list_local_runeya_instances. Needed only when several instances are running: then pass it on every call of this conversation.',\n ),\n },\n });\n if (!fixedUrl) registerInstanceTools(server, selector);\n\n await server.connect(new StdioServerTransport());\n}\n","export interface ProjectAnchor {\n projectId?: string | undefined;\n orgId?: string | undefined;\n}\n\ntype Containing = (baseUrl: string, authToken: string, path: string) => Promise<unknown>;\n\n/**\n * Le projet d'ancrage d'un MCP lancé hors de Runeya : celui que nomme\n * `RUNEYA_PROJECT_ID`, sinon celui dont le dossier contient le dossier où\n * l'outil IA a été lancé (`project.containing`). Sans lui, `list_services` rend\n * tous les projets de la machine.\n *\n * Demandé une fois par instance (`key`). Hors de tout projet, rien n'est\n * ancré : l'outil a été lancé ailleurs, toute la machine est voulue. Un échec\n * de la demande, lui, fait échouer l'appel avec une consigne : se rabattre sur\n * toute la machine sans le dire referait le défaut que l'ancrage corrige. La\n * demande est refaite au prochain appel.\n */\nexport function createProjectAnchors(env: ProjectAnchor, cwd: string, containing: Containing) {\n const anchors = new Map<string, Promise<ProjectAnchor>>();\n return (key: string, baseUrl: string, authToken: string): Promise<ProjectAnchor> => {\n if (env.projectId) return Promise.resolve(env);\n let anchor = anchors.get(key);\n if (!anchor) {\n anchor = containing(baseUrl, authToken, cwd)\n .then((found) => {\n const p = found as { projectId: string; orgId: string | null } | null;\n return p ? { projectId: p.projectId, ...(p.orgId ? { orgId: p.orgId } : {}) } : {};\n })\n .catch((err: unknown) => {\n anchors.delete(key);\n throw new Error(`Impossible de savoir à quel projet Runeya appartient le dossier ${cwd} (${(err as Error).message}). Nomme le projet : appelle list_projects, puis passe son projectId.`);\n });\n anchors.set(key, anchor);\n }\n return anchor;\n };\n}\n","/**\n * Le jeton que le MCP présente au serveur Runeya, renouvelé avant qu'il expire.\n *\n * Le CLI reçoit un JWT à son lancement et garde le même processus MCP pendant\n * toute la conversation : sans renouvellement, les tools répondent 401 une fois\n * la durée du jeton écoulée (8 h par défaut). Même mécanisme que l'onglet :\n * `POST /api/auth/refresh` à la moitié de la durée restante. Un jeton déjà\n * expiré, par exemple après une veille plus longue que sa durée, n'est plus\n * renouvelable.\n */\n\nconst RETRY_DELAY_MS = 1000;\n\nexport interface SessionToken {\n /** Le jeton à envoyer maintenant. */\n current(): string;\n /** Vrai pour le jeton reçu au lancement, que le modèle peut recopier tel quel. */\n isInitial(token: string): boolean;\n stop(): void;\n}\n\n/** Expiry of a JWT, in Unix seconds. `null` when the token cannot be read. */\nexport function jwtExpiresAt(token: string): number | null {\n try {\n const payload = JSON.parse(Buffer.from(token.split('.')[1] ?? '', 'base64url').toString('utf8')) as { exp?: unknown };\n return typeof payload.exp === 'number' ? payload.exp : null;\n } catch {\n return null;\n }\n}\n\nexport function startSessionToken(initial: string, baseUrl: string, fetchImpl: typeof fetch = fetch): SessionToken {\n let token = initial;\n let timer: ReturnType<typeof setTimeout> | null = null;\n let stopped = false;\n\n const arm = (delayMs: number) => {\n if (stopped) return;\n timer = setTimeout(() => { void refresh(); }, delayMs);\n // Le minuteur ne doit pas retenir le processus quand le CLI ferme stdin.\n timer.unref?.();\n };\n\n const schedule = () => {\n const exp = jwtExpiresAt(token);\n if (exp === null) return;\n const remainingMs = exp * 1000 - Date.now();\n if (remainingMs > 0) arm(remainingMs / 2);\n };\n\n const retry = () => {\n const exp = jwtExpiresAt(token);\n if (exp !== null && exp * 1000 - Date.now() > RETRY_DELAY_MS) arm(RETRY_DELAY_MS);\n };\n\n async function refresh(): Promise<void> {\n timer = null;\n let res: Response;\n try {\n res = await fetchImpl(`${baseUrl}/api/auth/refresh`, {\n method: 'POST',\n headers: { Authorization: `Bearer ${token}` },\n signal: AbortSignal.timeout(5000),\n });\n } catch {\n retry();\n return;\n }\n if (res.status === 401) return;\n if (!res.ok) { retry(); return; }\n const body = await res.json().catch(() => null) as { token?: unknown } | null;\n if (typeof body?.token !== 'string') { retry(); return; }\n token = body.token;\n schedule();\n }\n\n if (initial) schedule();\n\n return {\n current: () => token,\n isInitial: (candidate) => candidate === initial,\n stop: () => {\n stopped = true;\n if (timer) clearTimeout(timer);\n timer = null;\n },\n };\n}\n","/**\n * Quelle instance Runeya le MCP appelle, quand il n'est pas lancé par Runeya.\n *\n * Lancé par Runeya, le MCP reçoit `RUNEYA_BASE_URL` et n'a rien à choisir.\n * Lancé par un client externe (`runeya mcp`), il lit le registre machine :\n * - aucune instance : rien à appeler, l'outil échoue en le disant ;\n * - une seule : elle est prise d'office ;\n * - plusieurs : l'appel doit nommer l'instance par son pid\n * (`runeyaInstance`), que l'utilisateur choisit dans la conversation.\n *\n * Le choix n'est pas gardé ici. Un client comme Cursor ou Claude Desktop garde\n * un seul process MCP pour toutes ses conversations : un choix mémorisé dans\n * l'une s'appliquerait en silence aux autres. Le modèle le porte donc dans\n * chaque appel, et chaque conversation a le sien.\n *\n * Le pid, pas le port : une instance qui s'arrête ne fait pas atterrir les\n * appels sur celle qui reprendrait son port. La liste est relue à chaque appel.\n */\n\n/** Ce dont le sélecteur a besoin d'une instance du registre. */\nexport interface McpInstance {\n pid: number;\n port: number;\n cwd: string;\n /** Le dossier de données de l'instance : stable d'un redémarrage à l'autre. */\n dataDir: string;\n version?: string | undefined;\n}\n\nexport interface InstanceSelector {\n /** L'instance à appeler (`pid` si l'appel en nomme une), ou une erreur qui dit quoi faire. */\n instance(pid?: number): Promise<McpInstance>;\n /** Les instances vivantes. */\n list(): Promise<McpInstance[]>;\n}\n\nexport function instanceUrl(i: McpInstance): string {\n return `http://localhost:${i.port}`;\n}\n\nfunction describe(i: McpInstance): string {\n return `pid ${i.pid} — port ${i.port} — ${i.cwd}${i.version ? ` (v${i.version})` : ''}`;\n}\n\nexport function createInstanceSelector(listInstances: () => Promise<McpInstance[]>): InstanceSelector {\n return {\n async instance(pid) {\n const instances = await listInstances();\n if (pid !== undefined) {\n const named = instances.find((i) => i.pid === pid);\n if (named) return named;\n throw new Error(`Aucune instance Runeya lancée avec le pid ${pid}. Instances en cours :\\n${instances.map((i) => `- ${describe(i)}`).join('\\n') || '(aucune)'}`);\n }\n if (instances.length === 0) {\n throw new Error(\"Runeya n'est pas lancé sur cette machine : aucune instance à contacter.\");\n }\n if (instances.length === 1) return instances[0]!;\n throw new Error(\n `Plusieurs instances Runeya sont lancées. Demande à l'utilisateur laquelle utiliser, puis passe son pid dans le paramètre runeyaInstance, à chaque appel de cette conversation :\\n${instances.map((i) => `- ${describe(i)}`).join('\\n')}`,\n );\n },\n\n list: listInstances,\n };\n}\n","import { createHash } from 'node:crypto';\nimport { mkdir, readFile, rm, writeFile, chmod } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { callTrpc } from '@runeya/packages-ai-capabilities';\nimport { jwtExpiresAt } from './session-token.js';\n\n/**\n * Le côté client de l'appairage (voir `apps/server/src/services/mcp-pairing.ts`).\n *\n * Aucun secret n'est écrit dans la config de Claude Code, Cursor… : le secret\n * d'appairage vit dans `~/.runeya/mcp/`, lisible par l'utilisateur seul, un\n * fichier par dossier de données d'instance et par client. Il survit donc aux redémarrages et\n * aux changements de port de l'instance.\n *\n * Un appel d'outil ne reste jamais bloqué en attendant l'accord : il échoue en\n * disant quoi faire, et l'appel suivant reprend où en est la demande.\n */\n\nexport interface ClientInfo {\n name: string;\n title?: string | undefined;\n}\n\n/**\n * Le nom montré à l'utilisateur dans la demande d'accès et la liste des accès :\n * le titre que le client annonce s'il en donne un, sinon son nom technique mis\n * en forme (`claude-code` → « Claude Code »).\n */\nexport function clientDisplayName(info: ClientInfo): string {\n const title = info.title?.trim();\n if (title) return title.slice(0, 100);\n const words = info.name.split(/[-_\\s]+/).filter(Boolean);\n const pretty = words.map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');\n return pretty.slice(0, 100) || 'Client MCP';\n}\n\nexport interface PairingTarget {\n baseUrl: string;\n /** Ce qui identifie l'instance d'un redémarrage à l'autre : son dossier de données. */\n key: string;\n}\n\ninterface PendingRequest {\n requestId: string;\n code: string;\n}\n\n/** Une marge pour ne pas envoyer un jeton qui expire en route. */\nconst EXPIRY_MARGIN_S = 30;\n\nexport class PairingClient {\n private tokens = new Map<string, string>();\n private pending = new Map<string, PendingRequest>();\n\n constructor(\n private readonly dir: string,\n /** Ce que le client annonce à la connexion MCP (`clientInfo`). */\n private readonly clientInfo: () => ClientInfo,\n private readonly cwd: string,\n private readonly fetchImpl: typeof fetch = fetch,\n ) {}\n\n /** Le nom technique du client : il distingue les secrets, il ne s'affiche pas. */\n private name(): string {\n return this.clientInfo().name.slice(0, 100) || 'Client MCP';\n }\n\n /**\n * Un secret par instance ET par client : accepter Claude Code ne donne pas\n * accès à Cursor, et chaque outil se révoque à part.\n */\n private secretFile(key: string): string {\n const id = createHash('sha256').update(`${key}\\0${this.name()}`).digest('hex').slice(0, 32);\n return join(this.dir, `${id}.json`);\n }\n\n private async readSecret(key: string): Promise<string | null> {\n try {\n const { secret } = JSON.parse(await readFile(this.secretFile(key), 'utf8')) as { secret?: unknown };\n return typeof secret === 'string' ? secret : null;\n } catch {\n return null;\n }\n }\n\n private async writeSecret(key: string, secret: string): Promise<void> {\n await mkdir(this.dir, { recursive: true, mode: 0o700 });\n const file = this.secretFile(key);\n await writeFile(file, JSON.stringify({ secret }), { mode: 0o600 });\n // `mode` ne s'applique qu'à la création : un fichier existant garde les siens.\n await chmod(file, 0o600);\n }\n\n private async call(target: PairingTarget, route: string, method: 'GET' | 'POST', input: Record<string, unknown>): Promise<unknown> {\n try {\n return await callTrpc(target.baseUrl, route, method, input, '', this.fetchImpl);\n } catch (err) {\n // Une instance antérieure à l'appairage ne connaît pas ces routes.\n if (/NOT_FOUND/.test((err as Error).message)) {\n throw new Error(`L'instance Runeya de ${target.baseUrl} est trop ancienne pour accepter ce client : mettez Runeya à jour, ou passez une clé d'API dans RUNEYA_API_TOKEN.`);\n }\n throw err;\n }\n }\n\n /**\n * Oublie le jeton en mémoire. Le serveur l'a refusé, typiquement après un\n * redémarrage (la clé de signature change) : le prochain `token()` le\n * réobtient avec le secret, sans nouvelle demande d'accès.\n */\n forget(target: PairingTarget): void {\n this.tokens.delete(target.key);\n }\n\n /** Un jeton pour cette instance, ou une erreur qui dit à l'utilisateur quoi faire. */\n async token(target: PairingTarget): Promise<string> {\n const cached = this.tokens.get(target.key);\n const exp = cached ? jwtExpiresAt(cached) : null;\n if (cached && exp !== null && exp - EXPIRY_MARGIN_S > Date.now() / 1000) return cached;\n this.tokens.delete(target.key);\n\n const secret = await this.readSecret(target.key);\n if (secret) {\n try {\n const { token } = await this.call(target, 'mcpPairing.exchange', 'POST', { secret }) as { token: string };\n this.tokens.set(target.key, token);\n return token;\n } catch (err) {\n if (!/UNAUTHORIZED/.test((err as Error).message)) throw err;\n // Appairage révoqué dans Runeya : le secret ne servira plus.\n await rm(this.secretFile(target.key), { force: true });\n }\n }\n\n return this.pair(target);\n }\n\n private async pair(target: PairingTarget): Promise<string> {\n const pending = this.pending.get(target.key);\n if (pending) {\n const { status, secret } = await this.call(target, 'mcpPairing.status', 'GET', { requestId: pending.requestId }) as { status: string; secret?: string };\n if (status === 'approved' && secret) {\n this.pending.delete(target.key);\n await this.writeSecret(target.key, secret);\n return this.token(target);\n }\n if (status === 'pending') {\n throw new Error(`Accès en attente : l'utilisateur doit accepter la demande dans Runeya (code ${pending.code}), puis relancer l'action.`);\n }\n this.pending.delete(target.key);\n if (status === 'denied') throw new Error('L\\'utilisateur a refusé l\\'accès dans Runeya.');\n // Expirée : on repart d'une nouvelle demande.\n }\n\n const { id, code } = await this.call(target, 'mcpPairing.request', 'POST', { name: clientDisplayName(this.clientInfo()), cwd: this.cwd }) as { id: string; code: string };\n this.pending.set(target.key, { requestId: id, code });\n throw new Error(`Runeya doit autoriser ce client. Une demande d'accès vient de s'afficher dans Runeya avec le code ${code} : demande à l'utilisateur de vérifier le code et d'accepter, puis relance l'action.`);\n }\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\nimport type { SessionToken } from './session-token.js';\nimport type { InstanceSelector } from './instance-selector.js';\nimport type { ProjectAnchor } from './project-anchors.js';\nimport { RUNEYA_CAPABILITIES, applyProjectAnchor, buildMcpShape, runCapability, toRawParams, type RuneyaCapability } from '@runeya/packages-ai-capabilities';\n\nexport interface RuneyaMcpOptions {\n /**\n * Le serveur Runeya à appeler, relu à chaque appel d'outil, et le jeton qui\n * lui est propre s'il en a un. Les deux viennent d'un même appel, pour viser\n * toujours la même instance.\n */\n target: (input: Record<string, unknown>) => Promise<{\n baseUrl: string;\n authToken?: string | undefined;\n /**\n * Le projet d'ancrage de cette instance. Une fonction : il n'est demandé que\n * pour un outil qui en a besoin, pour qu'un échec n'empêche pas d'appeler\n * `list_projects` et de nommer le projet à la main.\n */\n projectAnchor?: (() => Promise<ProjectAnchor>) | undefined;\n /** Le jeton a été refusé : l'oublier pour que le prochain `target()` en obtienne un neuf. */\n forgetToken?: (() => void) | undefined;\n }>;\n sessionToken: SessionToken;\n internalToken?: string;\n /** Le projet par défaut, quand `target` n'en donne pas. */\n projectAnchor: ProjectAnchor;\n /**\n * Paramètres ajoutés à chaque outil, lus par `target` et jamais transmis à la\n * route (le choix d'instance du MCP externe, `runeyaInstance`).\n */\n extraShape?: Record<string, z.ZodType> | undefined;\n}\n\n/**\n * L'outil accepte un projet ou une organisation et l'appel n'en donne pas : il\n * faut l'ancre. Les autres (`list_projects`, `wait`…) s'en passent.\n */\nexport function needsAnchor(cap: Pick<RuneyaCapability, 'params'>, params: Record<string, string | undefined>): boolean {\n const accepts = (name: string) => cap.params.some((p) => p.name === name);\n // Une portée nommée par l'appel (projet ou organisation) se passe d'ancre :\n // y ajouter le projet courant le mêlerait à une autre organisation.\n if (params['orgId'] || params['organizationId']) return false;\n if (accepts('projectId')) return !params['projectId'];\n return ['orgId', 'organizationId'].some((name) => accepts(name) && !params[name]);\n}\n\nconst RETRY = Symbol('retry');\n\ntype ToolResult = { content: { type: 'text'; text: string }[]; isError?: boolean };\n\nfunction text(value: unknown): ToolResult {\n return { content: [{ type: 'text', text: typeof value === 'string' ? value : JSON.stringify(value, null, 2) }] };\n}\n\nfunction error(err: unknown): ToolResult {\n return { content: [{ type: 'text', text: `Error: ${(err as Error).message}` }], isError: true };\n}\n\n/** Le serveur MCP Runeya : un outil par capacité de `RUNEYA_CAPABILITIES`, et le prompt `work_on_card`. */\nexport function createRuneyaMcpServer(opts: RuneyaMcpOptions): McpServer {\n const server = new McpServer({ name: 'runeya', version: '1.0.0' });\n\n /**\n * Appeler une capacité : instance, jeton, ancrage, et une relance si le jeton\n * obtenu par `target` est refusé (instance redémarrée). Le seul chemin des\n * outils comme du prompt.\n */\n async function callCapability(cap: RuneyaCapability, input: Record<string, unknown>): Promise<unknown> {\n const rawParams = toRawParams(input, cap);\n const raw = rawParams['authToken'];\n // Le jeton du lancement, même recopié en clair par le modèle, cède la\n // place au jeton renouvelé.\n const explicit = raw && !raw.startsWith('$') && !opts.sessionToken.isInitial(raw) ? raw : null;\n const call = async () => {\n const target = await opts.target(input);\n const authToken = explicit ?? (target.authToken || opts.sessionToken.current() || opts.internalToken || undefined);\n if (!authToken) throw new Error('No auth token available');\n\n const anchor = target.projectAnchor && needsAnchor(cap, rawParams) ? await target.projectAnchor() : opts.projectAnchor;\n const params = applyProjectAnchor(cap, rawParams, anchor);\n try {\n return await runCapability(cap, target.baseUrl, params, authToken);\n } catch (err) {\n // Un jeton donné par l'appel ne se remplace pas.\n if (!explicit && target.forgetToken && /UNAUTHORIZED/.test((err as Error).message)) {\n target.forgetToken();\n return RETRY;\n }\n throw err;\n }\n };\n let result = await call();\n if (result === RETRY) result = await call();\n if (result === RETRY) throw new Error('Runeya refuse le jeton de ce client, même renouvelé.');\n return result;\n }\n\n for (const cap of RUNEYA_CAPABILITIES) {\n server.tool(cap.name, cap.description, { ...buildMcpShape(cap), ...opts.extraShape }, async (input) => {\n try {\n return text(await callCapability(cap, input as Record<string, unknown>));\n } catch (err) {\n return error(err);\n }\n });\n }\n\n registerWorkOnCardPrompt(server, (input) => callCapability(CARD_CONTEXT, input), opts.extraShape !== undefined);\n\n return server;\n}\n\nconst CARD_CONTEXT = RUNEYA_CAPABILITIES.find((c) => c.name === 'kanban_get_card_context')!;\n\n/**\n * Le prompt `work_on_card` : les clients l'affichent comme une commande\n * (`/mcp__runeya__work_on_card` dans Claude Code, `/mcp.runeya.work_on_card`\n * dans VS Code).\n *\n * Il rend le contexte de la carte comme un message de l'utilisateur : un prompt\n * MCP ne peut pas poser de consigne système. Les consignes du tableau guident\n * donc le travail sans la force qu'elles ont dans une session lancée depuis la\n * carte dans Runeya, et le message le dit.\n */\nfunction registerWorkOnCardPrompt(\n server: McpServer,\n cardContext: (input: Record<string, unknown>) => Promise<unknown>,\n withInstance: boolean,\n): void {\n const args = {\n boardId: z.string().max(128).describe('The kanban board ID'),\n cardId: z.string().max(128).describe('The card ID'),\n ...(withInstance ? { runeyaInstance: z.string().max(16).optional().describe('pid of the Runeya instance, when several are running') } : {}),\n };\n server.prompt(\n 'work_on_card',\n 'Work on a Runeya kanban card: loads the card, the board guidelines and the expected ending.',\n args,\n async (input) => {\n const { boardId, cardId, runeyaInstance } = input as { boardId: string; cardId: string; runeyaInstance?: string };\n const pid = runeyaInstance ? Number(runeyaInstance) : undefined;\n const { context } = await cardContext({ boardId, cardId, ...(pid ? { runeyaInstance: pid } : {}) }) as { context: string };\n return {\n description: `Carte ${cardId}`,\n messages: [{\n role: 'user' as const,\n content: {\n type: 'text' as const,\n text: `Je veux travailler sur cette carte du kanban Runeya. Voici son contexte, tel que Runeya le donne à une session lancée depuis la carte : suis ces consignes, en particulier la fin attendue (déplacer la carte si besoin, puis la commenter).\\n\\n${context}`,\n },\n }],\n };\n },\n );\n}\n\n/**\n * Les outils de choix d'instance, pour un MCP lancé hors de Runeya\n * (`runeya mcp`). Ils lisent le registre machine et ne passent par aucune\n * instance : ils doivent répondre avant qu'une instance soit choisie.\n */\nexport function registerInstanceTools(server: McpServer, selector: InstanceSelector): void {\n server.tool(\n 'list_local_runeya_instances',\n 'List the Runeya instances running on this machine, with their pid. When several are running, ask the user which one to use, then pass its pid as runeyaInstance on every call of this conversation.',\n {},\n async () => {\n try {\n return text(await selector.list());\n } catch (err) {\n return error(err);\n }\n },\n );\n}\n"],"mappings":"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","names":["join","z","join","authToken","z"]}
package/index.js CHANGED
@@ -66,7 +66,8 @@ var ENV_FLAGS = [
66
66
  { flag: "--cloud-url", env: "RUNEYA_CLOUD_URL" },
67
67
  { flag: "--cloud-app-url", env: "RUNEYA_CLOUD_APP_URL" },
68
68
  { flag: "--cors-origin", env: "RUNEYA_CORS_ORIGIN" },
69
- { flag: "--claude-pty", env: "RUNEYA_CLAUDE_PTY" }
69
+ { flag: "--claude-pty", env: "RUNEYA_CLAUDE_PTY" },
70
+ { flag: "--project", env: "RUNEYA_PROJECT_ID" }
70
71
  ];
71
72
  var flagsWithValues = /* @__PURE__ */ new Set([
72
73
  "--port",
@@ -83,10 +84,10 @@ for (const { flag, env: key } of ENV_FLAGS) {
83
84
  }
84
85
  if (args.includes("--debug")) process.env["RUNEYA_DEBUG"] = "1";
85
86
  async function runDesktopInstaller(required) {
86
- const { installDesktopLauncher, listDesktopLaunchers, uninstallDesktopLauncher, deriveIdentity } = await import("./desktop-launcher-YPNRQMIS.js");
87
+ const { installDesktopLauncher, listDesktopLaunchers, uninstallDesktopLauncher, deriveIdentity } = await import("./desktop-launcher-GD4QPM66.js");
87
88
  const { printInstallReport } = await import("./desktop-wizard-XIDDAG7L.js");
88
- const { runInstallerPage } = await import("./desktop-installer-page-6JDSXK2E.js");
89
- const { createGlobalInstaller } = await import("./install-gate-2NNH7RM2.js");
89
+ const { runInstallerPage } = await import("./desktop-installer-page-I5FHFDES.js");
90
+ const { createGlobalInstaller } = await import("./install-gate-PVMGJDWE.js");
90
91
  const current = { nodePath: process.execPath, cliEntry: fileURLToPath(import.meta.url) };
91
92
  const outcome = await runInstallerPage({
92
93
  openUrl: (url) => open(url),
@@ -109,11 +110,11 @@ async function runDesktopInstaller(required) {
109
110
  }
110
111
  var subCommand = args[0];
111
112
  if (subCommand === "install-desktop" || subCommand === "uninstall-desktop") {
112
- const { installDesktopLauncher, uninstallDesktopLauncher, deriveIdentity } = await import("./desktop-launcher-YPNRQMIS.js");
113
+ const { installDesktopLauncher, uninstallDesktopLauncher, deriveIdentity } = await import("./desktop-launcher-GD4QPM66.js");
113
114
  const { shouldOpenInstallPage, printInstallReport, printUninstallReport } = await import("./desktop-wizard-XIDDAG7L.js");
114
115
  const { parsePort } = await import("./free-port-5UNHDWGE.js");
115
116
  const rest = args.slice(1);
116
- const subFlagsWithValues = /* @__PURE__ */ new Set(["--name", "--port", "--agent-port"]);
117
+ const subFlagsWithValues = /* @__PURE__ */ new Set(["--name", "--port", "--agent-port", "--project"]);
117
118
  const positional = rest.find((a, i) => !a.startsWith("-") && !subFlagsWithValues.has(rest[i - 1] ?? ""));
118
119
  const nameFlag = getFlagValue(["--name"]);
119
120
  const portFlagRaw = getFlagValue(["--port"]);
@@ -126,6 +127,14 @@ if (subCommand === "install-desktop" || subCommand === "uninstall-desktop") {
126
127
  }
127
128
  const port = parsePort(portFlagRaw);
128
129
  const agentPort = parsePort(agentPortFlagRaw);
130
+ const projectId = getFlagValue(["--project"]) ?? null;
131
+ if (projectId !== null) {
132
+ const { isLauncherProjectId } = await import("./src-DCCEUWE6.js");
133
+ if (!isLauncherProjectId(projectId)) {
134
+ console.error(`Erreur : --project attend un id de projet (re\xE7u ${JSON.stringify(projectId)}).`);
135
+ process.exit(1);
136
+ }
137
+ }
129
138
  const workingDir = rest.includes("--no-cwd") ? null : resolve(positional ?? process.cwd());
130
139
  const displayName = nameFlag;
131
140
  const launcherBase = { nodePath: process.execPath, cliEntry: fileURLToPath(import.meta.url), distDir: __dirname };
@@ -134,7 +143,7 @@ if (subCommand === "install-desktop" || subCommand === "uninstall-desktop") {
134
143
  }
135
144
  const { name, id } = deriveIdentity(displayName, workingDir);
136
145
  if (subCommand === "install-desktop") {
137
- const { createGlobalInstaller, SKIP_INSTALL_CHECK_ENV } = await import("./install-gate-2NNH7RM2.js");
146
+ const { createGlobalInstaller, SKIP_INSTALL_CHECK_ENV } = await import("./install-gate-PVMGJDWE.js");
138
147
  if (process.env[SKIP_INSTALL_CHECK_ENV] !== "1") {
139
148
  const target = { cliEntry: launcherBase.cliEntry };
140
149
  const global = await (await createGlobalInstaller(target, version)).state();
@@ -155,7 +164,8 @@ if (subCommand === "install-desktop" || subCommand === "uninstall-desktop") {
155
164
  name,
156
165
  id,
157
166
  port,
158
- agentPort
167
+ agentPort,
168
+ projectId
159
169
  });
160
170
  printInstallReport({ name, workingDir, nodePath: process.execPath, port, agentPort, result });
161
171
  } else {
@@ -168,7 +178,7 @@ if (subCommand === "install-desktop" || subCommand === "uninstall-desktop") {
168
178
  }
169
179
  }
170
180
  if (subCommand === "instances") {
171
- const { listInstances, killInstance, restartInstance } = await import("./src-5TEXF3SU.js");
181
+ const { listInstances, killInstance, restartInstance } = await import("./src-LEDGW5KP.js");
172
182
  const { runInstancesCommand } = await import("./instances-command-KZ5UGXOR.js");
173
183
  process.exit(await runInstancesCommand(args.slice(1), {
174
184
  listInstances,
@@ -182,6 +192,13 @@ if (subCommand === "instances") {
182
192
  warn: (message) => console.error(message)
183
193
  }));
184
194
  }
195
+ if (subCommand === "mcp") {
196
+ const { listInstances } = await import("./src-LEDGW5KP.js");
197
+ const { runExternalMcp } = await import("./external-NMKQPROK.js");
198
+ await runExternalMcp({ listInstances });
199
+ await new Promise(() => {
200
+ });
201
+ }
185
202
  var targetDir = args.find((a, i) => !a.startsWith("-") && !flagsWithValues.has(args[i - 1] ?? ""));
186
203
  if (targetDir) {
187
204
  const resolved = resolve(targetDir);
@@ -208,10 +225,13 @@ Commands:
208
225
  --no-cwd Global launcher \u2014 do not pin a working directory
209
226
  --port <n> Port baked into the launcher (default: Runeya's own)
210
227
  --agent-port <n> Agent port baked in (default: launcher port + 1)
228
+ --project <id> Open this project directly (no project list)
211
229
  -y, --yes Skip the wizard and take the defaults
212
230
  instances [open|stop|restart <pid>]
213
231
  List the Runeya running on this machine, and act on
214
232
  one. --json for the raw records.
233
+ mcp Runeya MCP server on stdio, for Claude Code, Codex,
234
+ Cursor\u2026 Asks for access in the Runeya app on first use.
215
235
  --from-desktop Set by desktop launchers: report startup failures
216
236
  in a browser window instead of a silent exit
217
237
 
@@ -248,6 +268,8 @@ Configuration:
248
268
  --lan RUNEYA_LAN_PROXY false
249
269
  --debug RUNEYA_DEBUG false
250
270
  --claude-pty <0|1> RUNEYA_CLAUDE_PTY 1
271
+ --project <id> RUNEYA_PROJECT_ID \u2014
272
+ Open this project only: no project list, no switching
251
273
  PTY mode for the Claude Code runner
252
274
 
253
275
  Secrets have no flag \u2014 process arguments are readable by other users via 'ps':
@@ -332,7 +354,7 @@ function listenWithRetry(server, port, host, wss, maxRetries = 20, delay = 500)
332
354
  });
333
355
  }
334
356
  async function main() {
335
- const { mustOpenInstaller, hasDesktop, SKIP_INSTALL_CHECK_ENV } = await import("./install-gate-2NNH7RM2.js");
357
+ const { mustOpenInstaller, hasDesktop, SKIP_INSTALL_CHECK_ENV } = await import("./install-gate-PVMGJDWE.js");
336
358
  if (mustOpenInstaller({
337
359
  isFromDesktop,
338
360
  isRestartHandoff: process.env.RUNEYA_RESTART_HANDOFF === "1",
@@ -364,7 +386,7 @@ async function main() {
364
386
  console.warn(`[cli] Journal sur disque indisponible : ${err.message}`);
365
387
  }
366
388
  }
367
- const { createLocalServer, pullEnv, PullEnvError, registerInstance, registerCliLocation } = await import("./src-5TEXF3SU.js");
389
+ const { createLocalServer, pullEnv, PullEnvError, registerInstance, registerCliLocation } = await import("./src-LEDGW5KP.js");
368
390
  registerCliLocation({ execPath: process.execPath, cliEntry: fileURLToPath(import.meta.url) });
369
391
  if (isPullEnv) {
370
392
  if (!serviceArg) {
@@ -473,7 +495,7 @@ async function main() {
473
495
  networkInfo,
474
496
  setLanMode,
475
497
  resolveTrayIcon
476
- } = await import("./src-5TEXF3SU.js");
498
+ } = await import("./src-LEDGW5KP.js");
477
499
  tray = await startInstanceTray({
478
500
  info: { cwd: process.cwd(), port, version, url: appUrl },
479
501
  assetsDir: join(__dirname, "assets"),