nextclaw 0.44.2 → 0.45.1

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 (146) hide show
  1. package/dist/cli/app/index.js +5 -3
  2. package/dist/cli/app/index.js.map +1 -1
  3. package/dist/cli/launcher/index.js +1 -1
  4. package/dist/{nextclaw-cli-app-B_ffeXOD.js → nextclaw-cli-app-Bwa2L2Jv.js} +115 -22
  5. package/dist/nextclaw-cli-app-Bwa2L2Jv.js.map +1 -0
  6. package/dist/nextclaw-distribution.utils-Dryg4WzA.js +42 -0
  7. package/dist/nextclaw-distribution.utils-Dryg4WzA.js.map +1 -0
  8. package/package.json +9 -9
  9. package/resources/apps/nextclaw-portable-runtime-lab/README.md +34 -0
  10. package/resources/apps/nextclaw-portable-runtime-lab/assets/icon.svg +12 -0
  11. package/resources/apps/nextclaw-portable-runtime-lab/manifest.json +74 -0
  12. package/resources/apps/nextclaw-portable-runtime-lab/marketplace.json +21 -0
  13. package/resources/apps/nextclaw-portable-runtime-lab/panels/nextclaw-portable-runtime-lab-contacts.panel/assets/app.js +92 -0
  14. package/resources/apps/nextclaw-portable-runtime-lab/panels/nextclaw-portable-runtime-lab-contacts.panel/assets/style.css +43 -0
  15. package/resources/apps/nextclaw-portable-runtime-lab/panels/nextclaw-portable-runtime-lab-contacts.panel/index.html +43 -0
  16. package/resources/apps/nextclaw-portable-runtime-lab/panels/nextclaw-portable-runtime-lab-contacts.panel/panel-app.json +10 -0
  17. package/resources/apps/nextclaw-portable-runtime-lab/panels/nextclaw-portable-runtime-lab-focus.panel/assets/app.js +132 -0
  18. package/resources/apps/nextclaw-portable-runtime-lab/panels/nextclaw-portable-runtime-lab-focus.panel/assets/style.css +39 -0
  19. package/resources/apps/nextclaw-portable-runtime-lab/panels/nextclaw-portable-runtime-lab-focus.panel/index.html +40 -0
  20. package/resources/apps/nextclaw-portable-runtime-lab/panels/nextclaw-portable-runtime-lab-focus.panel/panel-app.json +14 -0
  21. package/resources/apps/nextclaw-portable-runtime-lab/panels/nextclaw-portable-runtime-lab-notes.panel/assets/app.js +117 -0
  22. package/resources/apps/nextclaw-portable-runtime-lab/panels/nextclaw-portable-runtime-lab-notes.panel/assets/style.css +41 -0
  23. package/resources/apps/nextclaw-portable-runtime-lab/panels/nextclaw-portable-runtime-lab-notes.panel/index.html +41 -0
  24. package/resources/apps/nextclaw-portable-runtime-lab/panels/nextclaw-portable-runtime-lab-notes.panel/panel-app.json +12 -0
  25. package/resources/apps/nextclaw-portable-runtime-lab/panels/nextclaw-portable-runtime-lab-panel.panel/assets/app.js +266 -0
  26. package/resources/apps/nextclaw-portable-runtime-lab/panels/nextclaw-portable-runtime-lab-panel.panel/assets/style.css +87 -0
  27. package/resources/apps/nextclaw-portable-runtime-lab/panels/nextclaw-portable-runtime-lab-panel.panel/index.html +154 -0
  28. package/resources/apps/nextclaw-portable-runtime-lab/panels/nextclaw-portable-runtime-lab-panel.panel/panel-app.json +29 -0
  29. package/resources/apps/nextclaw-portable-runtime-lab/panels/nextclaw-portable-runtime-lab-todos.panel/assets/app.js +143 -0
  30. package/resources/apps/nextclaw-portable-runtime-lab/panels/nextclaw-portable-runtime-lab-todos.panel/assets/style.css +48 -0
  31. package/resources/apps/nextclaw-portable-runtime-lab/panels/nextclaw-portable-runtime-lab-todos.panel/index.html +45 -0
  32. package/resources/apps/nextclaw-portable-runtime-lab/panels/nextclaw-portable-runtime-lab-todos.panel/panel-app.json +12 -0
  33. package/resources/apps/nextclaw-portable-runtime-lab/service-components/nextclaw-portable-runtime-lab-capabilities/service-app.json +20 -0
  34. package/resources/apps/nextclaw-portable-runtime-lab/service-components/nextclaw-portable-runtime-lab-capabilities/service.wasm +0 -0
  35. package/resources/apps/nextclaw-portable-runtime-lab/service-components/nextclaw-portable-runtime-lab-composition/service-app.json +30 -0
  36. package/resources/apps/nextclaw-portable-runtime-lab/service-components/nextclaw-portable-runtime-lab-composition/service.wasm +0 -0
  37. package/resources/apps/nextclaw-portable-runtime-lab/service-components/nextclaw-portable-runtime-lab-provider/service-app.json +30 -0
  38. package/resources/apps/nextclaw-portable-runtime-lab/service-components/nextclaw-portable-runtime-lab-provider/service.wasm +0 -0
  39. package/resources/apps/nextclaw-portable-runtime-lab/service-components/nextclaw-portable-runtime-lab-resident/service-app.json +31 -0
  40. package/resources/apps/nextclaw-portable-runtime-lab/service-components/nextclaw-portable-runtime-lab-resident/service.wasm +0 -0
  41. package/resources/apps/nextclaw-portable-runtime-lab/service-components/nextclaw-portable-runtime-lab-state/service-app.json +49 -0
  42. package/resources/apps/nextclaw-portable-runtime-lab/service-components/nextclaw-portable-runtime-lab-state/service.wasm +0 -0
  43. package/ui-dist/assets/{api-DNLQPHfQ.js → api-m33O1Kb0.js} +3 -3
  44. package/ui-dist/assets/api-m33O1Kb0.js.gz +0 -0
  45. package/ui-dist/assets/{appearance-settings-page-BlAFMOGV.js → appearance-settings-page-x4Vjnfmz.js} +1 -1
  46. package/ui-dist/assets/appearance-settings-page-x4Vjnfmz.js.gz +0 -0
  47. package/ui-dist/assets/{book-open-BryfPTTa.js → book-open-BHiasMCg.js} +1 -1
  48. package/ui-dist/assets/{channels-list-page-Cfbgubyv.js → channels-list-page-DOxJZeXq.js} +1 -1
  49. package/ui-dist/assets/channels-list-page-DOxJZeXq.js.gz +0 -0
  50. package/ui-dist/assets/{config-split-page-Dd8xG0Jk.js → config-split-page-DBnMEP1o.js} +1 -1
  51. package/ui-dist/assets/config-split-page-DBnMEP1o.js.gz +0 -0
  52. package/ui-dist/assets/{confirm-dialog-CZIWjnJ6.js → confirm-dialog-f5NKLh5I.js} +2 -2
  53. package/ui-dist/assets/confirm-dialog-f5NKLh5I.js.gz +0 -0
  54. package/ui-dist/assets/{createLucideIcon-BxwVe8ZD.js → createLucideIcon-CHALGA42.js} +1 -1
  55. package/ui-dist/assets/{desktop-capabilities-page-C6YxsHeV.js → desktop-capabilities-page-DixayAe6.js} +1 -1
  56. package/ui-dist/assets/desktop-capabilities-page-DixayAe6.js.gz +0 -0
  57. package/ui-dist/assets/{desktop-update-config-BVN4QPJn.js → desktop-update-config-ChhfeOHj.js} +1 -1
  58. package/ui-dist/assets/desktop-update-config-ChhfeOHj.js.gz +0 -0
  59. package/ui-dist/assets/{dist-Cd8Nm3BW.js → dist-_UMsWSY0.js} +1 -1
  60. package/ui-dist/assets/{dist-Cd8Nm3BW.js.gz → dist-_UMsWSY0.js.gz} +0 -0
  61. package/ui-dist/assets/{dist-BRloKr3S.js → dist-sD6V4nHX.js} +1 -1
  62. package/ui-dist/assets/dist-sD6V4nHX.js.gz +0 -0
  63. package/ui-dist/assets/doc-browser-Bq7LJsQY.js +1 -0
  64. package/ui-dist/assets/doc-browser-Bq7LJsQY.js.gz +0 -0
  65. package/ui-dist/assets/doc-browser-BuDgpY2e.js +1 -0
  66. package/ui-dist/assets/{doc-browser-context-3JGufiMA.js → doc-browser-context-Capim3og.js} +1 -1
  67. package/ui-dist/assets/doc-browser-context-Capim3og.js.gz +0 -0
  68. package/ui-dist/assets/{extensions-Dedd2oDI.js → extensions-Bdclr3Wr.js} +1 -1
  69. package/ui-dist/assets/extensions-Bdclr3Wr.js.gz +0 -0
  70. package/ui-dist/assets/{form-actions-FWCEof3V.js → form-actions-CH0kWhEt.js} +1 -1
  71. package/ui-dist/assets/index-BjxnLYE3.js +109 -0
  72. package/ui-dist/assets/index-BjxnLYE3.js.gz +0 -0
  73. package/ui-dist/assets/index-CBmwJjwE.css +1 -0
  74. package/ui-dist/assets/index-CBmwJjwE.css.gz +0 -0
  75. package/ui-dist/assets/loader-circle-CH_0iPLy.js +1 -0
  76. package/ui-dist/assets/{mcp-marketplace-page-e9Rmfz-d.js → mcp-marketplace-page-BUsxLVB5.js} +2 -2
  77. package/ui-dist/assets/mcp-marketplace-page-BUsxLVB5.js.gz +0 -0
  78. package/ui-dist/assets/mcp-marketplace-page-GPRV3fB0.js +1 -0
  79. package/ui-dist/assets/{model-config-page-C173Ssil.js → model-config-page-CIevwqp4.js} +1 -1
  80. package/ui-dist/assets/model-config-page-CIevwqp4.js.gz +0 -0
  81. package/ui-dist/assets/{navigation-link-DTPOvGGL.js → navigation-link-CAUiM3zS.js} +1 -1
  82. package/ui-dist/assets/navigation-link-CAUiM3zS.js.gz +0 -0
  83. package/ui-dist/assets/plus-B3obD55V.js +1 -0
  84. package/ui-dist/assets/{privacy-settings-page-CRserBVF.js → privacy-settings-page-CNKfZsYH.js} +1 -1
  85. package/ui-dist/assets/privacy-settings-page-CNKfZsYH.js.gz +0 -0
  86. package/ui-dist/assets/{providers-config-page-C8jUtnZb.js → providers-config-page-DAah2NIS.js} +1 -1
  87. package/ui-dist/assets/providers-config-page-DAah2NIS.js.gz +0 -0
  88. package/ui-dist/assets/{react-BAzO6LZS.js → react-Co6JiC1n.js} +1 -1
  89. package/ui-dist/assets/react-Co6JiC1n.js.gz +0 -0
  90. package/ui-dist/assets/{refresh-cw-BCj-yjde.js → refresh-cw-BG_LX3SS.js} +1 -1
  91. package/ui-dist/assets/remote-DuscgfvR.js +1 -0
  92. package/ui-dist/assets/{rotate-cw-BPnW4qUl.js → rotate-cw-Cz7I_m7O.js} +1 -1
  93. package/ui-dist/assets/{runtime-config-page-aeQ1Dryz.js → runtime-config-page-Cl1RpmSC.js} +1 -1
  94. package/ui-dist/assets/runtime-config-page-Cl1RpmSC.js.gz +0 -0
  95. package/ui-dist/assets/{save-KT0MumfJ.js → save-BiHnb8ps.js} +1 -1
  96. package/ui-dist/assets/{search-Cv1Hx_nx.js → search-C47TyRg0.js} +1 -1
  97. package/ui-dist/assets/{search-config-page-b283V-fo.js → search-config-page-BAIzd6gN.js} +1 -1
  98. package/ui-dist/assets/search-config-page-BAIzd6gN.js.gz +0 -0
  99. package/ui-dist/assets/{secrets-config-page-ss7x3BOo.js → secrets-config-page-Ddi-n43R.js} +1 -1
  100. package/ui-dist/assets/secrets-config-page-Ddi-n43R.js.gz +0 -0
  101. package/ui-dist/assets/security-config-C8MHnE1r.js +1 -0
  102. package/ui-dist/assets/{select-BVgD4EpG.js → select-BzQKfW1e.js} +2 -2
  103. package/ui-dist/assets/select-BzQKfW1e.js.gz +0 -0
  104. package/ui-dist/assets/skeleton-BXFAE9SE.js +1 -0
  105. package/ui-dist/assets/{tag-chip-DZ4ircUB.js → tag-chip-C3K814HZ.js} +1 -1
  106. package/ui-dist/assets/x-C1r9IXet.js +1 -0
  107. package/ui-dist/index.html +29 -21
  108. package/ui-dist/index.html.gz +0 -0
  109. package/ui-dist/themes/island/island-atmosphere.webp +0 -0
  110. package/dist/nextclaw-cli-app-B_ffeXOD.js.map +0 -1
  111. package/dist/nextclaw-distribution.utils-BneKclXo.js +0 -24
  112. package/dist/nextclaw-distribution.utils-BneKclXo.js.map +0 -1
  113. package/ui-dist/assets/api-DNLQPHfQ.js.gz +0 -0
  114. package/ui-dist/assets/appearance-settings-page-BlAFMOGV.js.gz +0 -0
  115. package/ui-dist/assets/channels-list-page-Cfbgubyv.js.gz +0 -0
  116. package/ui-dist/assets/config-split-page-Dd8xG0Jk.js.gz +0 -0
  117. package/ui-dist/assets/confirm-dialog-CZIWjnJ6.js.gz +0 -0
  118. package/ui-dist/assets/desktop-capabilities-page-C6YxsHeV.js.gz +0 -0
  119. package/ui-dist/assets/desktop-update-config-BVN4QPJn.js.gz +0 -0
  120. package/ui-dist/assets/dist-BRloKr3S.js.gz +0 -0
  121. package/ui-dist/assets/doc-browser-Bzgqmmiu.js +0 -1
  122. package/ui-dist/assets/doc-browser-Bzgqmmiu.js.gz +0 -0
  123. package/ui-dist/assets/doc-browser-D0qI0Z_R.js +0 -1
  124. package/ui-dist/assets/doc-browser-context-3JGufiMA.js.gz +0 -0
  125. package/ui-dist/assets/extensions-Dedd2oDI.js.gz +0 -0
  126. package/ui-dist/assets/index-CXkHcESm.css +0 -1
  127. package/ui-dist/assets/index-CXkHcESm.css.gz +0 -0
  128. package/ui-dist/assets/index-D25tRg1p.js +0 -109
  129. package/ui-dist/assets/index-D25tRg1p.js.gz +0 -0
  130. package/ui-dist/assets/loader-circle-BoyCoFps.js +0 -1
  131. package/ui-dist/assets/mcp-marketplace-page-Cn-9CDh3.js +0 -1
  132. package/ui-dist/assets/mcp-marketplace-page-e9Rmfz-d.js.gz +0 -0
  133. package/ui-dist/assets/model-config-page-C173Ssil.js.gz +0 -0
  134. package/ui-dist/assets/navigation-link-DTPOvGGL.js.gz +0 -0
  135. package/ui-dist/assets/plus-D1R4Vipq.js +0 -1
  136. package/ui-dist/assets/privacy-settings-page-CRserBVF.js.gz +0 -0
  137. package/ui-dist/assets/providers-config-page-C8jUtnZb.js.gz +0 -0
  138. package/ui-dist/assets/react-BAzO6LZS.js.gz +0 -0
  139. package/ui-dist/assets/remote-6BHWP5zi.js +0 -1
  140. package/ui-dist/assets/runtime-config-page-aeQ1Dryz.js.gz +0 -0
  141. package/ui-dist/assets/search-config-page-b283V-fo.js.gz +0 -0
  142. package/ui-dist/assets/secrets-config-page-ss7x3BOo.js.gz +0 -0
  143. package/ui-dist/assets/security-config-HHz62oxH.js +0 -1
  144. package/ui-dist/assets/select-BVgD4EpG.js.gz +0 -0
  145. package/ui-dist/assets/skeleton-ChUmcyys.js +0 -1
  146. package/ui-dist/assets/x-BWDRfVWL.js +0 -1
@@ -0,0 +1,42 @@
1
+ import { accessSync, chmodSync, constants, existsSync, readFileSync, statSync } from "node:fs";
2
+ import { basename, dirname, resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ //#region src/cli/shared/lib/distribution/nextclaw-distribution.utils.ts
5
+ function createNextclawDistribution(importMetaUrl) {
6
+ const entrypoint = fileURLToPath(importMetaUrl);
7
+ const packageRoot = resolve(dirname(entrypoint), "../../..");
8
+ const { version } = JSON.parse(readFileSync(resolve(packageRoot, "package.json"), "utf-8"));
9
+ const portableRunnerExecutable = process.platform === "win32" ? "nextclaw-wasmtime-runner.exe" : "nextclaw-wasmtime-runner";
10
+ return {
11
+ version: typeof version === "string" ? version : "0.0.0",
12
+ appEntrypoint: resolve(packageRoot, "dist/cli/app/index.js"),
13
+ launcherVersion: typeof version === "string" ? version : "0.0.0",
14
+ launcherEntrypoint: resolve(dirname(entrypoint), "../launcher", basename(entrypoint)),
15
+ launchedByLauncher: false,
16
+ templatesDir: resolve(packageRoot, "templates"),
17
+ uiDistDir: resolve(packageRoot, "ui-dist"),
18
+ runtimeUpdatePublicKeyPath: resolve(packageRoot, "resources/update-bundle-public.pem"),
19
+ builtInAppsDirectory: resolve(packageRoot, "resources/apps"),
20
+ portableServiceRunnerPath: resolve(packageRoot, "resources/native", `${process.platform}-${process.arch}`, portableRunnerExecutable)
21
+ };
22
+ }
23
+ function repairPackagedPortableRunnerPermissions(distribution) {
24
+ if (process.platform === "win32") return false;
25
+ const runnerPath = distribution.portableServiceRunnerPath?.trim();
26
+ if (!runnerPath || !existsSync(runnerPath)) return false;
27
+ const runnerStat = statSync(runnerPath);
28
+ if (!runnerStat.isFile()) return false;
29
+ try {
30
+ accessSync(runnerPath, constants.X_OK);
31
+ return false;
32
+ } catch {
33
+ chmodSync(runnerPath, runnerStat.mode & 511 | 73);
34
+ accessSync(runnerPath, constants.X_OK);
35
+ process.stderr.write(`[nextclaw] restored executable permission for bundled Portable Service App runner: ${runnerPath}\n`);
36
+ return true;
37
+ }
38
+ }
39
+ //#endregion
40
+ export { repairPackagedPortableRunnerPermissions as n, createNextclawDistribution as t };
41
+
42
+ //# sourceMappingURL=nextclaw-distribution.utils-Dryg4WzA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nextclaw-distribution.utils-Dryg4WzA.js","names":[],"sources":["../src/cli/shared/lib/distribution/nextclaw-distribution.utils.ts"],"sourcesContent":["import { accessSync, chmodSync, constants, existsSync, readFileSync, statSync } from \"node:fs\";\nimport { basename, dirname, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport type { NextclawDistribution } from \"@nextclaw/service\";\n\nexport function createNextclawDistribution(importMetaUrl: string): NextclawDistribution {\n const entrypoint = fileURLToPath(importMetaUrl);\n const packageRoot = resolve(dirname(entrypoint), \"../../..\");\n const { version } = JSON.parse(readFileSync(resolve(packageRoot, \"package.json\"), \"utf-8\")) as { version?: string };\n const portableRunnerExecutable = process.platform === \"win32\"\n ? \"nextclaw-wasmtime-runner.exe\"\n : \"nextclaw-wasmtime-runner\";\n return {\n version: typeof version === \"string\" ? version : \"0.0.0\",\n appEntrypoint: resolve(packageRoot, \"dist/cli/app/index.js\"),\n launcherVersion: typeof version === \"string\" ? version : \"0.0.0\",\n launcherEntrypoint: resolve(dirname(entrypoint), \"../launcher\", basename(entrypoint)),\n launchedByLauncher: false,\n templatesDir: resolve(packageRoot, \"templates\"),\n uiDistDir: resolve(packageRoot, \"ui-dist\"),\n runtimeUpdatePublicKeyPath: resolve(packageRoot, \"resources/update-bundle-public.pem\"),\n builtInAppsDirectory: resolve(packageRoot, \"resources/apps\"),\n portableServiceRunnerPath: resolve(\n packageRoot,\n \"resources/native\",\n `${process.platform}-${process.arch}`,\n portableRunnerExecutable,\n )\n };\n}\n\nexport function repairPackagedPortableRunnerPermissions(distribution: NextclawDistribution): boolean {\n if (process.platform === \"win32\") return false;\n const runnerPath = distribution.portableServiceRunnerPath?.trim();\n if (!runnerPath || !existsSync(runnerPath)) return false;\n const runnerStat = statSync(runnerPath);\n if (!runnerStat.isFile()) return false;\n try {\n accessSync(runnerPath, constants.X_OK);\n return false;\n } catch {\n chmodSync(runnerPath, (runnerStat.mode & 0o777) | 0o111);\n accessSync(runnerPath, constants.X_OK);\n process.stderr.write(`[nextclaw] restored executable permission for bundled Portable Service App runner: ${runnerPath}\\n`);\n return true;\n }\n}\n"],"mappings":";;;;AAKA,SAAgB,2BAA2B,eAA6C;CACtF,MAAM,aAAa,cAAc,cAAc;CAC/C,MAAM,cAAc,QAAQ,QAAQ,WAAW,EAAE,WAAW;CAC5D,MAAM,EAAE,YAAY,KAAK,MAAM,aAAa,QAAQ,aAAa,eAAe,EAAE,QAAQ,CAAC;CAC3F,MAAM,2BAA2B,QAAQ,aAAa,UAClD,iCACA;AACJ,QAAO;EACL,SAAS,OAAO,YAAY,WAAW,UAAU;EACjD,eAAe,QAAQ,aAAa,wBAAwB;EAC5D,iBAAiB,OAAO,YAAY,WAAW,UAAU;EACzD,oBAAoB,QAAQ,QAAQ,WAAW,EAAE,eAAe,SAAS,WAAW,CAAC;EACrF,oBAAoB;EACpB,cAAc,QAAQ,aAAa,YAAY;EAC/C,WAAW,QAAQ,aAAa,UAAU;EAC1C,4BAA4B,QAAQ,aAAa,qCAAqC;EACtF,sBAAsB,QAAQ,aAAa,iBAAiB;EAC5D,2BAA2B,QACzB,aACA,oBACA,GAAG,QAAQ,SAAS,GAAG,QAAQ,QAC/B,yBACD;EACF;;AAGH,SAAgB,wCAAwC,cAA6C;AACnG,KAAI,QAAQ,aAAa,QAAS,QAAO;CACzC,MAAM,aAAa,aAAa,2BAA2B,MAAM;AACjE,KAAI,CAAC,cAAc,CAAC,WAAW,WAAW,CAAE,QAAO;CACnD,MAAM,aAAa,SAAS,WAAW;AACvC,KAAI,CAAC,WAAW,QAAQ,CAAE,QAAO;AACjC,KAAI;AACF,aAAW,YAAY,UAAU,KAAK;AACtC,SAAO;SACD;AACN,YAAU,YAAa,WAAW,OAAO,MAAS,GAAM;AACxD,aAAW,YAAY,UAAU,KAAK;AACtC,UAAQ,OAAO,MAAM,sFAAsF,WAAW,IAAI;AAC1H,SAAO"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nextclaw",
3
- "version": "0.44.2",
3
+ "version": "0.45.1",
4
4
  "description": "Powerful AI assistant for coordinating agents, skills, CLI tools, automations, and messaging apps.",
5
5
  "private": false,
6
6
  "type": "module",
@@ -42,9 +42,9 @@
42
42
  "jszip": "^3.10.1",
43
43
  "yaml": "^2.8.1",
44
44
  "@nextclaw/core": "0.17.11",
45
+ "@nextclaw/app-runtime": "0.14.0",
46
+ "@nextclaw/kernel": "0.12.0",
45
47
  "@nextclaw/mcp": "0.3.38",
46
- "@nextclaw/app-runtime": "0.13.3",
47
- "@nextclaw/kernel": "0.11.0",
48
48
  "@nextclaw/ncp": "0.10.0",
49
49
  "@nextclaw/ncp-agent-runtime": "0.4.21",
50
50
  "@nextclaw/ncp-mcp": "0.2.38",
@@ -52,11 +52,11 @@
52
52
  "@nextclaw/nextclaw-hermes-acp-bridge": "0.3.21",
53
53
  "@nextclaw/nextclaw-ncp-runtime-http-client": "0.3.22",
54
54
  "@nextclaw/nextclaw-ncp-runtime-stdio-client": "0.3.38",
55
- "@nextclaw/remote": "0.3.46",
56
- "@nextclaw/runtime": "0.4.37",
57
- "@nextclaw/service": "0.3.49",
58
- "@nextclaw/server": "0.19.0",
59
- "@nextclaw/shared": "0.4.29"
55
+ "@nextclaw/remote": "0.3.47",
56
+ "@nextclaw/server": "0.20.0",
57
+ "@nextclaw/service": "0.4.1",
58
+ "@nextclaw/shared": "0.4.29",
59
+ "@nextclaw/runtime": "0.4.37"
60
60
  },
61
61
  "devDependencies": {
62
62
  "@types/node": "^20.17.6",
@@ -64,7 +64,7 @@
64
64
  "tsx": "^4.19.2",
65
65
  "typescript": "^5.6.3",
66
66
  "vitest": "^4.1.2",
67
- "@nextclaw/ui": "0.21.0"
67
+ "@nextclaw/ui": "0.22.0"
68
68
  },
69
69
  "scripts": {
70
70
  "dev": "tsx watch --tsconfig ../../scripts/dev/dev-runtime.tsconfig.json src/cli/app/index.ts",
@@ -0,0 +1,34 @@
1
+ # 日常小工具箱
2
+
3
+ 这是 Portable Runtime MVP 的产品化体验包。它不要求体验者理解 WASM、Action 或 Provider,而是先提供四个普通人能直接使用的小应用;每个应用背后对应一条必须验证的运行时主链。
4
+
5
+ ## 先体验产品
6
+
7
+ | 小应用 | 能做什么 | 背后验证的机制 |
8
+ | --- | --- | --- |
9
+ | 今日清单 | 添加、完成、恢复和删除今天的任务 | Rust/WASM 数据 Action、Host KV 持久化、Panel 与 Agent 共用数据 |
10
+ | 灵感便签 | 随手保存、搜索和删除便签 | 两个独立 Panel 复用同一个数据 Component |
11
+ | 专注小钟 | 开始一轮专注、记录完成点,关掉页面后仍计时 | Resident Component、宿主事件投递、持久恢复 |
12
+ | 联系人整理 | 清理姓名空格、邮箱大小写和重复标签 | Consumer 经宿主调用独立 Provider Component |
13
+
14
+ 应用列表还保留一个「开发者验证台」。它不是普通用户的主入口,只用于查看共享 runner、受控网络、越权拒绝、超时恢复和运行时指标等底层证据。
15
+
16
+ ## Agent 也能使用
17
+
18
+ 「今日清单」和「灵感便签」使用的 Service Action 可以显式授权给 Agent。授权后,Panel 与 Agent 操作的是同一份结构化数据,权限、持久化和错误处理也走同一条主链,没有 Demo 专用旁路。
19
+
20
+ ## 真实调用链
21
+
22
+ ```text
23
+ 日常小应用 / Agent
24
+ → Service Action 授权
25
+ → ServiceAppManager
26
+ → wasi-component runtime
27
+ → 共享 Rust/Wasmtime runner
28
+ → Host capability / component-call
29
+ → Rust/WASM Component
30
+ ```
31
+
32
+ ## 当前边界
33
+
34
+ 这是单平台技术 MVP:官方 Guest 只覆盖 Rust,不承诺 Python/FastAPI 等现有框架直接编译,也不代表跨平台 runner 分发、签名、升级和生产级资源治理已经完成。
@@ -0,0 +1,12 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
2
+ <defs>
3
+ <linearGradient id="g" x1="8" y1="8" x2="56" y2="56" gradientUnits="userSpaceOnUse">
4
+ <stop stop-color="#2dd4bf"/>
5
+ <stop offset="1" stop-color="#2563eb"/>
6
+ </linearGradient>
7
+ </defs>
8
+ <rect width="64" height="64" rx="16" fill="#07111f"/>
9
+ <path d="M17 18h30v20a9 9 0 0 1-9 9H26a9 9 0 0 1-9-9V18Z" fill="none" stroke="url(#g)" stroke-width="4"/>
10
+ <path d="M24 27h16M24 35h10" stroke="#e6fffb" stroke-width="3" stroke-linecap="round"/>
11
+ <circle cx="46" cy="46" r="7" fill="#60a5fa" stroke="#07111f" stroke-width="3"/>
12
+ </svg>
@@ -0,0 +1,74 @@
1
+ {
2
+ "schemaVersion": 2,
3
+ "id": "nextclaw.portable-runtime-lab",
4
+ "name": "日常小工具箱",
5
+ "version": "0.5.2",
6
+ "description": "清单、便签、专注计时和联系人整理,四个能直接使用的日常小应用。",
7
+ "icon": "assets/icon.svg",
8
+ "engines": {
9
+ "nextclaw": ">=0.43.0"
10
+ },
11
+ "presentation": {
12
+ "primaryPanel": "nextclaw-portable-runtime-lab-todos"
13
+ },
14
+ "runtime": {
15
+ "profile": "wasi"
16
+ },
17
+ "distribution": {
18
+ "mode": "universal"
19
+ },
20
+ "storage": {
21
+ "scope": "global",
22
+ "schemaVersion": 1
23
+ },
24
+ "permissions": {
25
+ "storage": {
26
+ "namespace": "portable-runtime-lab"
27
+ },
28
+ "allowedDomains": [
29
+ "httpbin.org"
30
+ ]
31
+ },
32
+ "components": [
33
+ {
34
+ "kind": "panel",
35
+ "path": "panels/nextclaw-portable-runtime-lab-todos.panel"
36
+ },
37
+ {
38
+ "kind": "panel",
39
+ "path": "panels/nextclaw-portable-runtime-lab-notes.panel"
40
+ },
41
+ {
42
+ "kind": "panel",
43
+ "path": "panels/nextclaw-portable-runtime-lab-focus.panel"
44
+ },
45
+ {
46
+ "kind": "panel",
47
+ "path": "panels/nextclaw-portable-runtime-lab-contacts.panel"
48
+ },
49
+ {
50
+ "kind": "panel",
51
+ "path": "panels/nextclaw-portable-runtime-lab-panel.panel"
52
+ },
53
+ {
54
+ "kind": "service",
55
+ "path": "service-components/nextclaw-portable-runtime-lab-state"
56
+ },
57
+ {
58
+ "kind": "service",
59
+ "path": "service-components/nextclaw-portable-runtime-lab-capabilities"
60
+ },
61
+ {
62
+ "kind": "service",
63
+ "path": "service-components/nextclaw-portable-runtime-lab-resident"
64
+ },
65
+ {
66
+ "kind": "service",
67
+ "path": "service-components/nextclaw-portable-runtime-lab-provider"
68
+ },
69
+ {
70
+ "kind": "service",
71
+ "path": "service-components/nextclaw-portable-runtime-lab-composition"
72
+ }
73
+ ]
74
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "slug": "portable-runtime-lab",
3
+ "summary": "Four everyday mini apps powered by portable Rust/WASM services.",
4
+ "summaryI18n": {
5
+ "zh-CN": "用清单、便签、专注计时和联系人整理,体验可移植 Service App。",
6
+ "en-US": "Use todos, notes, a focus timer, and contact cleanup powered by portable Service Apps."
7
+ },
8
+ "description": "Product-first demos for persistent data, shared capabilities, resident background work, component composition, and Agent access.",
9
+ "descriptionI18n": {
10
+ "zh-CN": "四个普通人能直接使用的小应用,背后覆盖持久数据、能力复用、后台常驻、组件组合与 Agent 调用。",
11
+ "en-US": "Four approachable mini apps backed by persistence, capability reuse, resident work, component composition, and Agent access."
12
+ },
13
+ "author": "NextClaw",
14
+ "tags": ["productivity", "todo", "notes", "wasm", "experimental"],
15
+ "sourceRepo": "https://github.com/Peiiii/nextclaw",
16
+ "homepage": "https://nextclaw.io",
17
+ "featured": false,
18
+ "visuals": {
19
+ "accentColor": "#7C5CFC"
20
+ }
21
+ }
@@ -0,0 +1,92 @@
1
+ const actionIds = {
2
+ compose: "nextclaw-portable-runtime-lab-composition.compose_contact",
3
+ providerStatus: "nextclaw-portable-runtime-lab-provider.provider_status",
4
+ };
5
+
6
+ const elements = {
7
+ form: document.querySelector("[data-contact-form]"),
8
+ name: document.querySelector("[data-name]"),
9
+ email: document.querySelector("[data-email]"),
10
+ tags: document.querySelector("[data-tags]"),
11
+ card: document.querySelector("[data-contact-card]"),
12
+ placeholder: document.querySelector("[data-placeholder]"),
13
+ avatar: document.querySelector("[data-avatar]"),
14
+ displayName: document.querySelector("[data-display-name]"),
15
+ displayEmail: document.querySelector("[data-display-email]"),
16
+ displayTags: document.querySelector("[data-display-tags]"),
17
+ copy: document.querySelector("[data-copy]"),
18
+ notice: document.querySelector("[data-notice]"),
19
+ count: document.querySelector("[data-proof-count]"),
20
+ };
21
+
22
+ let latestContact = null;
23
+
24
+ function serviceActions() {
25
+ const bridge = window.nextclaw?.serviceActions;
26
+ if (!bridge) throw new Error("应用还没有连接到 NextClaw,请刷新后重试。");
27
+ return bridge;
28
+ }
29
+
30
+ async function invoke(actionId, input = {}) {
31
+ return await serviceActions().invoke(actionId, input);
32
+ }
33
+
34
+ function initials(name) {
35
+ const parts = name.split(/\s+/).filter(Boolean);
36
+ return parts.slice(0, 2).map((part) => part[0]?.toUpperCase() ?? "").join("") || "?";
37
+ }
38
+
39
+ function render(result) {
40
+ const provider = result.provider ?? {};
41
+ latestContact = {
42
+ name: provider.normalizedName ?? "",
43
+ email: provider.normalizedEmail ?? "",
44
+ tags: Array.isArray(provider.normalizedTags) ? provider.normalizedTags : [],
45
+ };
46
+ elements.placeholder.hidden = true;
47
+ elements.card.hidden = false;
48
+ elements.avatar.textContent = initials(latestContact.name);
49
+ elements.displayName.textContent = latestContact.name;
50
+ elements.displayEmail.textContent = latestContact.email;
51
+ elements.displayEmail.href = `mailto:${latestContact.email}`;
52
+ elements.displayTags.replaceChildren();
53
+ for (const tag of latestContact.tags) {
54
+ const chip = document.createElement("span");
55
+ chip.textContent = tag;
56
+ elements.displayTags.append(chip);
57
+ }
58
+ elements.count.textContent = `${provider.providerCallCount ?? "—"} 次`;
59
+ }
60
+
61
+ elements.form.addEventListener("submit", (event) => {
62
+ event.preventDefault();
63
+ elements.notice.textContent = "正在整理…";
64
+ elements.notice.dataset.tone = "";
65
+ void invoke(actionIds.compose, {
66
+ name: elements.name.value,
67
+ email: elements.email.value,
68
+ tags: elements.tags.value.split(",").map((tag) => tag.trim()).filter(Boolean),
69
+ }).then((result) => {
70
+ render(result);
71
+ elements.notice.textContent = "姓名空格、邮箱大小写和重复标签都已整理";
72
+ elements.notice.dataset.tone = "success";
73
+ }).catch((error) => {
74
+ elements.notice.textContent = error instanceof Error ? error.message : String(error);
75
+ elements.notice.dataset.tone = "error";
76
+ });
77
+ });
78
+
79
+ elements.copy.addEventListener("click", () => {
80
+ if (!latestContact) return;
81
+ const text = `${latestContact.name} <${latestContact.email}>${latestContact.tags.length ? ` · ${latestContact.tags.join(", ")}` : ""}`;
82
+ void navigator.clipboard.writeText(text).then(() => {
83
+ elements.notice.textContent = "联系人已复制";
84
+ elements.notice.dataset.tone = "success";
85
+ }).catch(() => {
86
+ elements.notice.textContent = text;
87
+ });
88
+ });
89
+
90
+ void invoke(actionIds.providerStatus).then((status) => {
91
+ elements.count.textContent = `${status.providerCallCount ?? 0} 次`;
92
+ }).catch(() => undefined);
@@ -0,0 +1,43 @@
1
+ :root { color-scheme: light; font-family: Inter, "PingFang SC", "Microsoft YaHei", sans-serif; color: #282b45; background: #eef1fb; }
2
+ * { box-sizing: border-box; }
3
+ body { margin: 0; min-width: 320px; background: radial-gradient(circle at 85% 10%, #e8dcff 0, transparent 35%), linear-gradient(135deg, #f9faff, #eef2fb); }
4
+ button, input { font: inherit; }
5
+ button { cursor: pointer; }
6
+ .app-shell { width: min(940px, calc(100% - 32px)); margin: 0 auto; padding: 38px 0 52px; }
7
+ .hero { margin-bottom: 26px; }
8
+ .eyebrow { margin: 0 0 8px; color: #725cc7; font-size: 12px; font-weight: 800; letter-spacing: .18em; }
9
+ h1 { margin: 0; font-family: Georgia, "Noto Serif SC", serif; font-size: clamp(36px, 7vw, 58px); letter-spacing: -.04em; }
10
+ .hero > p:last-child { margin: 8px 0 0; color: #74758a; }
11
+ .workspace { display: grid; grid-template-columns: minmax(260px, .9fr) minmax(300px, 1.1fr); gap: 18px; min-height: 370px; }
12
+ .contact-form { display: grid; align-content: start; gap: 16px; padding: 22px; border: 1px solid #dee2f1; border-radius: 22px; background: rgba(255,255,255,.82); box-shadow: 0 20px 60px rgba(58,62,102,.09); }
13
+ .contact-form label { display: grid; gap: 7px; color: #65677c; font-size: 13px; font-weight: 650; }
14
+ .contact-form input { width: 100%; border: 1px solid #d9ddec; border-radius: 11px; outline: 0; padding: 11px 12px; color: #2f314b; background: #fafbff; font-weight: 450; }
15
+ .contact-form input:focus { border-color: #8976d2; box-shadow: 0 0 0 3px rgba(137,118,210,.12); }
16
+ .contact-form button { margin-top: 4px; border: 0; border-radius: 12px; padding: 12px 18px; color: #fff; background: #6650b8; font-weight: 750; }
17
+ .contact-card, .placeholder { border: 1px solid #dcdff0; border-radius: 24px; background: linear-gradient(145deg, rgba(255,255,255,.92), rgba(245,244,255,.9)); box-shadow: 0 24px 70px rgba(59,58,105,.12); }
18
+ .contact-card { position: relative; display: flex; align-items: center; gap: 18px; padding: 32px; overflow: hidden; }
19
+ .contact-card::after { position: absolute; right: -55px; bottom: -75px; width: 190px; height: 190px; border-radius: 50%; background: rgba(150,125,224,.12); content: ""; }
20
+ .contact-card[hidden] { display: none; }
21
+ .avatar { display: grid; flex: 0 0 auto; width: 78px; height: 78px; place-items: center; border-radius: 24px; color: #fff; background: linear-gradient(145deg, #8973d4, #5d4aaf); box-shadow: 0 10px 25px rgba(88,66,173,.25); font-size: 25px; font-weight: 800; }
22
+ .card-copy { min-width: 0; flex: 1; z-index: 1; }
23
+ .card-copy > p { margin: 0 0 5px; color: #77738f; font-size: 12px; }
24
+ .card-copy h2 { margin: 0; overflow-wrap: anywhere; font-size: 24px; }
25
+ .card-copy a { display: inline-block; margin-top: 5px; color: #6653ad; overflow-wrap: anywhere; text-decoration: none; }
26
+ .tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 16px; }
27
+ .tags span { padding: 6px 9px; border-radius: 999px; color: #5c4e91; background: #e8e2fb; font-size: 12px; }
28
+ .copy-button { z-index: 1; align-self: flex-start; border: 1px solid #d6d1e8; border-radius: 10px; padding: 8px 11px; color: #615a78; background: rgba(255,255,255,.8); font-size: 12px; }
29
+ .placeholder { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; padding: 32px; color: #8a8b9e; text-align: center; }
30
+ .placeholder[hidden] { display: none; }
31
+ .placeholder span { color: #8369dc; font-size: 34px; }
32
+ .placeholder p { max-width: 280px; margin: 0; line-height: 1.6; }
33
+ .notice { min-height: 22px; margin: 14px 2px; color: #74758a; font-size: 14px; }
34
+ .notice[data-tone="success"] { color: #42775b; }
35
+ .notice[data-tone="error"] { color: #ad5047; }
36
+ .behind-scenes { margin-top: 26px; padding: 16px 18px; border: 1px solid #dfe2ef; border-radius: 16px; color: #737488; background: rgba(255,255,255,.55); font-size: 13px; }
37
+ .behind-scenes summary { color: #505268; cursor: pointer; font-weight: 700; }
38
+ .behind-scenes p { line-height: 1.65; }
39
+ .behind-scenes dl { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; margin: 12px 0 0; }
40
+ .behind-scenes dl div { padding: 10px; border-radius: 10px; background: #fff; }
41
+ .behind-scenes dt { color: #9697a9; font-size: 11px; }
42
+ .behind-scenes dd { margin: 3px 0 0; color: #505268; overflow-wrap: anywhere; }
43
+ @media (max-width: 680px) { .app-shell { width: calc(100% - 22px); padding-top: 24px; } .workspace { grid-template-columns: 1fr; } .contact-card { min-height: 260px; flex-wrap: wrap; } .copy-button { margin-left: auto; } .behind-scenes dl { grid-template-columns: 1fr; } }
@@ -0,0 +1,43 @@
1
+ <!doctype html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>联系人整理</title>
7
+ <link rel="stylesheet" href="assets/style.css" />
8
+ </head>
9
+ <body>
10
+ <main class="app-shell">
11
+ <header class="hero">
12
+ <p class="eyebrow">CONTACT CLEANUP</p>
13
+ <h1>联系人整理</h1>
14
+ <p>复制来的信息乱七八糟?交给它变成一张干净的联系人卡片。</p>
15
+ </header>
16
+
17
+ <section class="workspace">
18
+ <form class="contact-form" data-contact-form>
19
+ <label>姓名<input data-name value=" Ada Lovelace " autocomplete="name" /></label>
20
+ <label>邮箱<input data-email value=" ADA@EXAMPLE.COM " autocomplete="email" /></label>
21
+ <label>标签<input data-tags value="AI, ai, Math" placeholder="用逗号分隔" /></label>
22
+ <button type="submit">整理联系人</button>
23
+ </form>
24
+
25
+ <article class="contact-card" data-contact-card hidden>
26
+ <div class="avatar" data-avatar>AL</div>
27
+ <div class="card-copy"><p>整理完成</p><h2 data-display-name>—</h2><a data-display-email>—</a><div class="tags" data-display-tags></div></div>
28
+ <button type="button" class="copy-button" data-copy>复制</button>
29
+ </article>
30
+ <div class="placeholder" data-placeholder><span>✦</span><p>填写左边的信息,整理后的联系人会出现在这里。</p></div>
31
+ </section>
32
+
33
+ <p class="notice" data-notice></p>
34
+
35
+ <details class="behind-scenes">
36
+ <summary>这和普通表单有什么不同?</summary>
37
+ <p>这个 Panel 只负责收集和展示。真正的规范化逻辑由独立 Provider Component 提供,Consumer 通过宿主受控调用进行组合。以后替换联系人规则,页面不用复制一份实现。</p>
38
+ <dl><div><dt>组合方式</dt><dd>Host mediated component call</dd></div><div><dt>Provider 已处理</dt><dd data-proof-count>等待执行</dd></div></dl>
39
+ </details>
40
+ </main>
41
+ <script src="assets/app.js"></script>
42
+ </body>
43
+ </html>
@@ -0,0 +1,10 @@
1
+ {
2
+ "id": "nextclaw-portable-runtime-lab-contacts",
3
+ "title": "联系人整理",
4
+ "description": "把乱格式的姓名、邮箱和标签整理成干净联系人卡片。",
5
+ "entry": "index.html",
6
+ "actions": [
7
+ "nextclaw-portable-runtime-lab-composition.compose_contact",
8
+ "nextclaw-portable-runtime-lab-provider.provider_status"
9
+ ]
10
+ }
@@ -0,0 +1,132 @@
1
+ const actionIds = {
2
+ status: "nextclaw-portable-runtime-lab-resident.resident_status",
3
+ reset: "nextclaw-portable-runtime-lab-resident.resident_reset",
4
+ milestone: "nextclaw-portable-runtime-lab-resident.resident_emit_event",
5
+ recordsList: "nextclaw-portable-runtime-lab-state.records_list",
6
+ recordUpsert: "nextclaw-portable-runtime-lab-state.record_upsert",
7
+ };
8
+
9
+ const elements = {
10
+ clock: document.querySelector("[data-clock]"),
11
+ caption: document.querySelector("[data-clock-caption]"),
12
+ milestones: document.querySelector("[data-milestones]"),
13
+ notice: document.querySelector("[data-notice]"),
14
+ epoch: document.querySelector("[data-proof-epoch]"),
15
+ events: document.querySelector("[data-proof-events]"),
16
+ };
17
+
18
+ function serviceActions() {
19
+ const bridge = window.nextclaw?.serviceActions;
20
+ if (!bridge) throw new Error("应用还没有连接到 NextClaw,请刷新后重试。");
21
+ return bridge;
22
+ }
23
+
24
+ async function invoke(actionId, input = {}) {
25
+ return await serviceActions().invoke(actionId, input);
26
+ }
27
+
28
+ function formatDuration(totalSeconds) {
29
+ const safe = Math.max(0, Number(totalSeconds) || 0);
30
+ const hours = Math.floor(safe / 3600);
31
+ const minutes = Math.floor((safe % 3600) / 60);
32
+ const seconds = safe % 60;
33
+ return hours > 0
34
+ ? `${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`
35
+ : `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
36
+ }
37
+
38
+ function milestoneTime(record) {
39
+ const timestamp = Number(String(record.id).replace("focus-", ""));
40
+ return Number.isFinite(timestamp) ? new Date(timestamp) : null;
41
+ }
42
+
43
+ function renderMilestones(records) {
44
+ const items = (Array.isArray(records) ? records : [])
45
+ .filter((record) => Array.isArray(record.tags) && record.tags.includes("focus-milestone"))
46
+ .sort((left, right) => (milestoneTime(right)?.getTime() ?? 0) - (milestoneTime(left)?.getTime() ?? 0));
47
+ elements.milestones.replaceChildren();
48
+ if (items.length === 0) {
49
+ const empty = document.createElement("li");
50
+ empty.className = "empty";
51
+ empty.textContent = "还没有完成点";
52
+ elements.milestones.append(empty);
53
+ return;
54
+ }
55
+ for (const item of items) {
56
+ const row = document.createElement("li");
57
+ const dot = document.createElement("i");
58
+ const copy = document.createElement("div");
59
+ const title = document.createElement("strong");
60
+ title.textContent = item.title || "完成了一个阶段";
61
+ const time = document.createElement("span");
62
+ const occurredAt = milestoneTime(item);
63
+ time.textContent = occurredAt
64
+ ? occurredAt.toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit", second: "2-digit" })
65
+ : `数据版本 ${item.version}`;
66
+ copy.append(title, time);
67
+ row.append(dot, copy);
68
+ elements.milestones.append(row);
69
+ }
70
+ }
71
+
72
+ async function refresh() {
73
+ const [state, records] = await Promise.all([
74
+ invoke(actionIds.status),
75
+ invoke(actionIds.recordsList),
76
+ ]);
77
+ elements.clock.textContent = formatDuration(state.eventCount);
78
+ elements.caption.textContent = state.eventCount > 0 ? "本轮专注仍在后台继续" : "准备好后,开始一轮新的专注";
79
+ elements.epoch.textContent = `第 ${state.instanceEpoch} 次运行实例`;
80
+ elements.events.textContent = `${state.eventCount} 个持久事件`;
81
+ renderMilestones(records.records);
82
+ if (elements.notice.dataset.tone === "error") {
83
+ elements.notice.textContent = "";
84
+ elements.notice.dataset.tone = "";
85
+ }
86
+ return state;
87
+ }
88
+
89
+ async function run(operation, successMessage) {
90
+ elements.notice.textContent = "正在更新…";
91
+ elements.notice.dataset.tone = "";
92
+ try {
93
+ await operation();
94
+ await refresh();
95
+ elements.notice.textContent = successMessage;
96
+ elements.notice.dataset.tone = "success";
97
+ } catch (error) {
98
+ elements.notice.textContent = error instanceof Error ? error.message : String(error);
99
+ elements.notice.dataset.tone = "error";
100
+ }
101
+ }
102
+
103
+ document.querySelector("[data-start]").addEventListener("click", () => {
104
+ void run(() => invoke(actionIds.reset), "新一轮专注已经开始,关闭页面也会继续");
105
+ });
106
+ document.querySelector("[data-milestone]").addEventListener("click", () => {
107
+ const now = new Date();
108
+ const eventId = `focus-${now.getTime()}`;
109
+ void run(async () => {
110
+ await invoke(actionIds.milestone, {
111
+ eventId,
112
+ kind: "focus-milestone",
113
+ triggeredAt: now.toISOString(),
114
+ });
115
+ await invoke(actionIds.recordUpsert, {
116
+ id: eventId,
117
+ title: "完成了一个阶段",
118
+ status: "completed",
119
+ tags: ["focus-milestone", "focus"],
120
+ });
121
+ }, "已经记下这个完成点,并保存到专注历史");
122
+ });
123
+ document.querySelector("[data-refresh]").addEventListener("click", () => void refresh());
124
+
125
+ void refresh().catch((error) => {
126
+ elements.notice.textContent = error instanceof Error ? error.message : String(error);
127
+ elements.notice.dataset.tone = "error";
128
+ });
129
+ const poll = window.setInterval(() => {
130
+ if (document.visibilityState === "visible") void refresh();
131
+ }, 1000);
132
+ window.addEventListener("pagehide", () => window.clearInterval(poll), { once: true });
@@ -0,0 +1,39 @@
1
+ :root { color-scheme: dark; font-family: Inter, "PingFang SC", "Microsoft YaHei", sans-serif; color: #f3f0ff; background: #141124; }
2
+ * { box-sizing: border-box; }
3
+ body { margin: 0; min-width: 320px; background: radial-gradient(circle at 50% 0, #302552 0, #171329 43%, #100e1c 100%); }
4
+ button { font: inherit; cursor: pointer; }
5
+ .app-shell { width: min(800px, calc(100% - 32px)); margin: 0 auto; padding: 38px 0 52px; }
6
+ .hero { display: flex; align-items: flex-start; justify-content: space-between; gap: 20px; }
7
+ .eyebrow { margin: 0 0 8px; color: #a993f4; font-size: 12px; font-weight: 800; letter-spacing: .18em; }
8
+ h1 { margin: 0; font-family: Georgia, "Noto Serif SC", serif; font-size: clamp(36px, 7vw, 58px); letter-spacing: -.04em; }
9
+ .hero p:last-child { margin: 8px 0 0; color: #aaa3c1; }
10
+ .live { display: inline-flex; flex: 0 0 auto; align-items: center; gap: 7px; margin-top: 8px; padding: 8px 11px; border: 1px solid rgba(171,155,233,.24); border-radius: 999px; color: #c8bdf2; background: rgba(51,41,83,.7); font-size: 12px; }
11
+ .live i { width: 7px; height: 7px; border-radius: 50%; background: #8ae0ae; box-shadow: 0 0 10px #8ae0ae; }
12
+ .clock-card { margin-top: 30px; padding: clamp(34px, 8vw, 68px) 24px; border: 1px solid rgba(187,169,255,.2); border-radius: 30px; background: linear-gradient(145deg, rgba(62,48,99,.78), rgba(30,25,50,.88)); text-align: center; box-shadow: 0 28px 90px rgba(2,0,12,.35), inset 0 1px rgba(255,255,255,.08); }
13
+ .clock { font-variant-numeric: tabular-nums; font-size: clamp(64px, 15vw, 116px); font-weight: 200; letter-spacing: -.06em; line-height: 1; text-shadow: 0 0 40px rgba(176,153,255,.18); }
14
+ .clock-card > p { margin: 14px 0 0; color: #aca4c5; }
15
+ .actions { display: flex; justify-content: center; gap: 10px; margin-top: 28px; }
16
+ .actions button { border: 0; border-radius: 13px; padding: 12px 20px; color: #201a34; background: #bba9ff; font-weight: 750; }
17
+ .actions .secondary { border: 1px solid rgba(194,177,255,.26); color: #d5cbf6; background: rgba(255,255,255,.05); }
18
+ .timeline { margin-top: 28px; padding: 22px; border: 1px solid rgba(188,174,237,.14); border-radius: 20px; background: rgba(24,20,40,.58); }
19
+ .section-title { display: flex; justify-content: space-between; gap: 16px; }
20
+ .section-title h2 { margin: 0; font-size: 18px; }
21
+ .section-title p { margin: 6px 0 0; color: #928ba8; font-size: 13px; }
22
+ .text-button { border: 0; color: #bba9ff; background: transparent; }
23
+ .timeline ol { display: grid; gap: 10px; margin: 20px 0 0; padding: 0; list-style: none; }
24
+ .timeline li:not(.empty) { display: flex; align-items: center; gap: 13px; padding: 11px 13px; border-radius: 12px; background: rgba(255,255,255,.045); }
25
+ .timeline li i { width: 10px; height: 10px; border: 2px solid #9e87ef; border-radius: 50%; }
26
+ .timeline li div { display: flex; flex: 1; align-items: center; justify-content: space-between; gap: 10px; }
27
+ .timeline li span { color: #938ca8; font-size: 12px; }
28
+ .empty { padding: 24px; color: #7f788f; text-align: center; }
29
+ .notice { min-height: 22px; margin: 14px 2px; color: #a19ab5; font-size: 14px; }
30
+ .notice[data-tone="success"] { color: #8ae0ae; }
31
+ .notice[data-tone="error"] { color: #ff978e; }
32
+ .behind-scenes { margin-top: 25px; padding: 16px 18px; border: 1px solid rgba(187,172,238,.16); border-radius: 16px; color: #9d96af; background: rgba(255,255,255,.025); font-size: 13px; }
33
+ .behind-scenes summary { color: #c9c0e6; cursor: pointer; font-weight: 700; }
34
+ .behind-scenes p { line-height: 1.65; }
35
+ .behind-scenes dl { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; margin: 12px 0 0; }
36
+ .behind-scenes dl div { padding: 10px; border-radius: 10px; background: rgba(255,255,255,.04); }
37
+ .behind-scenes dt { color: #817a93; font-size: 11px; }
38
+ .behind-scenes dd { margin: 3px 0 0; color: #d2cae7; }
39
+ @media (max-width: 560px) { .app-shell { width: calc(100% - 22px); padding-top: 24px; } .hero { flex-direction: column; } .actions { flex-direction: column; } .section-title { align-items: flex-start; } .behind-scenes dl { grid-template-columns: 1fr; } }