arcane-os 0.62.0 → 0.64.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 (44) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +1 -1
  3. package/browser-runtime/core/client.mjs +7 -2
  4. package/docs/reference/README.md +7 -0
  5. package/docs/reference/cli.md +5 -1
  6. package/docs/reference/core-client.md +11 -2
  7. package/docs/reference/core-native-packaging.md +57 -8
  8. package/docs/reference/core-repositories.md +149 -0
  9. package/docs/reference/inventory/package-api.json +163 -9
  10. package/docs/reference/local-ai.md +51 -7
  11. package/docs/reference/local-image-flux.md +154 -0
  12. package/docs/reference/local-image-generation.md +12 -0
  13. package/docs/reference/local-image-sdxl.md +139 -0
  14. package/docs/reference/local-whisper.md +219 -0
  15. package/docs/reference/native-speech.md +163 -0
  16. package/docs/reference/runtime-modules.md +16 -2
  17. package/docs/reference/sdk-api.md +335 -6
  18. package/package.json +5 -1
  19. package/runtime/arcane/components/speech.html +15 -3
  20. package/runtime/arcane/components/voice-transcription.html +19 -3
  21. package/runtime/arcane/modules/AI.js +250 -27
  22. package/runtime/arcane/modules/AIProviderRuntime.js +20 -3
  23. package/runtime/arcane/modules/AIRuntimeState.js +15 -2
  24. package/runtime/arcane/modules/ComponentContracts.js +3 -0
  25. package/schemas/arcane-app.schema.json +53 -2
  26. package/schemas/arcane-package.schema.json +53 -2
  27. package/src/core/repositories.mjs +210 -0
  28. package/src/core/services/speech.mjs +361 -0
  29. package/src/local-ai/config.mjs +9 -3
  30. package/src/local-ai/dev.mjs +32 -2
  31. package/src/local-ai/image-library.mjs +9 -0
  32. package/src/local-ai/image-models/flux.mjs +31 -0
  33. package/src/local-ai/image-models/sdxl.mjs +18 -0
  34. package/src/local-ai/install.mjs +26 -7
  35. package/src/local-ai/whisper/build.mjs +48 -0
  36. package/src/local-ai/whisper/config.mjs +61 -0
  37. package/src/local-ai/whisper/index.mjs +515 -0
  38. package/src/local-ai/whisper/install.mjs +274 -0
  39. package/src/local-ai/whisper/native/CMakeLists.txt +98 -0
  40. package/src/local-ai/whisper/native/NOTICE +18 -0
  41. package/src/local-ai/whisper/native/README.md +104 -0
  42. package/src/local-ai/whisper/native/main.cpp +489 -0
  43. package/src/native/portable-layout.mjs +37 -7
  44. package/src/native/windows-provider.mjs +39 -15
@@ -41,13 +41,30 @@ function portableRequest(target) {
41
41
  return {...target, target: 'portable', format: 'portable'};
42
42
  }
43
43
 
44
+ function applicationExecutable(application) {
45
+ const name = is.string(application.displayName) && application.displayName
46
+ ? application.displayName : application.id;
47
+ // Derive a Windows filename without changing the application's name.
48
+ // These substitutions belong only to Windows' reserved filename syntax.
49
+ const filename = `${name}.exe`.replace(/[<>:"/\\|?*\u0000-\u001f]/gu, '_');
50
+ return /^(?:con|prn|aux|nul|com[1-9¹²³]|lpt[1-9¹²³])(?:\.|$)/iu.test(filename)
51
+ ? `_${filename}` : filename;
52
+ }
53
+
54
+ function hostAssetPath(relative, executable) {
55
+ if (relative === 'Arcane.exe') return executable;
56
+ if (relative === 'Arcane.exe.config') return `${executable}.config`;
57
+ return relative;
58
+ }
59
+
44
60
  async function emit(onEvent, event) {
45
61
  if (is.function(onEvent)) await onEvent(event);
46
62
  }
47
63
 
48
- async function hostFilesMissing(directory) {
64
+ async function hostFilesMissing(directory, executable = 'Arcane.exe') {
49
65
  const missing = [];
50
- for (const relative of HOST_FILES) {
66
+ for (const source of HOST_FILES) {
67
+ const relative = hostAssetPath(source, executable);
51
68
  try {
52
69
  if (!(await stat(path.join(directory, relative))).isFile()) missing.push(relative);
53
70
  } catch (error) {
@@ -58,9 +75,9 @@ async function hostFilesMissing(directory) {
58
75
  return missing;
59
76
  }
60
77
 
61
- async function requireHostDirectory(directory) {
78
+ async function requireHostDirectory(directory, executable = 'Arcane.exe') {
62
79
  const resolved = await realpath(directory);
63
- const missing = await hostFilesMissing(resolved);
80
+ const missing = await hostFilesMissing(resolved, executable);
64
81
  if (missing.length) {
65
82
  throw new ArcaneError(ERROR_CODES.prerequisiteMissing, 'The selected Windows host assets are incomplete.', {
66
83
  details: {hostDirectory: resolved, missing}
@@ -126,7 +143,7 @@ function contains(root, candidate) {
126
143
  return relative === '' || (!path.isAbsolute(relative) && relative !== '..' && !relative.startsWith(`..${path.sep}`));
127
144
  }
128
145
 
129
- async function copyHostAssets(source, root, files, signal, relative = '', ancestors = new Set()) {
146
+ async function copyHostAssets(source, root, files, signal, executable, relative = '', ancestors = new Set()) {
130
147
  throwIfAborted(signal);
131
148
  const canonical = await realpath(source);
132
149
  const output = await realpath(root);
@@ -139,11 +156,12 @@ async function copyHostAssets(source, root, files, signal, relative = '', ancest
139
156
  for (const entry of await readdir(source, {withFileTypes: true})) {
140
157
  throwIfAborted(signal);
141
158
  const selected = path.join(source, entry.name);
142
- const destination = relative ? `${relative}/${entry.name}` : entry.name;
159
+ const sourceRelative = relative ? `${relative}/${entry.name}` : entry.name;
160
+ const destination = hostAssetPath(sourceRelative, executable);
143
161
  const information = await stat(selected);
144
162
  if (information.isDirectory()) {
145
163
  await mkdir(path.join(root, destination), {recursive: true});
146
- await copyHostAssets(selected, root, files, signal, destination, parents);
164
+ await copyHostAssets(selected, root, files, signal, executable, sourceRelative, parents);
147
165
  } else if (information.isFile()) {
148
166
  await writeOutput(root, destination, await readFile(selected, {signal}), files, signal);
149
167
  } else {
@@ -152,7 +170,7 @@ async function copyHostAssets(source, root, files, signal, relative = '', ancest
152
170
  }
153
171
  }
154
172
 
155
- async function applyApplicationIcon(request, root, files) {
173
+ async function applyApplicationIcon(request, root, files, executableName) {
156
174
  const {appDescriptor, release, signal, onEvent} = request;
157
175
  const icon = appDescriptor.native?.icon;
158
176
  if (!icon) return {};
@@ -168,7 +186,7 @@ async function applyApplicationIcon(request, root, files) {
168
186
  {signal}
169
187
  );
170
188
  throwIfAborted(signal);
171
- const executable = path.join(root, 'Arcane.exe');
189
+ const executable = path.join(root, executableName);
172
190
  let branded;
173
191
  try {
174
192
  const launcher = await readFile(
@@ -268,14 +286,15 @@ export function createWindowsNativeProvider({services, hostDirectory} = {}) {
268
286
  try {
269
287
  const host = selectedHost ?? await releasedHostDirectory(path.resolve(request.outputRoot), artifact.manifest.sdk.version, {signal, onEvent});
270
288
  const files = [...artifact.manifest.files];
271
- await copyHostAssets(host, root, files, signal);
272
- const branding = await applyApplicationIcon(request, root, files);
289
+ const executable = applicationExecutable(request.appDescriptor);
290
+ await copyHostAssets(host, root, files, signal, executable);
291
+ const branding = await applyApplicationIcon(request, root, files, executable);
273
292
  const manifest = {
274
293
  ...artifact.manifest,
275
294
  kind: 'arcane-windows-native',
276
295
  target,
277
296
  host: {
278
- executable: 'Arcane.exe',
297
+ executable,
279
298
  platform: 'windows', architecture: 'x64', runtime: 'webview2',
280
299
  coreExecutable: 'runtime/ArcaneCore.exe',
281
300
  coreLoader: 'runtime/arcane-core-loader.cjs',
@@ -296,7 +315,7 @@ export function createWindowsNativeProvider({services, hostDirectory} = {}) {
296
315
  throwIfAborted(signal);
297
316
  const target = windowsTarget(targetRequest);
298
317
  const result = await portable.verify({artifact, targetRequest: portableRequest(target), signal});
299
- await requireHostDirectory(result.outputRoot);
318
+ await requireHostDirectory(result.outputRoot, result.manifest.host.executable);
300
319
  const verified = {...result, target: 'windows-x64', executable: true};
301
320
  await emit(onEvent, {type: 'native.verified', target: 'windows-x64', outputRoot: result.outputRoot});
302
321
  throwIfAborted(signal);
@@ -308,8 +327,13 @@ export function createWindowsNativeProvider({services, hostDirectory} = {}) {
308
327
  if (process.platform !== 'win32' || process.arch !== 'x64') {
309
328
  throw new ArcaneError(ERROR_CODES.nativeRunUnsupported, 'Running this Windows x64 artifact requires a Windows x64 host.');
310
329
  }
311
- const root = await requireHostDirectory(artifact?.target?.rootDir);
312
- return runProcess(path.join(root, 'Arcane.exe'), ['--close-on-stdin-eof'], {
330
+ const directory = artifact?.target?.rootDir;
331
+ const manifest = JSON.parse(
332
+ await readFile(path.join(directory, MANIFEST), 'utf8')
333
+ );
334
+ const executable = manifest.host.executable;
335
+ const root = await requireHostDirectory(directory, executable);
336
+ return runProcess(path.join(root, executable), ['--close-on-stdin-eof'], {
313
337
  cwd: root, signal, onEvent, cancellationMode: 'close-input'
314
338
  });
315
339
  }