arcane-os 0.1.0-dev.5

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 (248) hide show
  1. package/CHANGELOG.md +154 -0
  2. package/COMMERCIAL-LICENSE.md +13 -0
  3. package/LICENSE +661 -0
  4. package/NOTICE +70 -0
  5. package/README.md +448 -0
  6. package/bin/arcane-test.mjs +741 -0
  7. package/bin/arcane.mjs +5 -0
  8. package/docs/architecture.md +234 -0
  9. package/docs/compatibility.md +36 -0
  10. package/docs/event-manager.md +166 -0
  11. package/docs/platform-targets.md +108 -0
  12. package/docs/publishing.md +203 -0
  13. package/docs/reference/README.md +117 -0
  14. package/docs/reference/arcane-ollama.md +288 -0
  15. package/docs/reference/availability-and-normalization.md +123 -0
  16. package/docs/reference/behavioral-testing.md +86 -0
  17. package/docs/reference/cli.md +569 -0
  18. package/docs/reference/core/README.md +62 -0
  19. package/docs/reference/core/arcane-ai-contracts.md +873 -0
  20. package/docs/reference/core/arcane-api.md +601 -0
  21. package/docs/reference/core/arcane-entities.md +65 -0
  22. package/docs/reference/core/arcane-events.md +134 -0
  23. package/docs/reference/core/ollama-module.md +181 -0
  24. package/docs/reference/core/reference/arcane-api/ai-and-ollama.md +1909 -0
  25. package/docs/reference/core/reference/arcane-api/applications-terminal-capabilities.md +1057 -0
  26. package/docs/reference/core/reference/arcane-api/core-and-events.md +320 -0
  27. package/docs/reference/core/reference/arcane-api/filesystem-storage-preferences-appearance.md +610 -0
  28. package/docs/reference/core/reference/arcane-api/namespaces.md +1157 -0
  29. package/docs/reference/core/reference/arcane-api/platform-installation-users-system.md +1423 -0
  30. package/docs/reference/core/reference/arcane-api/session-provisioning-diagnostics-development.md +315 -0
  31. package/docs/reference/event-manager.md +957 -0
  32. package/docs/reference/inventory/package-api.json +2632 -0
  33. package/docs/reference/inventory/runtime-components.json +934 -0
  34. package/docs/reference/inventory/runtime-entities.json +26 -0
  35. package/docs/reference/inventory/runtime-modules.json +1249 -0
  36. package/docs/reference/protocols.md +242 -0
  37. package/docs/reference/runtime-components.md +1098 -0
  38. package/docs/reference/runtime-entities.md +303 -0
  39. package/docs/reference/runtime-modules.md +2010 -0
  40. package/docs/reference/sdk-api.md +4901 -0
  41. package/docs/roadmap.md +79 -0
  42. package/docs/work-amplification.md +124 -0
  43. package/node_modules/event-pubsub/CHANGELOG.md +55 -0
  44. package/node_modules/event-pubsub/MIGRATION.md +70 -0
  45. package/node_modules/event-pubsub/README.md +363 -0
  46. package/node_modules/event-pubsub/SECURITY.md +37 -0
  47. package/node_modules/event-pubsub/index.js +141 -0
  48. package/node_modules/event-pubsub/licence +21 -0
  49. package/node_modules/event-pubsub/package.json +59 -0
  50. package/node_modules/strong-type/README.md +408 -0
  51. package/node_modules/strong-type/assets/strong-type-header.png +0 -0
  52. package/node_modules/strong-type/index.js +1151 -0
  53. package/node_modules/strong-type/licence +21 -0
  54. package/node_modules/strong-type/node.js +125 -0
  55. package/node_modules/strong-type/package.json +61 -0
  56. package/package.json +95 -0
  57. package/runtime/ARCANE_RUNTIME_RELEASE.json +791 -0
  58. package/runtime/arcane/components/app-bar.html +468 -0
  59. package/runtime/arcane/components/assistant-panel.html +715 -0
  60. package/runtime/arcane/components/calculator.html +8 -0
  61. package/runtime/arcane/components/chart.html +655 -0
  62. package/runtime/arcane/components/chat.html +1225 -0
  63. package/runtime/arcane/components/conversation-view.html +13 -0
  64. package/runtime/arcane/components/dashboard-config.html +341 -0
  65. package/runtime/arcane/components/data-maintenance.html +112 -0
  66. package/runtime/arcane/components/data-view.html +92 -0
  67. package/runtime/arcane/components/directory-picker.html +197 -0
  68. package/runtime/arcane/components/document-inspector.html +252 -0
  69. package/runtime/arcane/components/file-drop.html +264 -0
  70. package/runtime/arcane/components/file-inspector.html +293 -0
  71. package/runtime/arcane/components/file-manager.html +1715 -0
  72. package/runtime/arcane/components/header.html +142 -0
  73. package/runtime/arcane/components/integration-settings.html +14 -0
  74. package/runtime/arcane/components/local-ai-status.html +360 -0
  75. package/runtime/arcane/components/markdown-document.html +1048 -0
  76. package/runtime/arcane/components/markdown-editor.html +360 -0
  77. package/runtime/arcane/components/media-embed.html +8 -0
  78. package/runtime/arcane/components/modal.html +402 -0
  79. package/runtime/arcane/components/output-panel.html +259 -0
  80. package/runtime/arcane/components/preferences-form.html +135 -0
  81. package/runtime/arcane/components/record-timeline.html +105 -0
  82. package/runtime/arcane/components/relationship-board.html +116 -0
  83. package/runtime/arcane/components/screen-capture.html +8 -0
  84. package/runtime/arcane/components/source-code-viewer.html +441 -0
  85. package/runtime/arcane/components/source-explanation.html +124 -0
  86. package/runtime/arcane/components/speech.html +365 -0
  87. package/runtime/arcane/components/summary-strip.html +177 -0
  88. package/runtime/arcane/components/table.html +77 -0
  89. package/runtime/arcane/components/task-progress.html +282 -0
  90. package/runtime/arcane/components/terminal-workspace.html +65 -0
  91. package/runtime/arcane/components/theme-editor.html +41 -0
  92. package/runtime/arcane/components/theme-switcher.html +46 -0
  93. package/runtime/arcane/components/unified-inbox.html +20 -0
  94. package/runtime/arcane/components/voice-transcription.html +476 -0
  95. package/runtime/arcane/components/weather-widget.html +8 -0
  96. package/runtime/arcane/components/web-navigator.html +239 -0
  97. package/runtime/arcane/css/communications.css +1 -0
  98. package/runtime/arcane/css/dashboard-config.css +45 -0
  99. package/runtime/arcane/css/document-site.css +981 -0
  100. package/runtime/arcane/css/layout.css +438 -0
  101. package/runtime/arcane/css/primitives.css +321 -0
  102. package/runtime/arcane/css/theme.css +112 -0
  103. package/runtime/arcane/css/utility-workspace.css +1 -0
  104. package/runtime/arcane/entities/ApiModelRecord.js +20 -0
  105. package/runtime/arcane/entities/Calculation.js +13 -0
  106. package/runtime/arcane/entities/Chat.js +581 -0
  107. package/runtime/arcane/entities/CommunicationMessage.js +29 -0
  108. package/runtime/arcane/entities/CommunicationThread.js +21 -0
  109. package/runtime/arcane/entities/Document.js +10 -0
  110. package/runtime/arcane/entities/File.js +143 -0
  111. package/runtime/arcane/entities/Image.js +135 -0
  112. package/runtime/arcane/entities/IntentEnvelope.js +834 -0
  113. package/runtime/arcane/entities/Preference.js +83 -0
  114. package/runtime/arcane/entities/TWiNPolicyDecision.js +1092 -0
  115. package/runtime/arcane/entities/TerminalSession.js +46 -0
  116. package/runtime/arcane/entities/Theme.js +107 -0
  117. package/runtime/arcane/entities/User.js +1046 -0
  118. package/runtime/arcane/entities/Weather.js +23 -0
  119. package/runtime/arcane/img/arcane-os-everywhere.png +0 -0
  120. package/runtime/arcane/img/arrow-left.png +0 -0
  121. package/runtime/arcane/img/arrow-right.png +0 -0
  122. package/runtime/arcane/img/doc.svg +5 -0
  123. package/runtime/arcane/img/folder.svg +4 -0
  124. package/runtime/arcane/img/image.svg +5 -0
  125. package/runtime/arcane/img/refresh.png +0 -0
  126. package/runtime/arcane/img/send.svg +9 -0
  127. package/runtime/arcane/img/trash.svg +5 -0
  128. package/runtime/arcane/img/upload.svg +5 -0
  129. package/runtime/arcane/modules/AI.js +2048 -0
  130. package/runtime/arcane/modules/AIPreferenceRuntime.js +32 -0
  131. package/runtime/arcane/modules/AIPreferenceTuple.js +92 -0
  132. package/runtime/arcane/modules/AIResponseLength.js +42 -0
  133. package/runtime/arcane/modules/AIResponseURLPolicy.js +626 -0
  134. package/runtime/arcane/modules/AnsiText.js +53 -0
  135. package/runtime/arcane/modules/ApiModelDatabase.js +25 -0
  136. package/runtime/arcane/modules/AppDataScope.js +245 -0
  137. package/runtime/arcane/modules/AppearancePreferences.js +28 -0
  138. package/runtime/arcane/modules/ArcaneCommunicationBridge.js +22 -0
  139. package/runtime/arcane/modules/ArcaneNavigationPolicy.js +135 -0
  140. package/runtime/arcane/modules/ArcaneNetworkPolicy.js +255 -0
  141. package/runtime/arcane/modules/AsyncBoundary.js +161 -0
  142. package/runtime/arcane/modules/BrowserTestSuite.js +326 -0
  143. package/runtime/arcane/modules/CalculatorEngine.js +20 -0
  144. package/runtime/arcane/modules/CaseEvidenceIndexer.js +134 -0
  145. package/runtime/arcane/modules/ChartLibrary.js +35 -0
  146. package/runtime/arcane/modules/ChatRecords.js +13 -0
  147. package/runtime/arcane/modules/CommunicationAppController.js +43 -0
  148. package/runtime/arcane/modules/CommunicationHub.js +16 -0
  149. package/runtime/arcane/modules/CommunicationPreferences.js +13 -0
  150. package/runtime/arcane/modules/CommunicationProviderRegistry.js +16 -0
  151. package/runtime/arcane/modules/ComponentContracts.js +586 -0
  152. package/runtime/arcane/modules/ConfiguredAIChatSession.js +288 -0
  153. package/runtime/arcane/modules/ConversationActionItems.js +488 -0
  154. package/runtime/arcane/modules/ConversationClosingReport.js +274 -0
  155. package/runtime/arcane/modules/ConversationTimebox.js +527 -0
  156. package/runtime/arcane/modules/CoreLocalModelCatalog.js +255 -0
  157. package/runtime/arcane/modules/DBLS.js +171 -0
  158. package/runtime/arcane/modules/DBOPFS.js +1154 -0
  159. package/runtime/arcane/modules/DBOPFSWorker.js +116 -0
  160. package/runtime/arcane/modules/DataMaintenance.js +91 -0
  161. package/runtime/arcane/modules/DevelopmentWorkspace.js +74 -0
  162. package/runtime/arcane/modules/DirectoryPicker.js +109 -0
  163. package/runtime/arcane/modules/DocumentNavigation.js +223 -0
  164. package/runtime/arcane/modules/Errors.js +1025 -0
  165. package/runtime/arcane/modules/GifEncoder.js +29 -0
  166. package/runtime/arcane/modules/HTMLImport.js +114 -0
  167. package/runtime/arcane/modules/InMemoryCommunicationProvider.js +14 -0
  168. package/runtime/arcane/modules/IsolatedModelQuestionRunner.js +275 -0
  169. package/runtime/arcane/modules/LocalAIReadiness.js +870 -0
  170. package/runtime/arcane/modules/LocalAIReadinessController.js +156 -0
  171. package/runtime/arcane/modules/MD.js +111 -0
  172. package/runtime/arcane/modules/Mail.js +352 -0
  173. package/runtime/arcane/modules/MailTransport.mjs +180 -0
  174. package/runtime/arcane/modules/Marked.min.js +71 -0
  175. package/runtime/arcane/modules/MemoryRecords.js +44 -0
  176. package/runtime/arcane/modules/MessageAdvisory.js +38 -0
  177. package/runtime/arcane/modules/ModelDefinition.js +189 -0
  178. package/runtime/arcane/modules/Ollama.js +74 -0
  179. package/runtime/arcane/modules/OllamaModelIdentifier.js +21 -0
  180. package/runtime/arcane/modules/OllamaSettings.js +24 -0
  181. package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +14 -0
  182. package/runtime/arcane/modules/PreferenceStore.js +109 -0
  183. package/runtime/arcane/modules/QRCode.min.js +1 -0
  184. package/runtime/arcane/modules/Questionnaire.js +61 -0
  185. package/runtime/arcane/modules/RecordLinkIndex.js +24 -0
  186. package/runtime/arcane/modules/RecordPassageIndex.js +222 -0
  187. package/runtime/arcane/modules/RecordReviewStore.js +94 -0
  188. package/runtime/arcane/modules/RevocableProjectionLedger.js +1623 -0
  189. package/runtime/arcane/modules/RiskSignalAnalyzer.js +37 -0
  190. package/runtime/arcane/modules/ScamRiskPolicy.js +62 -0
  191. package/runtime/arcane/modules/ScopedOPFSCache.js +183 -0
  192. package/runtime/arcane/modules/ScreenCapture.js +20 -0
  193. package/runtime/arcane/modules/SpeechPlayback.js +581 -0
  194. package/runtime/arcane/modules/StaticDocumentCatalog.js +1248 -0
  195. package/runtime/arcane/modules/SystemAppearance.js +20 -0
  196. package/runtime/arcane/modules/SystemPlatformPresentation.js +51 -0
  197. package/runtime/arcane/modules/SystemToolRegistry.js +28 -0
  198. package/runtime/arcane/modules/TerminalClient.js +52 -0
  199. package/runtime/arcane/modules/TerminalCommandRegistry.js +50 -0
  200. package/runtime/arcane/modules/ThemeBootstrap.js +26 -0
  201. package/runtime/arcane/modules/ThemeManager.js +131 -0
  202. package/runtime/arcane/modules/TimeGuard.js +149 -0
  203. package/runtime/arcane/modules/ToolCallRouter.js +83 -0
  204. package/runtime/arcane/modules/WaitForComponent.js +102 -0
  205. package/runtime/arcane/modules/YouTubeMedia.js +16 -0
  206. package/runtime/arcane/modules/uPlot.LICENSE.txt +21 -0
  207. package/runtime/arcane/modules/uPlot.iife.min.js +2 -0
  208. package/runtime/arcane/modules/uPlot.min.css +1 -0
  209. package/runtime/arcane/security/arcane-network-policy.json +6 -0
  210. package/runtime/strong-type/index.js +352 -0
  211. package/runtime/strong-type/licence +21 -0
  212. package/runtime/strong-type/package.json +45 -0
  213. package/schemas/arcane-app-bundle.schema.json +125 -0
  214. package/schemas/arcane-app.schema.json +329 -0
  215. package/schemas/arcane-lock.schema.json +86 -0
  216. package/schemas/arcane-package.schema.json +224 -0
  217. package/schemas/cli-event.schema.json +122 -0
  218. package/schemas/event-stack.schema.json +152 -0
  219. package/schemas/native-build-plan.schema.json +176 -0
  220. package/schemas/target-adapter.schema.json +117 -0
  221. package/src/app-descriptor.mjs +500 -0
  222. package/src/cli/main.mjs +561 -0
  223. package/src/constants.mjs +31 -0
  224. package/src/dev-server.mjs +718 -0
  225. package/src/doctor.mjs +315 -0
  226. package/src/dom-event-instrumentation.mjs +594 -0
  227. package/src/errors.mjs +75 -0
  228. package/src/event-manager.mjs +1342 -0
  229. package/src/event-queue.mjs +138 -0
  230. package/src/events.mjs +219 -0
  231. package/src/index.mjs +177 -0
  232. package/src/integrated-provider-loader.mjs +432 -0
  233. package/src/native-plan.mjs +698 -0
  234. package/src/native-provider-loader.mjs +1126 -0
  235. package/src/packager/core.mjs +2691 -0
  236. package/src/process.mjs +353 -0
  237. package/src/release-bundle.mjs +2523 -0
  238. package/src/repository.mjs +90 -0
  239. package/src/runtime.mjs +452 -0
  240. package/src/scaffold.mjs +380 -0
  241. package/src/targets/index.mjs +436 -0
  242. package/src/templates/assets/app-icon.png +0 -0
  243. package/src/templates/workspace-template.mjs +388 -0
  244. package/src/testing-loader.mjs +9 -0
  245. package/src/testing.mjs +427 -0
  246. package/src/toolchain.mjs +1335 -0
  247. package/src/update-check.mjs +307 -0
  248. package/src/workspace.mjs +449 -0
@@ -0,0 +1,116 @@
1
+ self.addEventListener(
2
+ 'message',
3
+ async function accessFile(event){
4
+ const port=event.ports[0];
5
+ const operation=event.data.operation||'write';
6
+ let response={success:true};
7
+ let accessHandle;
8
+
9
+ try{
10
+ try{
11
+ const root=await navigator.storage.getDirectory();
12
+ const applicationId=String(event.data.applicationId||'');
13
+
14
+ if(applicationId.length>64
15
+ ||!/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/.test(applicationId)){
16
+ throw new DOMException(
17
+ 'Arcane rejected an invalid application data scope.',
18
+ 'SecurityError'
19
+ );
20
+ }
21
+
22
+ const applications=await root.getDirectoryHandle(
23
+ 'apps',
24
+ {create:operation==='write'}
25
+ );
26
+ const application=await applications.getDirectoryHandle(
27
+ applicationId,
28
+ {create:operation==='write'}
29
+ );
30
+ const directory=await application.getDirectoryHandle(
31
+ event.data.directoryName,
32
+ {create:operation==='write'}
33
+ );
34
+ const file=await directory.getFileHandle(
35
+ event.data.fileName,
36
+ {create:operation==='write'}
37
+ );
38
+
39
+ if(typeof file.createSyncAccessHandle!=='function'){
40
+ throw new DOMException(
41
+ 'OPFS file access is not supported in this browser.',
42
+ 'NotSupportedError'
43
+ );
44
+ }
45
+
46
+ accessHandle=await file.createSyncAccessHandle();
47
+
48
+ if(operation==='read'){
49
+ const fileData=new Uint8Array(accessHandle.getSize());
50
+ let bytesRead=0;
51
+
52
+ while(bytesRead<fileData.byteLength){
53
+ const read=accessHandle.read(
54
+ fileData.subarray(bytesRead),
55
+ {at:bytesRead}
56
+ );
57
+
58
+ if(read<1){
59
+ throw new Error('OPFS read did not make progress.');
60
+ }
61
+
62
+ bytesRead+=read;
63
+ }
64
+
65
+ response={
66
+ success:true,
67
+ fileData:fileData.buffer
68
+ };
69
+ }else{
70
+ const fileData=new Uint8Array(event.data.fileData);
71
+ const writePosition=event.data.append ? accessHandle.getSize() : 0;
72
+
73
+ if(!event.data.append){
74
+ accessHandle.truncate(0);
75
+ }
76
+
77
+ let bytesWritten=0;
78
+
79
+ while(bytesWritten<fileData.byteLength){
80
+ const written=accessHandle.write(
81
+ fileData.subarray(bytesWritten),
82
+ {at:writePosition+bytesWritten}
83
+ );
84
+
85
+ if(written<1){
86
+ throw new Error('OPFS write did not make progress.');
87
+ }
88
+
89
+ bytesWritten+=written;
90
+ }
91
+
92
+ accessHandle.flush();
93
+ }
94
+ }finally{
95
+ if(accessHandle){
96
+ accessHandle.close();
97
+ }
98
+ }
99
+ }catch(error){
100
+ response={
101
+ error:{
102
+ name:error.name||'Error',
103
+ message:error.message||String(error)
104
+ }
105
+ };
106
+ }
107
+
108
+ if(response.fileData){
109
+ port.postMessage(response,[response.fileData]);
110
+ }else{
111
+ port.postMessage(response);
112
+ }
113
+
114
+ port.close();
115
+ }
116
+ );
@@ -0,0 +1,91 @@
1
+ import './DBOPFS.js';
2
+ import {hasUserEntry} from './ChatRecords.js';
3
+ import {hasMemoryContent} from './MemoryRecords.js';
4
+
5
+ async function clearEmptyChatsAndMemories(){
6
+ await waitForDBOPFS();
7
+
8
+ const chatFileNames=await dbopfs.getAllKeys('chats');
9
+ const emptyChats=[];
10
+
11
+ for(let i=0;i<chatFileNames.length;i++){
12
+ const chat=await dbopfs.get(
13
+ 'chats',
14
+ chatFileNames[i],
15
+ true
16
+ );
17
+
18
+ if(!hasUserEntry(chat)){
19
+ emptyChats.push(chatFileNames[i]);
20
+ }
21
+ }
22
+
23
+ const memoryFileNames=await dbopfs.getAllKeys('memories');
24
+ const associatedMemories=new Set(
25
+ emptyChats.map(chatFileName=>`memory-${chatFileName}`)
26
+ );
27
+ const emptyMemories=new Set();
28
+
29
+ for(let i=0;i<memoryFileNames.length;i++){
30
+ const memory=await dbopfs.get(
31
+ 'memories',
32
+ memoryFileNames[i],
33
+ true
34
+ );
35
+
36
+ if(
37
+ associatedMemories.has(memoryFileNames[i])
38
+ || !hasMemoryContent(memory)
39
+ ){
40
+ emptyMemories.add(memoryFileNames[i]);
41
+ }
42
+ }
43
+
44
+ const chatResults=await dbopfs.deleteMany('chats',emptyChats);
45
+ const memoryResults=await dbopfs.deleteMany(
46
+ 'memories',
47
+ Array.from(emptyMemories)
48
+ );
49
+ const deletedChats=chatResults.filter(
50
+ result=>result.status==='fulfilled'
51
+ ).length;
52
+ const deletedMemories=memoryResults.filter(
53
+ result=>result.status==='fulfilled'
54
+ ).length;
55
+
56
+ return {
57
+ checkedChats:chatFileNames.length,
58
+ checkedMemories:memoryFileNames.length,
59
+ deletedChats:deletedChats,
60
+ deletedMemories:deletedMemories,
61
+ failed:(emptyChats.length-deletedChats)
62
+ +(emptyMemories.size-deletedMemories)
63
+ };
64
+ }
65
+
66
+ function waitForDBOPFS(){
67
+ if(window.dbopfs?.ready){
68
+ return Promise.resolve(window.dbopfs);
69
+ }
70
+
71
+ return new Promise(
72
+ function waitForDBOPFSPromise(resolve){
73
+ function ready(){
74
+ window.removeEventListener('dbopfs-ready',ready);
75
+ resolve(window.dbopfs);
76
+ }
77
+
78
+ window.addEventListener('dbopfs-ready',ready);
79
+
80
+ if(window.dbopfs?.ready){
81
+ ready();
82
+ }
83
+ }
84
+ );
85
+ }
86
+
87
+ export {
88
+ clearEmptyChatsAndMemories,
89
+ hasMemoryContent,
90
+ hasUserEntry
91
+ };
@@ -0,0 +1,74 @@
1
+ const ROOT_MAX_LENGTH=4096;
2
+ const QUERY_MAX_LENGTH=4096;
3
+ const TASK_ID_PATTERN=/^[a-z][a-z0-9-]{0,63}$/;
4
+
5
+ function workspaceRoot(value){
6
+ const root=String(value??'').trim();
7
+ if(!root||root.length>ROOT_MAX_LENGTH||/[\u0000-\u001f]/.test(root)){
8
+ throw new TypeError('Choose one existing development workspace directory.');
9
+ }
10
+ return root;
11
+ }
12
+
13
+ function contextQuery(value){
14
+ const query=String(value??'').trim();
15
+ if(query.length>QUERY_MAX_LENGTH||/[\u0000-\u0008\u000b\u000c\u000e-\u001f]/.test(query)){
16
+ throw new TypeError('Development context queries must be bounded plain text.');
17
+ }
18
+ return query;
19
+ }
20
+
21
+ function setupTaskId(value){
22
+ const taskId=String(value??'').trim().toLowerCase();
23
+ if(!TASK_ID_PATTERN.test(taskId)){
24
+ throw new TypeError('Choose a registered development setup task.');
25
+ }
26
+ return taskId;
27
+ }
28
+
29
+ /**
30
+ * Provider-neutral client for a native development-workspace capability.
31
+ *
32
+ * The provider owns filesystem authorization, canonical-path validation,
33
+ * context filtering, the setup-task allowlist, and an optional fixed Node.js
34
+ * prerequisite installer. This module never accepts an arbitrary command and
35
+ * never discovers workspaces on the user's machine.
36
+ */
37
+ export default class DevelopmentWorkspace{
38
+ constructor(api=globalThis.Arcane?.development){
39
+ this.api=api||null;
40
+ }
41
+
42
+ get available(){
43
+ return Boolean(this.api?.inspect&&this.api?.context&&this.api?.setup);
44
+ }
45
+
46
+ get nodeInstallerAvailable(){
47
+ return Boolean(this.api?.installNode);
48
+ }
49
+
50
+ require(method){
51
+ if(!this.available||typeof this.api?.[method]!=='function'){
52
+ throw new Error('The Arcane development-workspace capability is unavailable. Open this application through an installed Arcane OS developer build.');
53
+ }
54
+ return this.api[method].bind(this.api);
55
+ }
56
+
57
+ inspect(root){
58
+ return this.require('inspect')(workspaceRoot(root));
59
+ }
60
+
61
+ context(root,query=''){
62
+ return this.require('context')(workspaceRoot(root),contextQuery(query));
63
+ }
64
+
65
+ setup(root,taskId){
66
+ return this.require('setup')(workspaceRoot(root),setupTaskId(taskId));
67
+ }
68
+
69
+ installNode(){
70
+ return this.require('installNode')();
71
+ }
72
+ }
73
+
74
+ export {contextQuery,setupTaskId,workspaceRoot};
@@ -0,0 +1,109 @@
1
+ const TITLE_MAX_LENGTH=160;
2
+ const PATH_MAX_LENGTH=4096;
3
+ const CONTROL_CHARACTERS=/[\u0000-\u001f\u007f]/;
4
+
5
+ function isPlainRecord(value){
6
+ return Boolean(value)
7
+ &&typeof value==='object'
8
+ &&!Array.isArray(value)
9
+ &&Object.getPrototypeOf(value)===Object.prototype;
10
+ }
11
+
12
+ function coded(error,code){
13
+ error.code=code;
14
+ return error;
15
+ }
16
+
17
+ function optionalText(value,label,maximum){
18
+ if(value===undefined||value===null||value==='') return null;
19
+ if(typeof value!=='string') throw new TypeError(`${label} must be a string when provided.`);
20
+ const normalized=value.trim();
21
+ if(!normalized) return null;
22
+ if(normalized.length>maximum) throw new RangeError(`${label} exceeds ${maximum} characters.`);
23
+ if(CONTROL_CHARACTERS.test(normalized)) throw new TypeError(`${label} cannot contain control characters.`);
24
+ return normalized;
25
+ }
26
+
27
+ function normalizeDirectoryPickerOptions(input={}){
28
+ if(!isPlainRecord(input)) throw new TypeError('Directory picker options must be a plain object.');
29
+ const allowed=new Set(['initialPath','title']);
30
+ const unsupported=Object.keys(input).find(key=>!allowed.has(key));
31
+ if(unsupported) throw new TypeError(`Unsupported directory picker option: ${unsupported}`);
32
+
33
+ const title=optionalText(input.title,'title',TITLE_MAX_LENGTH);
34
+ const initialPath=optionalText(input.initialPath,'initialPath',PATH_MAX_LENGTH);
35
+ return Object.freeze({
36
+ ...(title?{title}:{}),
37
+ ...(initialPath?{initialPath}:{}),
38
+ });
39
+ }
40
+
41
+ function normalizeDirectorySelection(input){
42
+ const keys=isPlainRecord(input)?Object.keys(input):[];
43
+ if(
44
+ !isPlainRecord(input)
45
+ ||keys.length!==2
46
+ ||!keys.includes('cancelled')
47
+ ||!keys.includes('path')
48
+ ||typeof input.cancelled!=='boolean'
49
+ ){
50
+ throw coded(
51
+ new TypeError('The directory picker provider returned an invalid result.'),
52
+ 'DIRECTORY_PICKER_INVALID_RESULT',
53
+ );
54
+ }
55
+ if(input.cancelled){
56
+ if(input.path!==null){
57
+ throw coded(
58
+ new TypeError('A canceled directory selection must return a null path.'),
59
+ 'DIRECTORY_PICKER_INVALID_RESULT',
60
+ );
61
+ }
62
+ return Object.freeze({cancelled:true,path:null});
63
+ }
64
+ let path;
65
+ try{
66
+ path=optionalText(input.path,'The selected directory path',PATH_MAX_LENGTH);
67
+ }catch(error){
68
+ throw coded(error,'DIRECTORY_PICKER_INVALID_RESULT');
69
+ }
70
+ if(!path){
71
+ throw coded(
72
+ new TypeError('The directory picker provider did not return a selected path.'),
73
+ 'DIRECTORY_PICKER_INVALID_RESULT',
74
+ );
75
+ }
76
+ return Object.freeze({cancelled:false,path});
77
+ }
78
+
79
+ /**
80
+ * Opens a provider-owned operating-system directory selector.
81
+ *
82
+ * This wrapper does not enumerate directories, persist a selected path, or use
83
+ * a browser file picker. The injected provider must expose
84
+ * `selectDirectory(options)` and return `{cancelled, path}`.
85
+ */
86
+ export default class DirectoryPicker{
87
+ constructor(provider=globalThis.Arcane?.filesystem){
88
+ this.provider=provider||null;
89
+ }
90
+
91
+ get available(){
92
+ return typeof this.provider?.selectDirectory==='function';
93
+ }
94
+
95
+ async select(options={}){
96
+ if(!this.available){
97
+ throw coded(
98
+ new Error('The Arcane directory selector is unavailable. Open this application through an installed Arcane OS build.'),
99
+ 'DIRECTORY_PICKER_UNAVAILABLE',
100
+ );
101
+ }
102
+ const normalized=normalizeDirectoryPickerOptions(options);
103
+ return normalizeDirectorySelection(
104
+ await this.provider.selectDirectory({...normalized}),
105
+ );
106
+ }
107
+ }
108
+
109
+ export {normalizeDirectoryPickerOptions,normalizeDirectorySelection};
@@ -0,0 +1,223 @@
1
+ const documentNavigationBindings=new WeakMap();
2
+ const documentNavigationForms=new WeakSet();
3
+
4
+ const navigationSelector='[data-document-navigation]';
5
+ const filterSelector='[data-document-navigation-filter]';
6
+ const itemSelector='[data-document-navigation-item]';
7
+ const groupSelector='[data-document-navigation-group]';
8
+ const clearSelector='[data-document-navigation-clear]';
9
+ const statusSelector='[data-document-navigation-status]';
10
+ const currentPageSelector='[aria-current="page"]';
11
+
12
+ function normalizeNavigationSearch(value){
13
+ return String(value??'').trim().toLowerCase();
14
+ }
15
+
16
+ function navigationItemMatches(item,query){
17
+ if(query==='')return true;
18
+ const searchValue=normalizeNavigationSearch(item.getAttribute('data-navigation-search'));
19
+ return searchValue.includes(query);
20
+ }
21
+
22
+ function navigationGroupHasVisibleItem(group){
23
+ for(const item of group.querySelectorAll(itemSelector)){
24
+ if(!item.hidden)return true;
25
+ }
26
+ return false;
27
+ }
28
+
29
+ function documentNavigationStatusText(visibleCount,totalCount){
30
+ const noun=totalCount===1?'item':'items';
31
+ return `Showing ${visibleCount} of ${totalCount} ${noun}.`;
32
+ }
33
+
34
+ function updateDocumentNavigationStatus(status,visibleCount,totalCount){
35
+ if(!status)return;
36
+ status.textContent=documentNavigationStatusText(visibleCount,totalCount);
37
+ }
38
+
39
+ function nearestDocumentNavigationDetails(group){
40
+ return group.closest('details');
41
+ }
42
+
43
+ function documentNavigationDetailsIsOpen(details){
44
+ return typeof details.open==='boolean'
45
+ ?details.open
46
+ :details.hasAttribute('open');
47
+ }
48
+
49
+ function preserveDocumentNavigationDetails(binding){
50
+ binding.state.detailsOpen.clear();
51
+ for(const group of binding.groups){
52
+ const details=nearestDocumentNavigationDetails(group);
53
+ if(details&&!binding.state.detailsOpen.has(details)){
54
+ binding.state.detailsOpen.set(details,documentNavigationDetailsIsOpen(details));
55
+ }
56
+ }
57
+ binding.state.filtering=true;
58
+ }
59
+
60
+ function restoreDocumentNavigationDetails(binding){
61
+ for(const [details,wasOpen] of binding.state.detailsOpen){
62
+ details.open=wasOpen;
63
+ }
64
+ binding.state.detailsOpen.clear();
65
+ binding.state.filtering=false;
66
+ }
67
+
68
+ function applyDocumentNavigationFilter(binding){
69
+ const query=normalizeNavigationSearch(binding.filter.value);
70
+ const filtering=query!=='';
71
+ let visibleCount=0;
72
+
73
+ if(filtering&&!binding.state.filtering){
74
+ preserveDocumentNavigationDetails(binding);
75
+ }
76
+
77
+ for(const item of binding.items){
78
+ const visible=navigationItemMatches(item,query);
79
+ item.hidden=!visible;
80
+ if(visible)visibleCount+=1;
81
+ }
82
+
83
+ for(const group of binding.groups){
84
+ const visible=navigationGroupHasVisibleItem(group);
85
+ group.hidden=!visible;
86
+ if(filtering&&visible){
87
+ const details=nearestDocumentNavigationDetails(group);
88
+ if(details)details.open=true;
89
+ }
90
+ }
91
+
92
+ if(!filtering&&binding.state.filtering){
93
+ restoreDocumentNavigationDetails(binding);
94
+ }
95
+ if(binding.clearButton)binding.clearButton.hidden=!filtering;
96
+ updateDocumentNavigationStatus(binding.status,visibleCount,binding.items.length);
97
+ return visibleCount;
98
+ }
99
+
100
+ function clearDocumentNavigationFilter(binding,{focus=true}={}){
101
+ binding.filter.value='';
102
+ const visibleCount=applyDocumentNavigationFilter(binding);
103
+ if(focus)binding.filter.focus();
104
+ return visibleCount;
105
+ }
106
+
107
+ function preventDocumentNavigationSubmit(event){
108
+ event.preventDefault();
109
+ }
110
+
111
+ function revealCurrentDocumentNavigationItem(binding){
112
+ const currentItem=binding.navigation.querySelector(currentPageSelector);
113
+ if(
114
+ !currentItem
115
+ ||currentItem.hidden
116
+ ||typeof currentItem.getBoundingClientRect!=='function'
117
+ ||typeof binding.navigation.getBoundingClientRect!=='function'
118
+ )return false;
119
+
120
+ const navigationBounds=binding.navigation.getBoundingClientRect();
121
+ const itemBounds=currentItem.getBoundingClientRect();
122
+ if(
123
+ itemBounds.top>=navigationBounds.top
124
+ &&itemBounds.bottom<=navigationBounds.bottom
125
+ )return false;
126
+
127
+ const navigationCenter=(navigationBounds.top+navigationBounds.bottom)/2;
128
+ const itemCenter=(itemBounds.top+itemBounds.bottom)/2;
129
+ binding.navigation.scrollTop+=itemCenter-navigationCenter;
130
+ return true;
131
+ }
132
+
133
+ export function bindDocumentNavigation(navigation){
134
+ if(!navigation?.querySelector)return null;
135
+
136
+ const existingBinding=documentNavigationBindings.get(navigation);
137
+ if(existingBinding){
138
+ applyDocumentNavigationFilter(existingBinding);
139
+ return existingBinding;
140
+ }
141
+
142
+ const filter=navigation.querySelector(filterSelector);
143
+ if(!filter)return null;
144
+
145
+ const binding=Object.freeze({
146
+ clearButton:navigation.querySelector(clearSelector),
147
+ filter,
148
+ groups:Array.from(navigation.querySelectorAll(groupSelector)),
149
+ items:Array.from(navigation.querySelectorAll(itemSelector)),
150
+ navigation,
151
+ state:{detailsOpen:new Map(),filtering:false},
152
+ status:navigation.querySelector(statusSelector)
153
+ });
154
+
155
+ function handleDocumentNavigationInput(){
156
+ applyDocumentNavigationFilter(binding);
157
+ }
158
+
159
+ function handleDocumentNavigationKeydown(event){
160
+ if(event.key!=='Escape')return;
161
+ event.preventDefault();
162
+ clearDocumentNavigationFilter(binding);
163
+ }
164
+
165
+ function handleDocumentNavigationClear(event){
166
+ event.preventDefault();
167
+ clearDocumentNavigationFilter(binding);
168
+ }
169
+
170
+ filter.addEventListener('input',handleDocumentNavigationInput);
171
+ filter.addEventListener('keydown',handleDocumentNavigationKeydown);
172
+ binding.clearButton?.addEventListener('click',handleDocumentNavigationClear);
173
+
174
+ const form=filter.closest('form');
175
+ if(form&&!documentNavigationForms.has(form)){
176
+ form.addEventListener('submit',preventDocumentNavigationSubmit);
177
+ documentNavigationForms.add(form);
178
+ }
179
+
180
+ documentNavigationBindings.set(navigation,binding);
181
+ applyDocumentNavigationFilter(binding);
182
+ revealCurrentDocumentNavigationItem(binding);
183
+ return binding;
184
+ }
185
+
186
+ export function initializeDocumentNavigation(root=globalThis.document){
187
+ if(!root?.querySelectorAll)return [];
188
+
189
+ const navigations=[];
190
+ if(root.matches?.(navigationSelector))navigations.push(root);
191
+ for(const navigation of root.querySelectorAll(navigationSelector)){
192
+ navigations.push(navigation);
193
+ }
194
+
195
+ const bindings=[];
196
+ for(const navigation of navigations){
197
+ const binding=bindDocumentNavigation(navigation);
198
+ if(binding)bindings.push(binding);
199
+ }
200
+ return bindings;
201
+ }
202
+
203
+ function autoInitializeDocumentNavigation(){
204
+ initializeDocumentNavigation(globalThis.document);
205
+ }
206
+
207
+ if(typeof globalThis.document!=='undefined'){
208
+ if(globalThis.document.readyState==='loading'){
209
+ globalThis.document.addEventListener(
210
+ 'DOMContentLoaded',
211
+ autoInitializeDocumentNavigation,
212
+ {once:true}
213
+ );
214
+ }else{
215
+ autoInitializeDocumentNavigation();
216
+ }
217
+ }
218
+
219
+ export {
220
+ applyDocumentNavigationFilter,
221
+ clearDocumentNavigationFilter,
222
+ revealCurrentDocumentNavigationItem
223
+ };