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,380 @@
1
+ import {lstat,mkdir,readFile,readdir,writeFile} from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import {spawn} from 'node:child_process';
4
+ import {loadRuntimeRelease} from './runtime.mjs';
5
+ import {SDK_NAME,SDK_VERSION,workspaceTemplate} from './templates/workspace-template.mjs';
6
+ import {inspectWorkspaceProfile} from './workspace.mjs';
7
+ import {parseSemver} from './packager/core.mjs';
8
+
9
+ const APP_ID_PATTERN=/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
10
+ const DISPLAY_CONTROL_PATTERN=/[\x00-\x1f\x7f]/;
11
+ const LOCAL_TARBALL_PATTERN=/^file:.+\.tgz$/iu;
12
+
13
+ function fail(message,code='ARCANE_WORKSPACE_INVALID'){
14
+ const error=new Error(message);
15
+ error.code=code;
16
+ throw error;
17
+ }
18
+
19
+ function throwIfAborted(signal){
20
+ if(!signal?.aborted)return;
21
+ const error=signal.reason instanceof Error?signal.reason:new Error('Operation cancelled.');
22
+ error.code=error.code||'ARCANE_CANCELLED';
23
+ throw error;
24
+ }
25
+
26
+ async function emit(onEvent,event){
27
+ if(typeof onEvent==='function')await onEvent(Object.freeze(event));
28
+ }
29
+
30
+ function validateInputs(appId,displayName){
31
+ if(typeof appId!=='string'||!APP_ID_PATTERN.test(appId)){
32
+ fail(`Invalid app id: ${String(appId)}. Use lowercase words separated by hyphens.`,'ARCANE_USAGE');
33
+ }
34
+ if(displayName!==undefined&&(typeof displayName!=='string'||displayName!==displayName.trim()
35
+ ||!displayName||displayName.length>160||DISPLAY_CONTROL_PATTERN.test(displayName)
36
+ ||/[<>]/.test(displayName))){
37
+ fail('displayName must be plain trimmed text no longer than 160 characters.','ARCANE_USAGE');
38
+ }
39
+ }
40
+
41
+ function validateScaffoldTarget(target){
42
+ const targets=['browser','portable','windows-x64','linux-x64','linux-arm64','android-arm64'];
43
+ if(!targets.includes(target)){
44
+ fail(`Invalid scaffold target: ${String(target)}. Expected one of ${targets.join(', ')}.`,'ARCANE_USAGE');
45
+ }
46
+ }
47
+
48
+ async function scaffoldIcon(target){
49
+ return target!=='browser'
50
+ ?readFile(new URL('./templates/assets/app-icon.png',import.meta.url))
51
+ :undefined;
52
+ }
53
+
54
+ async function assertCreateTarget(targetPath){
55
+ try{
56
+ const info=await lstat(targetPath);
57
+ if(info.isSymbolicLink()||!info.isDirectory())fail(`Scaffold target must be a real directory: ${targetPath}.`);
58
+ const entries=await readdir(targetPath);
59
+ if(entries.length)fail(`Scaffold target is not empty: ${targetPath}. Refusing to overwrite it.`);
60
+ }catch(error){
61
+ if(error?.code!=='ENOENT')throw error;
62
+ let ancestor=path.dirname(targetPath);
63
+ while(true){
64
+ try{
65
+ const info=await lstat(ancestor);
66
+ if(info.isSymbolicLink()||!info.isDirectory()){
67
+ fail(`Scaffold target has a linked or non-directory ancestor: ${ancestor}.`);
68
+ }
69
+ break;
70
+ }catch(ancestorError){
71
+ if(ancestorError?.code!=='ENOENT')throw ancestorError;
72
+ const parent=path.dirname(ancestor);
73
+ if(parent===ancestor)throw ancestorError;
74
+ ancestor=parent;
75
+ }
76
+ }
77
+ await mkdir(targetPath,{recursive:true});
78
+ const info=await lstat(targetPath);
79
+ if(info.isSymbolicLink()||!info.isDirectory())fail(`Scaffold target must be a real directory: ${targetPath}.`);
80
+ }
81
+ }
82
+
83
+ async function assertInitTarget(workspaceRoot){
84
+ let info;
85
+ try{
86
+ info=await lstat(workspaceRoot);
87
+ }catch(error){
88
+ if(error?.code==='ENOENT')fail(`Workspace does not exist: ${workspaceRoot}.`);
89
+ throw error;
90
+ }
91
+ if(info.isSymbolicLink()||!info.isDirectory())fail(`Workspace must be a real directory: ${workspaceRoot}.`);
92
+ }
93
+
94
+ async function existingWorkspaceProfile(workspaceRoot){
95
+ const configPath=path.join(workspaceRoot,'arcane-packager.json');
96
+ try{
97
+ const info=await lstat(configPath);
98
+ if(info.isSymbolicLink()||!info.isFile()){
99
+ fail('arcane-packager.json must be a real file when present.');
100
+ }
101
+ }catch(error){
102
+ if(error?.code==='ENOENT')return null;
103
+ throw error;
104
+ }
105
+ return inspectWorkspaceProfile(workspaceRoot);
106
+ }
107
+
108
+ async function integratedCoreVersion(workspaceRoot){
109
+ const manifestPath=path.join(
110
+ workspaceRoot,
111
+ 'machine_bundles',
112
+ 'arcane-os-machine-bundle',
113
+ 'package.json'
114
+ );
115
+ let manifest;
116
+ try{
117
+ const info=await lstat(manifestPath);
118
+ if(info.isSymbolicLink()||!info.isFile())fail('The integrated Arcane machine package must be a real file.');
119
+ manifest=JSON.parse(await readFile(manifestPath,'utf8'));
120
+ }catch(error){
121
+ if(error?.code==='ENOENT'){
122
+ fail('The integrated Arcane workspace is missing its machine-bundle package identity.');
123
+ }
124
+ if(error instanceof SyntaxError)fail(`The integrated Arcane machine package is not valid JSON: ${error.message}`);
125
+ throw error;
126
+ }
127
+ if(manifest?.name!=='arcane-os-machine-bundle'){
128
+ fail('The integrated Arcane machine package identity is invalid.');
129
+ }
130
+ parseSemver(manifest.version);
131
+ return manifest.version;
132
+ }
133
+
134
+ async function assertNoLinkedAncestors(workspaceRoot,relative){
135
+ const segments=relative.split('/');
136
+ let current=workspaceRoot;
137
+ for(const segment of segments.slice(0,-1)){
138
+ current=path.join(current,segment);
139
+ try{
140
+ const info=await lstat(current);
141
+ if(info.isSymbolicLink()||!info.isDirectory()){
142
+ fail(`Refusing to traverse a linked or non-directory ancestor: ${path.relative(workspaceRoot,current).replaceAll('\\','/')}.`);
143
+ }
144
+ }catch(error){
145
+ if(error?.code==='ENOENT')return;
146
+ throw error;
147
+ }
148
+ }
149
+ }
150
+
151
+ function isSupportedSdkDeclaration(value){
152
+ return value===SDK_VERSION||(typeof value==='string'&&LOCAL_TARBALL_PATTERN.test(value)
153
+ &&!DISPLAY_CONTROL_PATTERN.test(value));
154
+ }
155
+
156
+ async function prepareExistingPackage(workspaceRoot,files){
157
+ const packagePath=path.join(workspaceRoot,'package.json');
158
+ let source;
159
+ try{source=await readFile(packagePath,'utf8');}
160
+ catch(error){
161
+ if(error?.code==='ENOENT')return Object.freeze({exists:false,updated:false});
162
+ throw error;
163
+ }
164
+ let existing;
165
+ try{existing=JSON.parse(source);}
166
+ catch(error){fail(`Existing package.json is not valid JSON: ${error.message}.`);}
167
+ if(!existing||typeof existing!=='object'||Array.isArray(existing))fail('Existing package.json must be a JSON object.');
168
+ const generated=JSON.parse(files.get('package.json'));
169
+ const conflicts=[];
170
+ if(existing.private!==undefined&&existing.private!==true)conflicts.push('private must be true');
171
+ if(existing.type!==undefined&&existing.type!=='module')conflicts.push('type must be "module"');
172
+ const declared=existing.devDependencies?.[SDK_NAME]??existing.dependencies?.[SDK_NAME];
173
+ if(declared!==undefined&&!isSupportedSdkDeclaration(declared)){
174
+ conflicts.push(`${SDK_NAME} must be ${SDK_VERSION} or a local file: tarball`);
175
+ }
176
+ const appSelectionScripts=new Set(['build','run']);
177
+ for(const [name,command] of Object.entries(generated.scripts)){
178
+ if(appSelectionScripts.has(name))continue;
179
+ if(existing.scripts?.[name]!==undefined&&existing.scripts[name]!==command){
180
+ conflicts.push(`scripts.${name} must be "${command}"`);
181
+ }
182
+ }
183
+ if(conflicts.length){
184
+ fail(`Existing package.json conflicts with Arcane setup; no values were overwritten. Resolve: ${conflicts.join('; ')}.`);
185
+ }
186
+ const merged={
187
+ ...existing,
188
+ private:true,
189
+ type:'module',
190
+ scripts:{...generated.scripts,...(existing.scripts||{})},
191
+ devDependencies:{...(existing.devDependencies||{}),[SDK_NAME]:declared??SDK_VERSION},
192
+ engines:{...generated.engines,...(existing.engines||{})}
193
+ };
194
+ if(existing.dependencies?.[SDK_NAME]!==undefined){
195
+ merged.dependencies={...existing.dependencies};
196
+ delete merged.dependencies[SDK_NAME];
197
+ if(Object.keys(merged.dependencies).length===0)delete merged.dependencies;
198
+ }
199
+ if(JSON.stringify(existing)===JSON.stringify(merged)){
200
+ return Object.freeze({exists:true,updated:false});
201
+ }
202
+ return Object.freeze({
203
+ exists:true,
204
+ updated:true,
205
+ packagePath,
206
+ source,
207
+ content:`${JSON.stringify(merged,null,2)}\n`
208
+ });
209
+ }
210
+
211
+ async function applyPackageMerge(workspaceRoot,plan,{signal,onEvent}){
212
+ if(!plan.updated)return false;
213
+ throwIfAborted(signal);
214
+ await assertNoLinkedAncestors(workspaceRoot,'package.json');
215
+ const packageInfo=await lstat(plan.packagePath);
216
+ if(packageInfo.isSymbolicLink()||!packageInfo.isFile())fail('Existing package.json must be a real file.');
217
+ if(await readFile(plan.packagePath,'utf8')!==plan.source){
218
+ fail('Existing package.json changed after Arcane preflight; no values were overwritten. Retry arcane init.');
219
+ }
220
+ await writeFile(plan.packagePath,plan.content,'utf8');
221
+ await emit(onEvent,{type:'scaffold.file.merged',path:'package.json'});
222
+ return true;
223
+ }
224
+
225
+ async function writeMissingFiles(workspaceRoot,files,{signal,onEvent}){
226
+ const createdFiles=[];
227
+ const skippedFiles=[];
228
+ const entries=[];
229
+ for(const [relative,content] of files){
230
+ throwIfAborted(signal);
231
+ const destination=path.resolve(workspaceRoot,...relative.split('/'));
232
+ const boundary=path.relative(workspaceRoot,destination);
233
+ if(boundary.startsWith('..')||path.isAbsolute(boundary))fail(`Template path escapes the workspace: ${relative}.`);
234
+ await assertNoLinkedAncestors(workspaceRoot,relative);
235
+ let exists=false;
236
+ try{
237
+ const info=await lstat(destination);
238
+ if(info.isSymbolicLink())fail(`Refusing to write through a symbolic link: ${relative}.`);
239
+ if(!info.isFile())fail(`Template file path is occupied by a non-file entry: ${relative}.`);
240
+ exists=true;
241
+ }catch(error){
242
+ if(error?.code!=='ENOENT')throw error;
243
+ }
244
+ entries.push({relative,content,destination,exists});
245
+ }
246
+ for(const {relative,content,destination,exists} of entries){
247
+ throwIfAborted(signal);
248
+ if(exists){
249
+ skippedFiles.push(relative);
250
+ await emit(onEvent,{type:'scaffold.file.skipped',path:relative});
251
+ continue;
252
+ }
253
+ await mkdir(path.dirname(destination),{recursive:true});
254
+ await assertNoLinkedAncestors(workspaceRoot,relative);
255
+ await writeFile(destination,content,{encoding:'utf8',flag:'wx'});
256
+ createdFiles.push(relative);
257
+ await emit(onEvent,{type:'scaffold.file.created',path:relative});
258
+ }
259
+ return {createdFiles,skippedFiles};
260
+ }
261
+
262
+ async function runGitInit(workspaceRoot,signal,onEvent){
263
+ throwIfAborted(signal);
264
+ await emit(onEvent,{type:'scaffold.git.started',command:'git init -b main'});
265
+ return new Promise((resolve,reject)=>{
266
+ throwIfAborted(signal);
267
+ const child=spawn('git',['init','-b','main'],{
268
+ cwd:workspaceRoot,
269
+ shell:false,
270
+ stdio:['ignore','pipe','pipe'],
271
+ windowsHide:true
272
+ });
273
+ let stderr='';
274
+ child.stderr.setEncoding('utf8');
275
+ child.stderr.on('data',chunk=>{stderr+=chunk;});
276
+ const abort=()=>child.kill();
277
+ signal?.addEventListener('abort',abort,{once:true});
278
+ child.once('error',error=>{
279
+ signal?.removeEventListener('abort',abort);
280
+ reject(error);
281
+ });
282
+ child.once('close',code=>{
283
+ signal?.removeEventListener('abort',abort);
284
+ if(signal?.aborted){
285
+ try{throwIfAborted(signal);}catch(error){reject(error);}
286
+ return;
287
+ }
288
+ if(code!==0){
289
+ const error=new Error(`git init failed with exit code ${code}: ${stderr.trim()}`);
290
+ error.code='ARCANE_OPERATION_FAILED';
291
+ reject(error);
292
+ return;
293
+ }
294
+ void emit(onEvent,{type:'scaffold.git.completed'}).then(resolve,reject);
295
+ });
296
+ });
297
+ }
298
+
299
+ export async function createWorkspace({
300
+ targetPath,
301
+ appId,
302
+ displayName,
303
+ target='browser',
304
+ initializeGit=false,
305
+ signal,
306
+ onEvent
307
+ }){
308
+ validateInputs(appId,displayName);
309
+ validateScaffoldTarget(target);
310
+ if(typeof targetPath!=='string'||!targetPath.trim())fail('targetPath is required.','ARCANE_USAGE');
311
+ if(typeof initializeGit!=='boolean')fail('initializeGit must be a boolean.','ARCANE_USAGE');
312
+ throwIfAborted(signal);
313
+ const workspaceRoot=path.resolve(targetPath);
314
+ await emit(onEvent,{type:'scaffold.started',mode:'create',workspaceRoot,appId,target});
315
+ await assertCreateTarget(workspaceRoot);
316
+ const runtimeRelease=await loadRuntimeRelease();
317
+ const template=workspaceTemplate({
318
+ appId,
319
+ displayName,
320
+ runtimeRelease,
321
+ target,
322
+ appIcon:await scaffoldIcon(target)
323
+ });
324
+ const result=await writeMissingFiles(workspaceRoot,template.files,{signal,onEvent});
325
+ if(initializeGit)await runGitInit(workspaceRoot,signal,onEvent);
326
+ const receipt={workspaceRoot,appId,displayName:template.name,target,...result,gitInitialized:Boolean(initializeGit)};
327
+ await emit(onEvent,{type:'scaffold.completed',...receipt});
328
+ return receipt;
329
+ }
330
+
331
+ export async function initWorkspace({
332
+ workspaceRoot=process.cwd(),
333
+ appId,
334
+ displayName,
335
+ target='browser',
336
+ signal,
337
+ onEvent
338
+ }){
339
+ validateInputs(appId,displayName);
340
+ validateScaffoldTarget(target);
341
+ throwIfAborted(signal);
342
+ const resolvedRoot=path.resolve(workspaceRoot);
343
+ await emit(onEvent,{type:'scaffold.started',mode:'init',workspaceRoot:resolvedRoot,appId,target});
344
+ await assertInitTarget(resolvedRoot);
345
+ const profile=await existingWorkspaceProfile(resolvedRoot);
346
+ const workspaceMode=profile?.workspaceMode??'external';
347
+ const template=workspaceMode==='integrated'
348
+ ?workspaceTemplate({
349
+ appId,
350
+ displayName,
351
+ appOnly:true,
352
+ minimumCoreVersion:await integratedCoreVersion(resolvedRoot),
353
+ target,
354
+ appIcon:await scaffoldIcon(target)
355
+ })
356
+ :workspaceTemplate({
357
+ appId,
358
+ displayName,
359
+ runtimeRelease:await loadRuntimeRelease(),
360
+ target,
361
+ appIcon:await scaffoldIcon(target)
362
+ });
363
+ const packagePlan=workspaceMode==='integrated'
364
+ ?Object.freeze({exists:true,updated:false})
365
+ :await prepareExistingPackage(resolvedRoot,template.files);
366
+ const result=await writeMissingFiles(resolvedRoot,template.files,{signal,onEvent});
367
+ const packageUpdated=await applyPackageMerge(resolvedRoot,packagePlan,{signal,onEvent});
368
+ const receipt={
369
+ workspaceRoot:resolvedRoot,
370
+ workspaceMode,
371
+ appId,
372
+ displayName:template.name,
373
+ target,
374
+ ...result,
375
+ packageUpdated,
376
+ gitInitialized:false
377
+ };
378
+ await emit(onEvent,{type:'scaffold.completed',...receipt});
379
+ return receipt;
380
+ }