@antelopejs/dms-ai 0.4.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/README.md +1 -1
  2. package/dist/constants/database.d.ts +7 -0
  3. package/dist/constants/database.js +11 -0
  4. package/dist/constants/database.js.map +1 -0
  5. package/dist/constants/pages.d.ts +5 -0
  6. package/dist/constants/pages.js +6 -1
  7. package/dist/constants/pages.js.map +1 -1
  8. package/dist/database/catalog.d.ts +4 -0
  9. package/dist/database/catalog.js +25 -0
  10. package/dist/database/catalog.js.map +1 -0
  11. package/dist/frontend-module.d.ts +2 -0
  12. package/dist/frontend-module.js +18 -0
  13. package/dist/frontend-module.js.map +1 -0
  14. package/dist/index.js +2 -12
  15. package/dist/index.js.map +1 -1
  16. package/dist/logging/log-buffer.js +1 -1
  17. package/dist/logging/log-buffer.js.map +1 -1
  18. package/dist/pages/activity/page.d.ts +1 -0
  19. package/dist/pages/activity/page.js +13 -0
  20. package/dist/pages/activity/page.js.map +1 -1
  21. package/dist/pages/meta.d.ts +2 -0
  22. package/dist/pages/meta.js +3 -1
  23. package/dist/pages/meta.js.map +1 -1
  24. package/dist/pages/overview/insights.js +0 -2
  25. package/dist/pages/overview/insights.js.map +1 -1
  26. package/dist/pages/overview/page.d.ts +1 -1
  27. package/dist/pages/overview/page.js +12 -11
  28. package/dist/pages/overview/page.js.map +1 -1
  29. package/dist/pages/settings/page.d.ts +1 -0
  30. package/dist/pages/settings/page.js +1 -0
  31. package/dist/pages/settings/page.js.map +1 -1
  32. package/dist/pages/skills/page.js +7 -0
  33. package/dist/pages/skills/page.js.map +1 -1
  34. package/dist/routes/activity.js +5 -2
  35. package/dist/routes/activity.js.map +1 -1
  36. package/dist/routes/builder.js +8 -0
  37. package/dist/routes/builder.js.map +1 -1
  38. package/dist/routes/codex-account.d.ts +8 -0
  39. package/dist/routes/codex-account.js +162 -0
  40. package/dist/routes/codex-account.js.map +1 -0
  41. package/dist/routes/database.d.ts +21 -0
  42. package/dist/routes/database.js +115 -0
  43. package/dist/routes/database.js.map +1 -0
  44. package/dist/routes/index.d.ts +2 -0
  45. package/dist/routes/index.js +2 -0
  46. package/dist/routes/index.js.map +1 -1
  47. package/dist/routes/list-query.d.ts +1 -0
  48. package/dist/routes/list-query.js +9 -0
  49. package/dist/routes/list-query.js.map +1 -1
  50. package/dist/routes/metric-shapes.js +7 -4
  51. package/dist/routes/metric-shapes.js.map +1 -1
  52. package/dist/routes/metrics.d.ts +2 -2
  53. package/dist/routes/metrics.js +10 -6
  54. package/dist/routes/metrics.js.map +1 -1
  55. package/dist/types/metrics.d.ts +2 -0
  56. package/frontend-vue/app/chat/components/ConversationDrawer.vue +2 -1
  57. package/frontend-vue/app/chat/components/PanelHeader.vue +3 -1
  58. package/frontend-vue/app/chat/utils/markdown.ts +36 -20
  59. package/frontend-vue/app/chat/utils/tool-lexicon.ts +46 -0
  60. package/frontend-vue/app/components/ActivityDetail.vue +2 -5
  61. package/frontend-vue/app/components/ChatPanel.vue +3 -1
  62. package/frontend-vue/app/components/SkillDetail.vue +72 -26
  63. package/frontend-vue/app/views/RowStepper.vue +1 -1
  64. package/frontend-vue/app/views/types.ts +0 -10
  65. package/frontend-vue/i18n/locales/dms-ai-pages-en-GB.json +46 -4
  66. package/frontend-vue/i18n/locales/dms-ai-pages-fr-FR.json +46 -4
  67. package/frontend-vue/i18n/locales/dms-ai-panel-en-GB.json +8 -0
  68. package/frontend-vue/i18n/locales/dms-ai-panel-fr-FR.json +8 -0
  69. package/frontend-vue/i18n/locales/dms-ai-views-en-GB.json +1 -0
  70. package/frontend-vue/i18n/locales/dms-ai-views-fr-FR.json +1 -0
  71. package/frontend-vue/package.json +1 -1
  72. package/frontend-vue/tests/chat/markdown.test.ts +69 -0
  73. package/frontend-vue/tests/i18n-views.test.ts +63 -0
  74. package/frontend-vue/tests/panel-header-height.test.ts +40 -0
  75. package/frontend-vue/tests/panel-states.test.ts +1 -1
  76. package/package.json +7 -5
  77. package/sidecar/dist/agent/permission-preview.js +49 -2
  78. package/sidecar/dist/agent/permission-preview.js.map +1 -1
  79. package/sidecar/dist/agent/tool-kinds.d.ts +1 -0
  80. package/sidecar/dist/agent/tool-kinds.js +2 -1
  81. package/sidecar/dist/agent/tool-kinds.js.map +1 -1
  82. package/sidecar/dist/constants/builder.d.ts +7 -1
  83. package/sidecar/dist/constants/builder.js +10 -0
  84. package/sidecar/dist/constants/builder.js.map +1 -1
  85. package/sidecar/dist/constants/chatgpt.d.ts +39 -0
  86. package/sidecar/dist/constants/chatgpt.js +55 -0
  87. package/sidecar/dist/constants/chatgpt.js.map +1 -0
  88. package/sidecar/dist/constants/codex.d.ts +4 -5
  89. package/sidecar/dist/constants/codex.js +4 -5
  90. package/sidecar/dist/constants/codex.js.map +1 -1
  91. package/sidecar/dist/constants/database.d.ts +11 -0
  92. package/sidecar/dist/constants/database.js +17 -0
  93. package/sidecar/dist/constants/database.js.map +1 -0
  94. package/sidecar/dist/constants/http.d.ts +3 -0
  95. package/sidecar/dist/constants/http.js +3 -0
  96. package/sidecar/dist/constants/http.js.map +1 -1
  97. package/sidecar/dist/constants/mcp.js +2 -0
  98. package/sidecar/dist/constants/mcp.js.map +1 -1
  99. package/sidecar/dist/constants/providers.d.ts +1 -1
  100. package/sidecar/dist/constants/providers.js +1 -1
  101. package/sidecar/dist/constants/providers.js.map +1 -1
  102. package/sidecar/dist/constants/tool-kinds.d.ts +1 -0
  103. package/sidecar/dist/constants/tool-kinds.js +7 -0
  104. package/sidecar/dist/constants/tool-kinds.js.map +1 -1
  105. package/sidecar/dist/database/database-client.d.ts +11 -0
  106. package/sidecar/dist/database/database-client.js +50 -0
  107. package/sidecar/dist/database/database-client.js.map +1 -0
  108. package/sidecar/dist/index.js +6 -0
  109. package/sidecar/dist/index.js.map +1 -1
  110. package/sidecar/dist/mcp/tool-definitions.js +2 -0
  111. package/sidecar/dist/mcp/tool-definitions.js.map +1 -1
  112. package/sidecar/dist/mcp/tools/builder-gate.js +3 -1
  113. package/sidecar/dist/mcp/tools/builder-gate.js.map +1 -1
  114. package/sidecar/dist/mcp/tools/builder-project.d.ts +8 -0
  115. package/sidecar/dist/mcp/tools/builder-project.js +181 -0
  116. package/sidecar/dist/mcp/tools/builder-project.js.map +1 -0
  117. package/sidecar/dist/mcp/tools/builder.d.ts +5 -2
  118. package/sidecar/dist/mcp/tools/builder.js +5 -11
  119. package/sidecar/dist/mcp/tools/builder.js.map +1 -1
  120. package/sidecar/dist/mcp/tools/database.d.ts +6 -0
  121. package/sidecar/dist/mcp/tools/database.js +42 -0
  122. package/sidecar/dist/mcp/tools/database.js.map +1 -0
  123. package/sidecar/dist/mcp/types.d.ts +2 -0
  124. package/sidecar/dist/metrics/aggregate.js +29 -7
  125. package/sidecar/dist/metrics/aggregate.js.map +1 -1
  126. package/sidecar/dist/metrics/types.d.ts +2 -0
  127. package/sidecar/dist/providers/claude/safe-mode.js +11 -1
  128. package/sidecar/dist/providers/claude/safe-mode.js.map +1 -1
  129. package/sidecar/dist/providers/codex/chatgpt/credentials.d.ts +10 -0
  130. package/sidecar/dist/providers/codex/chatgpt/credentials.js +63 -0
  131. package/sidecar/dist/providers/codex/chatgpt/credentials.js.map +1 -0
  132. package/sidecar/dist/providers/codex/chatgpt/http.d.ts +3 -0
  133. package/sidecar/dist/providers/codex/chatgpt/http.js +66 -0
  134. package/sidecar/dist/providers/codex/chatgpt/http.js.map +1 -0
  135. package/sidecar/dist/providers/codex/chatgpt/lock.d.ts +1 -0
  136. package/sidecar/dist/providers/codex/chatgpt/lock.js +58 -0
  137. package/sidecar/dist/providers/codex/chatgpt/lock.js.map +1 -0
  138. package/sidecar/dist/providers/codex/chatgpt/login.d.ts +8 -0
  139. package/sidecar/dist/providers/codex/chatgpt/login.js +138 -0
  140. package/sidecar/dist/providers/codex/chatgpt/login.js.map +1 -0
  141. package/sidecar/dist/providers/codex/chatgpt/oauth.d.ts +38 -0
  142. package/sidecar/dist/providers/codex/chatgpt/oauth.js +167 -0
  143. package/sidecar/dist/providers/codex/chatgpt/oauth.js.map +1 -0
  144. package/sidecar/dist/providers/codex/chatgpt/store.d.ts +21 -0
  145. package/sidecar/dist/providers/codex/chatgpt/store.js +75 -0
  146. package/sidecar/dist/providers/codex/chatgpt/store.js.map +1 -0
  147. package/sidecar/dist/providers/codex/config.d.ts +1 -5
  148. package/sidecar/dist/providers/codex/config.js +27 -4
  149. package/sidecar/dist/providers/codex/config.js.map +1 -1
  150. package/sidecar/dist/providers/codex/module.js +11 -4
  151. package/sidecar/dist/providers/codex/module.js.map +1 -1
  152. package/sidecar/dist/providers/codex/process.d.ts +1 -1
  153. package/sidecar/dist/providers/codex/process.js +7 -3
  154. package/sidecar/dist/providers/codex/process.js.map +1 -1
  155. package/sidecar/dist/providers/codex/provider.d.ts +0 -1
  156. package/sidecar/dist/providers/codex/provider.js +7 -5
  157. package/sidecar/dist/providers/codex/provider.js.map +1 -1
  158. package/sidecar/dist/server/http.js +38 -0
  159. package/sidecar/dist/server/http.js.map +1 -1
  160. package/sidecar/dist/server/permission-events.js +3 -1
  161. package/sidecar/dist/server/permission-events.js.map +1 -1
  162. package/sidecar/dist/server/raw-data.js +1 -1
  163. package/sidecar/dist/server/raw-data.js.map +1 -1
  164. package/sidecar/dist/server/safety-net.js +1 -1
  165. package/sidecar/dist/server/safety-net.js.map +1 -1
  166. package/skills/dms-ai/SKILL.md +2 -1
  167. package/skills/dms-external-database/REFERENCE.md +132 -0
  168. package/skills/dms-external-database/SKILL.md +142 -0
@@ -304,6 +304,30 @@ const ASSISTANT_TOOLS: Record<string, ToolLexiconEntry> = {
304
304
  target: firstOf("query", "search", "level"),
305
305
  isReadOnly: true,
306
306
  },
307
+ DatabaseConnections: {
308
+ verb: "list_databases",
309
+ icon: ICONS.DATA,
310
+ target: none,
311
+ isReadOnly: true,
312
+ },
313
+ DatabaseRelations: {
314
+ verb: "list_relations",
315
+ icon: ICONS.DATA,
316
+ target: firstOf("connection"),
317
+ isReadOnly: true,
318
+ },
319
+ DatabaseDescribe: {
320
+ verb: "describe_relations",
321
+ icon: ICONS.TABLE,
322
+ target: countOf("relations"),
323
+ isReadOnly: true,
324
+ },
325
+ DatabaseProfile: {
326
+ verb: "profile_relation",
327
+ icon: ICONS.TABLE,
328
+ target: firstOf("relation", "connection"),
329
+ isReadOnly: true,
330
+ },
307
331
  };
308
332
 
309
333
  const BUILDER_TOOLS: Record<string, ToolLexiconEntry> = {
@@ -393,6 +417,28 @@ const BUILDER_TOOLS: Record<string, ToolLexiconEntry> = {
393
417
  target: firstOf("ref"),
394
418
  isReadOnly: true,
395
419
  },
420
+ BuilderConfigureProject: {
421
+ verb: "configure_project",
422
+ icon: ICONS.DATA,
423
+ target: countOf("steps"),
424
+ },
425
+ BuilderMapResources: {
426
+ verb: "map_tables",
427
+ icon: ICONS.TABLE,
428
+ target: firstOf("connection", "module"),
429
+ },
430
+ BuilderMapping: {
431
+ verb: "read_mapping",
432
+ icon: ICONS.TABLE,
433
+ target: firstOf("connection", "module"),
434
+ isReadOnly: true,
435
+ },
436
+ BuilderAnalyseDatabase: {
437
+ verb: "analyse_database",
438
+ icon: ICONS.TABLE,
439
+ target: firstOf("connection", "job"),
440
+ isReadOnly: true,
441
+ },
396
442
  BuilderCreateResource: {
397
443
  verb: "create_table",
398
444
  icon: ICONS.TABLE,
@@ -9,11 +9,8 @@ import { useAiData } from "../views/composables/useAiData";
9
9
  import { useAssistant } from "../views/composables/useAssistant";
10
10
  import { useViewFormat } from "../views/composables/useViewFormat";
11
11
  import { AI_PAGES, AI_ROUTES, CHANGE_SET_QUERY_KEY } from "../views/constants";
12
- import type {
13
- ActivityDetailPayload,
14
- ActivityRow,
15
- RowNavigation,
16
- } from "../views/types";
12
+ import type { RowNavigation } from "#dms-ui/app/types/row-navigation";
13
+ import type { ActivityDetailPayload, ActivityRow } from "../views/types";
17
14
  import {
18
15
  activityOutput,
19
16
  argumentItems,
@@ -173,9 +173,11 @@ onMounted(() => {
173
173
  color: var(--ui-text-muted);
174
174
  }
175
175
 
176
+ /* Sits where the panel header's close button does: centred in a header
177
+ as tall as the DMS navbar (28px is the small square button height). */
176
178
  .dms-ai-panel-close {
177
179
  position: absolute;
178
- top: 11px;
180
+ top: calc((var(--ui-header-height) - 28px) / 2);
179
181
  right: 8px;
180
182
  }
181
183
 
@@ -1,15 +1,25 @@
1
1
  <script setup lang="ts">
2
+ import type { Tone } from "#dms-ui/app/types/tone";
2
3
  import { computed } from "vue";
3
- import { renderMarkdown } from "../chat/utils/markdown";
4
- import RowStepper from "../views/RowStepper.vue";
4
+ import { renderDocument } from "../chat/utils/markdown";
5
5
  import { useAssistant } from "../views/composables/useAssistant";
6
6
  import { useViewFormat } from "../views/composables/useViewFormat";
7
- import type { RowNavigation, SkillRow } from "../views/types";
7
+ import type { SkillRow } from "../views/types";
8
8
  import { skillLook } from "../views/utils/skills";
9
9
 
10
10
  interface Props {
11
11
  rowData?: SkillRow;
12
- navigation?: RowNavigation;
12
+ }
13
+
14
+ /** A fact of the skill: a status pill, an identifier (mono) or plain text. */
15
+ interface SkillFact {
16
+ id: string;
17
+ label: string;
18
+ value: string;
19
+ type?: "status" | "mono";
20
+ tone?: Tone;
21
+ /** Draws a button copying the value. */
22
+ copy?: boolean;
13
23
  }
14
24
 
15
25
  const props = defineProps<Props>();
@@ -20,9 +30,9 @@ const assistant = useAssistant();
20
30
 
21
31
  const skill = computed(() => props.rowData);
22
32
  const look = computed(() => (skill.value ? skillLook(skill.value) : null));
23
- const html = computed(() => renderMarkdown(skill.value?.body ?? ""));
33
+ const html = computed(() => renderDocument(skill.value?.body ?? ""));
24
34
 
25
- const facts = computed(() => {
35
+ const facts = computed<SkillFact[]>(() => {
26
36
  const current = skill.value;
27
37
  if (!current || !look.value) return [];
28
38
  return [
@@ -30,14 +40,21 @@ const facts = computed(() => {
30
40
  id: "source",
31
41
  label: t("dms_ai.views.skills.source_label"),
32
42
  value: t(`dms_ai.views.skills.source.${current.source}`),
33
- type: "status" as const,
43
+ type: "status",
34
44
  tone: look.value.badgeTone,
35
45
  },
46
+ {
47
+ id: "qualified",
48
+ label: t("dms_ai.views.skills.qualified_name_label"),
49
+ value: current.qualifiedName,
50
+ type: "mono",
51
+ copy: true,
52
+ },
36
53
  {
37
54
  id: "origin",
38
55
  label: t("dms_ai.views.skills.origin_label"),
39
56
  value: current.origin,
40
- type: "mono" as const,
57
+ type: "mono",
41
58
  copy: true,
42
59
  },
43
60
  {
@@ -67,22 +84,6 @@ function openLastConversation(): void {
67
84
 
68
85
  <template>
69
86
  <div v-if="skill && look" class="flex min-h-full flex-col gap-5">
70
- <RowStepper v-if="props.navigation" :navigation="props.navigation" />
71
-
72
- <header class="flex items-start gap-3">
73
- <DmsIconWell :icon="look.icon" :tone="look.tone" size="lg" />
74
- <div class="min-w-0 flex-1">
75
- <h3
76
- class="text-highlighted truncate font-mono text-[15px] font-semibold"
77
- >
78
- {{ skill.name }}
79
- </h3>
80
- <p class="text-dimmed mt-0.5 truncate font-mono text-[11.5px]">
81
- {{ skill.qualifiedName }}
82
- </p>
83
- </div>
84
- </header>
85
-
86
87
  <DmsBanner
87
88
  v-if="skill.isShadowed"
88
89
  size="sm"
@@ -95,9 +96,38 @@ function openLastConversation(): void {
95
96
  "
96
97
  />
97
98
 
98
- <DmsKeyValueList :items="facts" />
99
+ <!-- The drawer's header names the skill. The facts read as a record
100
+ drawer's fields: the DMS form row, a label column beside a
101
+ left-aligned value once the drawer is wide enough, the label above
102
+ it on a phone. -->
103
+ <div class="grid gap-3">
104
+ <DmsFieldRow
105
+ v-for="fact in facts"
106
+ :key="fact.id"
107
+ :label="fact.label"
108
+ layout="form"
109
+ spacing="list"
110
+ :inset="false"
111
+ >
112
+ <div class="flex min-w-0 items-center gap-1.5 text-[13px]">
113
+ <DmsStatusPill
114
+ v-if="fact.type === 'status'"
115
+ :tone="fact.tone ?? 'neutral'"
116
+ :label="fact.value"
117
+ />
118
+ <span
119
+ v-else
120
+ class="text-highlighted wrap-anywhere min-w-0"
121
+ :class="fact.type === 'mono' && 'font-mono text-[12.5px]'"
122
+ >
123
+ {{ fact.value }}
124
+ </span>
125
+ <DmsCopyButton v-if="fact.copy" :value="fact.value" class="-my-1" />
126
+ </div>
127
+ </DmsFieldRow>
128
+ </div>
99
129
 
100
- <p class="text-muted text-[13px] leading-relaxed">
130
+ <p class="text-muted max-w-[76ch] text-[13px] leading-relaxed">
101
131
  {{ skill.description }}
102
132
  </p>
103
133
 
@@ -185,4 +215,20 @@ function openLastConversation(): void {
185
215
  color: var(--ui-primary);
186
216
  text-decoration: underline;
187
217
  }
218
+ /* Running text keeps a readable measure in the wide sheet; code and tables
219
+ may use the whole width. */
220
+ .dms-ai-skill-doc :deep(:is(p, ul, ol, blockquote, h1, h2, h3, h4, h5, h6)) {
221
+ max-width: 76ch;
222
+ }
223
+ .dms-ai-skill-doc :deep(table) {
224
+ margin: 0 0 8px;
225
+ border-collapse: collapse;
226
+ display: block;
227
+ overflow-x: auto;
228
+ }
229
+ .dms-ai-skill-doc :deep(:is(th, td)) {
230
+ padding: 4px 8px;
231
+ border: 1px solid var(--ui-border);
232
+ text-align: start;
233
+ }
188
234
  </style>
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import type { RowNavigation } from "./types";
2
+ import type { RowNavigation } from "#dms-ui/app/types/row-navigation";
3
3
 
4
4
  interface Props {
5
5
  navigation: RowNavigation;
@@ -170,16 +170,6 @@ export interface SkillRow {
170
170
  icon?: string;
171
171
  }
172
172
 
173
- /** What a DMS row drawer hands its component to step through the rows. */
174
- export interface RowNavigation {
175
- index: number;
176
- total: number;
177
- hasPrev: boolean;
178
- hasNext: boolean;
179
- prev: () => void;
180
- next: () => void;
181
- }
182
-
183
173
  /** One fact of a change set, a row of a DmsKeyValueList. */
184
174
  export interface Fact {
185
175
  id: string;
@@ -28,6 +28,10 @@
28
28
  },
29
29
  "blocks": {
30
30
  "overview": {
31
+ "toolbar": {
32
+ "name": "Toolbar",
33
+ "description": "When the Overview last read its figures, a refresh, and the period"
34
+ },
31
35
  "period": {
32
36
  "name": "Period",
33
37
  "description": "Picks the period the Overview's figures cover"
@@ -80,11 +84,11 @@
80
84
  },
81
85
  "activity_chart": {
82
86
  "name": "Activity chart",
83
- "description": "Actions per day, compared with the previous period",
87
+ "description": "Actions per day, against the chosen comparison",
84
88
  "title": "Activity",
85
89
  "caption": "Actions over the selected period",
86
90
  "primary": "Actions",
87
- "comparison": "Previous period"
91
+ "comparison": "Comparison"
88
92
  },
89
93
  "allowed": {
90
94
  "name": "How actions were allowed",
@@ -133,6 +137,14 @@
133
137
  }
134
138
  },
135
139
  "activity": {
140
+ "toolbar": {
141
+ "name": "Toolbar",
142
+ "description": "When the audit log was last read, a refresh, and the period"
143
+ },
144
+ "period": {
145
+ "name": "Period",
146
+ "description": "Picks the period the audit log covers"
147
+ },
136
148
  "log": {
137
149
  "name": "Audit log",
138
150
  "description": "Every tool call, how it was allowed and what came out"
@@ -161,6 +173,10 @@
161
173
  }
162
174
  },
163
175
  "settings": {
176
+ "chatgpt": {
177
+ "name": "ChatGPT account",
178
+ "description": "The ChatGPT account Codex runs on, with sign-in and sign-out"
179
+ },
164
180
  "form": {
165
181
  "name": "Assistant settings",
166
182
  "description": "Agent, approvals, scope, skills and history defaults"
@@ -408,7 +424,32 @@
408
424
  },
409
425
  "codex": {
410
426
  "label": "Codex",
411
- "description": "OpenAI · needs the Codex CLI and an OpenAI API key"
427
+ "description": "OpenAI · runs on your ChatGPT plan, once signed in below"
428
+ }
429
+ },
430
+ "chatgpt": {
431
+ "signed_out": {
432
+ "title": "Run Codex on your ChatGPT plan",
433
+ "description": "Sign in with ChatGPT to use your plan's Codex usage here, with no API key. If the DMS runs on another machine, the page OpenAI sends you back to won't load: paste its address with “Paste the return address”.",
434
+ "action": "Sign in with ChatGPT"
435
+ },
436
+ "signed_in": {
437
+ "title": "Codex runs on the ChatGPT plan of {email}",
438
+ "description": "Its usage counts toward your plan's limits, which you can cap per app in ChatGPT's settings.",
439
+ "action": "Sign out"
440
+ },
441
+ "sign_out_confirm": {
442
+ "title": "Sign out of ChatGPT?",
443
+ "description": "Codex stops working on every DMS project of this machine until you sign in again. To remove dms-ai from your ChatGPT account entirely, disconnect it in ChatGPT's settings."
444
+ },
445
+ "signed_out_toast": "Signed out of ChatGPT",
446
+ "paste": {
447
+ "action": "Paste the return address",
448
+ "title": "Finish signing in with ChatGPT",
449
+ "description": "After you allowed access, OpenAI sent you to an address starting with http://127.0.0.1 that could not load. Copy that whole address from the address bar and paste it here. It works once, within 10 minutes.",
450
+ "field": "Return address",
451
+ "confirm": "Finish signing in",
452
+ "success": "Signed in with ChatGPT"
412
453
  }
413
454
  },
414
455
  "thinking": {
@@ -532,7 +573,8 @@
532
573
  },
533
574
  "detail": {
534
575
  "open": "Details",
535
- "title": "Skill detail"
576
+ "title": "Skill detail",
577
+ "position": "Skill {index} of {total}"
536
578
  },
537
579
  "empty": {
538
580
  "title": "No skills loaded",
@@ -28,6 +28,10 @@
28
28
  },
29
29
  "blocks": {
30
30
  "overview": {
31
+ "toolbar": {
32
+ "name": "Barre d'outils",
33
+ "description": "Quand la vue d'ensemble a lu ses chiffres, un rafraîchissement et la période"
34
+ },
31
35
  "period": {
32
36
  "name": "Période",
33
37
  "description": "Choisit la période couverte par les chiffres de la vue d'ensemble"
@@ -80,11 +84,11 @@
80
84
  },
81
85
  "activity_chart": {
82
86
  "name": "Graphique d'activité",
83
- "description": "Actions par jour, comparées à la période précédente",
87
+ "description": "Actions par jour, face à la comparaison choisie",
84
88
  "title": "Activité",
85
89
  "caption": "Actions sur la période choisie",
86
90
  "primary": "Actions",
87
- "comparison": "Période précédente"
91
+ "comparison": "Comparaison"
88
92
  },
89
93
  "allowed": {
90
94
  "name": "Comment les actions ont été autorisées",
@@ -133,6 +137,14 @@
133
137
  }
134
138
  },
135
139
  "activity": {
140
+ "toolbar": {
141
+ "name": "Barre d'outils",
142
+ "description": "Quand le journal d'audit a été lu, un rafraîchissement et la période"
143
+ },
144
+ "period": {
145
+ "name": "Période",
146
+ "description": "Choisit la période couverte par le journal d'audit"
147
+ },
136
148
  "log": {
137
149
  "name": "Journal d'audit",
138
150
  "description": "Chaque appel d'outil, comment il a été autorisé et son résultat"
@@ -161,6 +173,10 @@
161
173
  }
162
174
  },
163
175
  "settings": {
176
+ "chatgpt": {
177
+ "name": "Compte ChatGPT",
178
+ "description": "Le compte ChatGPT sur lequel tourne Codex, avec connexion et déconnexion"
179
+ },
164
180
  "form": {
165
181
  "name": "Réglages de l'assistant",
166
182
  "description": "Valeurs par défaut de l'agent, des validations, de la portée, des skills et de l'historique"
@@ -408,7 +424,32 @@
408
424
  },
409
425
  "codex": {
410
426
  "label": "Codex",
411
- "description": "OpenAI · nécessite la CLI Codex et une clé d'API OpenAI"
427
+ "description": "OpenAI · utilise votre forfait ChatGPT, une fois connecté ci-dessous"
428
+ }
429
+ },
430
+ "chatgpt": {
431
+ "signed_out": {
432
+ "title": "Utilisez Codex avec votre forfait ChatGPT",
433
+ "description": "Connectez-vous avec ChatGPT pour utiliser ici l'usage Codex de votre forfait, sans clé d'API. Si le DMS tourne sur une autre machine, la page où OpenAI vous renvoie ne chargera pas : collez son adresse avec « Coller l'adresse de retour ».",
434
+ "action": "Se connecter avec ChatGPT"
435
+ },
436
+ "signed_in": {
437
+ "title": "Codex utilise le forfait ChatGPT de {email}",
438
+ "description": "Son usage compte dans les limites de votre forfait, que vous pouvez plafonner par app dans les réglages de ChatGPT.",
439
+ "action": "Se déconnecter"
440
+ },
441
+ "sign_out_confirm": {
442
+ "title": "Se déconnecter de ChatGPT ?",
443
+ "description": "Codex ne fonctionnera plus sur aucun projet DMS de cette machine tant que vous ne vous reconnecterez pas. Pour retirer complètement dms-ai de votre compte ChatGPT, déconnectez-le dans les réglages de ChatGPT."
444
+ },
445
+ "signed_out_toast": "Déconnecté de ChatGPT",
446
+ "paste": {
447
+ "action": "Coller l'adresse de retour",
448
+ "title": "Terminer la connexion à ChatGPT",
449
+ "description": "Après votre autorisation, OpenAI vous a envoyé vers une adresse commençant par http://127.0.0.1 qui n'a pas pu charger. Copiez cette adresse complète depuis la barre d'adresse et collez-la ici. Elle ne sert qu'une fois, dans les 10 minutes.",
450
+ "field": "Adresse de retour",
451
+ "confirm": "Terminer la connexion",
452
+ "success": "Connecté avec ChatGPT"
412
453
  }
413
454
  },
414
455
  "thinking": {
@@ -532,7 +573,8 @@
532
573
  },
533
574
  "detail": {
534
575
  "open": "Détails",
535
- "title": "Détail de la skill"
576
+ "title": "Détail de la skill",
577
+ "position": "Skill {index} sur {total}"
536
578
  },
537
579
  "empty": {
538
580
  "title": "Aucune skill chargée",
@@ -416,6 +416,14 @@
416
416
  "list_pages": "List pages",
417
417
  "typecheck": "Typecheck",
418
418
  "read_logs": "Read logs",
419
+ "list_databases": "List databases",
420
+ "list_relations": "List relations",
421
+ "describe_relations": "Describe relations",
422
+ "profile_relation": "Profile relation",
423
+ "configure_project": "Configure project",
424
+ "map_tables": "Map database tables",
425
+ "analyse_database": "Analyse the database",
426
+ "read_mapping": "Read the mapping",
419
427
  "browse_blocks": "Browse blocks",
420
428
  "read_page": "Read page",
421
429
  "create_page": "Create page",
@@ -416,6 +416,14 @@
416
416
  "list_pages": "Lister les pages",
417
417
  "typecheck": "Vérifier les types",
418
418
  "read_logs": "Lire les journaux",
419
+ "list_databases": "Lister les bases de données",
420
+ "list_relations": "Lister les relations",
421
+ "describe_relations": "Décrire les relations",
422
+ "profile_relation": "Profiler la relation",
423
+ "configure_project": "Configurer le projet",
424
+ "map_tables": "Associer les tables de la base",
425
+ "analyse_database": "Analyser la base",
426
+ "read_mapping": "Lire l'association",
419
427
  "browse_blocks": "Parcourir les blocs",
420
428
  "read_page": "Lire la page",
421
429
  "create_page": "Créer la page",
@@ -131,6 +131,7 @@
131
131
  "uses": "{count} use | {count} uses",
132
132
  "never_used": "never used",
133
133
  "source_label": "Source",
134
+ "qualified_name_label": "Full name",
134
135
  "origin_label": "Origin",
135
136
  "used_label": "Used",
136
137
  "used_value": "{count}× · 30 days",
@@ -131,6 +131,7 @@
131
131
  "uses": "{count} utilisation | {count} utilisations",
132
132
  "never_used": "jamais utilisée",
133
133
  "source_label": "Source",
134
+ "qualified_name_label": "Nom complet",
134
135
  "origin_label": "Origine",
135
136
  "used_label": "Utilisée",
136
137
  "used_value": "{count}× · 30 jours",
@@ -6,7 +6,7 @@
6
6
  "license": "Apache-2.0",
7
7
  "packageManager": "pnpm@10.6.5",
8
8
  "engines": {
9
- "@antelopejs/dms-frontend": ">=0.6.3 <0.7.0"
9
+ "@antelopejs/dms-frontend": ">=0.8.0 <0.9.0"
10
10
  },
11
11
  "scripts": {
12
12
  "build": "pnpm --dir \"${DMS_FRONTEND_WORKSPACE:?Set DMS_FRONTEND_WORKSPACE to the generated Inertia workspace}\" build",
@@ -2,6 +2,7 @@
2
2
  import { describe, expect, it } from "vitest";
3
3
  import {
4
4
  createIncrementalMarkdown,
5
+ renderDocument,
5
6
  renderMarkdown,
6
7
  } from "../../app/chat/utils/markdown";
7
8
 
@@ -270,3 +271,71 @@ describe("a streaming answer renders incrementally", () => {
270
271
  expect(renderedChars).toBeLessThan(paragraphs.length * 5);
271
272
  });
272
273
  });
274
+
275
+ describe("a document reflows its hard-wrapped lines", () => {
276
+ const SKILL = [
277
+ "# Skill",
278
+ "",
279
+ "The DMS is a **backend-driven admin framework** built",
280
+ "on AntelopeJS. You add an",
281
+ "admin screen by writing TypeScript.",
282
+ "",
283
+ "Second paragraph.",
284
+ "",
285
+ "- one item",
286
+ " wrapped",
287
+ "- two",
288
+ "",
289
+ "```ts",
290
+ "const a = 1;",
291
+ "const b = 2;",
292
+ "```",
293
+ "",
294
+ "| a | b |",
295
+ "|---|---|",
296
+ "| 1 | 2 |",
297
+ ].join("\n");
298
+
299
+ function renderSkill(): HTMLElement {
300
+ const host = document.createElement("div");
301
+ host.innerHTML = renderDocument(SKILL);
302
+ return host;
303
+ }
304
+
305
+ it("joins a paragraph's lines with spaces, not line breaks", () => {
306
+ const host = renderSkill();
307
+ expect(host.querySelector("br")).toBeNull();
308
+ // A soft break stays a newline in the text, which HTML shows as a space.
309
+ const text = host.querySelector("p")?.textContent?.replace(/\s+/g, " ");
310
+ expect(text).toBe(
311
+ "The DMS is a backend-driven admin framework built on AntelopeJS. You add an admin screen by writing TypeScript.",
312
+ );
313
+ });
314
+
315
+ it("keeps paragraphs, lists, code blocks and tables", () => {
316
+ const host = renderSkill();
317
+ expect(host.querySelectorAll("p")).toHaveLength(2);
318
+ expect(host.querySelectorAll("li")).toHaveLength(2);
319
+ expect(host.querySelector("li")?.textContent).toBe("one item\nwrapped");
320
+ expect(host.querySelector("pre code")?.textContent).toBe(
321
+ "const a = 1;\nconst b = 2;\n",
322
+ );
323
+ expect(host.querySelectorAll("td")).toHaveLength(2);
324
+ });
325
+
326
+ it("still honours a hard break written in the source", () => {
327
+ const host = document.createElement("div");
328
+ host.innerHTML = renderDocument("first \nsecond");
329
+ expect(host.querySelector("br")).not.toBeNull();
330
+ });
331
+
332
+ it("leaves chat messages breaking on every newline", () => {
333
+ expect(renderMarkdown("one\ntwo")).toContain("<br>");
334
+ });
335
+
336
+ it("sanitizes like a chat message", () => {
337
+ expectHarmless(
338
+ renderDocument("<img src=x onerror=alert(1)>\n[x](javascript:alert(1))"),
339
+ );
340
+ });
341
+ });
@@ -0,0 +1,63 @@
1
+ import { readdirSync, readFileSync, statSync } from "node:fs";
2
+ import { join, resolve } from "node:path";
3
+ import { describe, expect, it } from "vitest";
4
+ import en from "../i18n/locales/dms-ai-views-en-GB.json";
5
+ import fr from "../i18n/locales/dms-ai-views-fr-FR.json";
6
+
7
+ type Tree = Record<string, unknown>;
8
+
9
+ const ROOT = resolve(__dirname, "..");
10
+ // The page components (the skill drawer and cards among them) and the view
11
+ // helpers they share; the chat panel has its own test.
12
+ const SOURCES = ["app/components", "app/views"];
13
+ const EXCLUDED = ["ChatPanel.vue"];
14
+ const LITERAL_KEY = /["'`](dms_ai\.views\.[A-Za-z0-9_.]+[A-Za-z0-9_])["'`]/g;
15
+
16
+ /** Keys built at run time from a value, each with every value it can take. */
17
+ const DYNAMIC_FAMILIES: Record<string, readonly string[]> = {
18
+ "dms_ai.views.skills.source": ["module", "local"],
19
+ };
20
+
21
+ function files(path: string): string[] {
22
+ const full = join(ROOT, path);
23
+ if (!statSync(full).isDirectory()) return [full];
24
+ return readdirSync(full)
25
+ .filter((entry) => !EXCLUDED.includes(entry))
26
+ .flatMap((entry) => files(join(path, entry)));
27
+ }
28
+
29
+ function flatten(tree: Tree, prefix = ""): string[] {
30
+ return Object.entries(tree).flatMap(([key, value]) =>
31
+ value !== null && typeof value === "object"
32
+ ? flatten(value as Tree, `${prefix}${key}.`)
33
+ : [`${prefix}${key}`],
34
+ );
35
+ }
36
+
37
+ function usedKeys(): string[] {
38
+ const source = SOURCES.flatMap(files)
39
+ .map((file) => readFileSync(file, "utf8"))
40
+ .join("\n");
41
+ return [
42
+ ...new Set([...source.matchAll(LITERAL_KEY)].map((match) => match[1])),
43
+ ];
44
+ }
45
+
46
+ const dynamicKeys = Object.entries(DYNAMIC_FAMILIES).flatMap(
47
+ ([prefix, values]) => values.map((value) => `${prefix}.${value}`),
48
+ );
49
+
50
+ describe("the views' strings", () => {
51
+ const english = new Set(flatten(en));
52
+ const french = new Set(flatten(fr));
53
+
54
+ it("exist in English and French alike", () => {
55
+ expect([...english].sort()).toEqual([...french].sort());
56
+ });
57
+
58
+ it("cover every key the views use", () => {
59
+ const keys = [...usedKeys(), ...dynamicKeys];
60
+ expect(keys.length).toBeGreaterThan(0);
61
+ expect(keys.filter((key) => !english.has(key))).toEqual([]);
62
+ });
63
+ });