jishushell 0.4.30 → 0.5.22

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 (226) hide show
  1. package/Dockerfile.hermes-slim +2 -5
  2. package/apps/anythingllm-container.yaml +287 -0
  3. package/apps/browserless-chromium-container.yaml +18 -6
  4. package/apps/filebrowser-container.yaml +164 -0
  5. package/apps/ollama-binary.yaml +44 -0
  6. package/apps/ollama-with-hollama-binary.yaml +45 -1
  7. package/apps/openclaw-binary.yaml +8 -0
  8. package/apps/openclaw-container.yaml +9 -1
  9. package/apps/openclaw-with-searxng-container.yaml +4 -0
  10. package/apps/searxng-container.yaml +5 -4
  11. package/apps/weknora-container.yaml +471 -0
  12. package/dist/cli/doctor.js +144 -16
  13. package/dist/cli/doctor.js.map +1 -1
  14. package/dist/cli/panel.js.map +1 -1
  15. package/dist/config.d.ts +19 -0
  16. package/dist/config.js +99 -1
  17. package/dist/config.js.map +1 -1
  18. package/dist/install.js +4 -4
  19. package/dist/install.js.map +1 -1
  20. package/dist/routes/auth.js +2 -2
  21. package/dist/routes/auth.js.map +1 -1
  22. package/dist/routes/backup.js +64 -11
  23. package/dist/routes/backup.js.map +1 -1
  24. package/dist/routes/external-mounts.d.ts +17 -0
  25. package/dist/routes/external-mounts.js +73 -0
  26. package/dist/routes/external-mounts.js.map +1 -0
  27. package/dist/routes/file-mounts.d.ts +13 -0
  28. package/dist/routes/file-mounts.js +90 -0
  29. package/dist/routes/file-mounts.js.map +1 -0
  30. package/dist/routes/files-organize.d.ts +28 -0
  31. package/dist/routes/files-organize.js +167 -0
  32. package/dist/routes/files-organize.js.map +1 -0
  33. package/dist/routes/files.d.ts +31 -0
  34. package/dist/routes/files.js +321 -0
  35. package/dist/routes/files.js.map +1 -0
  36. package/dist/routes/instances.js +87 -12
  37. package/dist/routes/instances.js.map +1 -1
  38. package/dist/routes/internal.d.ts +2 -0
  39. package/dist/routes/internal.js +59 -0
  40. package/dist/routes/internal.js.map +1 -0
  41. package/dist/routes/llm.js +29 -0
  42. package/dist/routes/llm.js.map +1 -1
  43. package/dist/routes/setup.js +9 -9
  44. package/dist/routes/setup.js.map +1 -1
  45. package/dist/routes/system.js +1 -1
  46. package/dist/routes/system.js.map +1 -1
  47. package/dist/routes/webdav.d.ts +17 -0
  48. package/dist/routes/webdav.js +114 -0
  49. package/dist/routes/webdav.js.map +1 -0
  50. package/dist/server.js +358 -6
  51. package/dist/server.js.map +1 -1
  52. package/dist/services/agent-apps/catalog.d.ts +3 -0
  53. package/dist/services/agent-apps/catalog.js +40 -13
  54. package/dist/services/agent-apps/catalog.js.map +1 -1
  55. package/dist/services/agent-apps/installers/shell-script.d.ts +1 -1
  56. package/dist/services/agent-apps/installers/shell-script.js +19 -2
  57. package/dist/services/agent-apps/installers/shell-script.js.map +1 -1
  58. package/dist/services/agent-apps/types.d.ts +3 -0
  59. package/dist/services/app/app-compiler.d.ts +1 -1
  60. package/dist/services/app/app-compiler.js +5 -5
  61. package/dist/services/app/app-compiler.js.map +1 -1
  62. package/dist/services/app/app-manager.d.ts +9 -0
  63. package/dist/services/app/app-manager.js +248 -43
  64. package/dist/services/app/app-manager.js.map +1 -1
  65. package/dist/services/app/custom-manager.js.map +1 -1
  66. package/dist/services/app/hermes-agent-manager.js +1 -0
  67. package/dist/services/app/hermes-agent-manager.js.map +1 -1
  68. package/dist/services/app/ollama-manager.js +1 -1
  69. package/dist/services/app/ollama-manager.js.map +1 -1
  70. package/dist/services/app/openclaw-manager.js +37 -5
  71. package/dist/services/app/openclaw-manager.js.map +1 -1
  72. package/dist/services/app/platform-transform.d.ts +32 -0
  73. package/dist/services/app/platform-transform.js +65 -0
  74. package/dist/services/app/platform-transform.js.map +1 -0
  75. package/dist/services/app-passwords.d.ts +61 -0
  76. package/dist/services/app-passwords.js +173 -0
  77. package/dist/services/app-passwords.js.map +1 -0
  78. package/dist/services/backup-manager.d.ts +11 -0
  79. package/dist/services/backup-manager.js +220 -8
  80. package/dist/services/backup-manager.js.map +1 -1
  81. package/dist/services/capability-endpoint-validator.js +26 -7
  82. package/dist/services/capability-endpoint-validator.js.map +1 -1
  83. package/dist/services/connection-apply.d.ts +2 -0
  84. package/dist/services/connection-apply.js +55 -1
  85. package/dist/services/connection-apply.js.map +1 -1
  86. package/dist/services/connection-resolver.js +1 -1
  87. package/dist/services/connection-resolver.js.map +1 -1
  88. package/dist/services/connection-transactor.d.ts +2 -0
  89. package/dist/services/connection-transactor.js +12 -2
  90. package/dist/services/connection-transactor.js.map +1 -1
  91. package/dist/services/external-mounts.d.ts +40 -0
  92. package/dist/services/external-mounts.js +187 -0
  93. package/dist/services/external-mounts.js.map +1 -0
  94. package/dist/services/files-manager.d.ts +252 -0
  95. package/dist/services/files-manager.js +1075 -0
  96. package/dist/services/files-manager.js.map +1 -0
  97. package/dist/services/files-mounts.d.ts +42 -0
  98. package/dist/services/files-mounts.js +207 -0
  99. package/dist/services/files-mounts.js.map +1 -0
  100. package/dist/services/instance-manager.js +90 -32
  101. package/dist/services/instance-manager.js.map +1 -1
  102. package/dist/services/llm-proxy/index.d.ts +28 -0
  103. package/dist/services/llm-proxy/index.js +76 -3
  104. package/dist/services/llm-proxy/index.js.map +1 -1
  105. package/dist/services/llm-proxy/ssrf.js +6 -2
  106. package/dist/services/llm-proxy/ssrf.js.map +1 -1
  107. package/dist/services/llm-proxy/validate-key.d.ts +41 -0
  108. package/dist/services/llm-proxy/validate-key.js +672 -0
  109. package/dist/services/llm-proxy/validate-key.js.map +1 -0
  110. package/dist/services/macos-launchd.d.ts +89 -0
  111. package/dist/services/macos-launchd.js +273 -0
  112. package/dist/services/macos-launchd.js.map +1 -0
  113. package/dist/services/nomad-manager.d.ts +11 -0
  114. package/dist/services/nomad-manager.js +343 -98
  115. package/dist/services/nomad-manager.js.map +1 -1
  116. package/dist/services/organize/applier.d.ts +46 -0
  117. package/dist/services/organize/applier.js +218 -0
  118. package/dist/services/organize/applier.js.map +1 -0
  119. package/dist/services/organize/rules.d.ts +57 -0
  120. package/dist/services/organize/rules.js +286 -0
  121. package/dist/services/organize/rules.js.map +1 -0
  122. package/dist/services/organize/scanner.d.ts +50 -0
  123. package/dist/services/organize/scanner.js +366 -0
  124. package/dist/services/organize/scanner.js.map +1 -0
  125. package/dist/services/organize/store.d.ts +14 -0
  126. package/dist/services/organize/store.js +82 -0
  127. package/dist/services/organize/store.js.map +1 -0
  128. package/dist/services/panel-manager.js +40 -11
  129. package/dist/services/panel-manager.js.map +1 -1
  130. package/dist/services/process-manager.js +3 -2
  131. package/dist/services/process-manager.js.map +1 -1
  132. package/dist/services/runtime/adapters/custom.js +56 -0
  133. package/dist/services/runtime/adapters/custom.js.map +1 -1
  134. package/dist/services/runtime/adapters/hermes.d.ts +4 -3
  135. package/dist/services/runtime/adapters/hermes.js +166 -64
  136. package/dist/services/runtime/adapters/hermes.js.map +1 -1
  137. package/dist/services/runtime/adapters/openclaw-routes.d.ts +8 -2
  138. package/dist/services/runtime/adapters/openclaw-routes.js +68 -0
  139. package/dist/services/runtime/adapters/openclaw-routes.js.map +1 -1
  140. package/dist/services/runtime/adapters/openclaw.d.ts +118 -0
  141. package/dist/services/runtime/adapters/openclaw.js +1459 -49
  142. package/dist/services/runtime/adapters/openclaw.js.map +1 -1
  143. package/dist/services/runtime/instance.d.ts +1 -1
  144. package/dist/services/runtime/instance.js +1 -1
  145. package/dist/services/runtime/instance.js.map +1 -1
  146. package/dist/services/runtime/mcp-shims/anythingllm-shim.d.ts +46 -0
  147. package/dist/services/runtime/mcp-shims/anythingllm-shim.js +281 -0
  148. package/dist/services/runtime/mcp-shims/anythingllm-shim.js.map +1 -0
  149. package/dist/services/runtime/mcp-shims/drive-shim.d.ts +54 -0
  150. package/dist/services/runtime/mcp-shims/drive-shim.js +489 -0
  151. package/dist/services/runtime/mcp-shims/drive-shim.js.map +1 -0
  152. package/dist/services/runtime/types.d.ts +31 -0
  153. package/dist/services/setup-manager.js +190 -68
  154. package/dist/services/setup-manager.js.map +1 -1
  155. package/dist/services/suggestions.js.map +1 -1
  156. package/dist/services/update-manager.js +32 -14
  157. package/dist/services/update-manager.js.map +1 -1
  158. package/dist/services/webdav/server.d.ts +24 -0
  159. package/dist/services/webdav/server.js +420 -0
  160. package/dist/services/webdav/server.js.map +1 -0
  161. package/dist/services/webdav/xml-builder.d.ts +73 -0
  162. package/dist/services/webdav/xml-builder.js +156 -0
  163. package/dist/services/webdav/xml-builder.js.map +1 -0
  164. package/dist/services/workspace-builder.d.ts +29 -0
  165. package/dist/services/workspace-builder.js +188 -0
  166. package/dist/services/workspace-builder.js.map +1 -0
  167. package/dist/types.d.ts +61 -0
  168. package/dist/utils/path-locks.d.ts +30 -0
  169. package/dist/utils/path-locks.js +63 -0
  170. package/dist/utils/path-locks.js.map +1 -0
  171. package/dist/utils/path-safety.d.ts +41 -0
  172. package/dist/utils/path-safety.js +119 -0
  173. package/dist/utils/path-safety.js.map +1 -0
  174. package/dist/utils/safe-write.d.ts +24 -0
  175. package/dist/utils/safe-write.js +82 -0
  176. package/dist/utils/safe-write.js.map +1 -0
  177. package/install/jishu-install.sh +247 -35
  178. package/install/jishu-uninstall.sh +45 -5
  179. package/package.json +20 -2
  180. package/public/assets/ApiKeyField-CvyAOcJS.js +1 -0
  181. package/public/assets/Dashboard-AuJESBlJ.js +1 -0
  182. package/public/assets/{HermesChatPanel-_GHoklgo.js → HermesChatPanel-CByPREwb.js} +1 -1
  183. package/public/assets/HermesConfigForm-DRda8FKX.js +4 -0
  184. package/public/assets/InitPassword-ka4wNpM5.js +1 -0
  185. package/public/assets/InstanceDetail-Cg1nS8HX.js +92 -0
  186. package/public/assets/Login-aPajuQzf.js +1 -0
  187. package/public/assets/NewInstance-Dd1ebNIx.js +1 -0
  188. package/public/assets/ProviderRecommendations-DFmADQ7V.js +1 -0
  189. package/public/assets/Settings-BYQnbLYL.js +1 -0
  190. package/public/assets/Setup-D05lwDOV.js +1 -0
  191. package/public/assets/WeixinLoginPanel-D89kdhP4.js +9 -0
  192. package/public/assets/index-HSXCsceK.css +1 -0
  193. package/public/assets/index-bnBu0nlQ.js +19 -0
  194. package/public/assets/registry-C_qeFTkZ.js +2 -0
  195. package/public/assets/usePolling-Bn93fe7M.js +1 -0
  196. package/public/assets/{vendor-i18n-ucpM0OR0.js → vendor-i18n-flxcMVeP.js} +2 -2
  197. package/public/assets/{vendor-react-Bk1hRGiY.js → vendor-react-ZC5T_huj.js} +7 -7
  198. package/public/index.html +4 -4
  199. package/scripts/check-app-spec.mjs +18 -4
  200. package/scripts/check-colima-launchd.mjs +230 -0
  201. package/scripts/check-new-file-tests.mjs +230 -0
  202. package/scripts/check-quarantine-expiry.mjs +105 -0
  203. package/scripts/perf/README.md +49 -0
  204. package/scripts/perf/auth.js +99 -0
  205. package/scripts/perf/config.js +63 -0
  206. package/scripts/perf/instances.js +143 -0
  207. package/scripts/perf/proxy.js +96 -0
  208. package/scripts/smoke/files-w1.sh +142 -0
  209. package/scripts/smoke-backend.mjs +122 -0
  210. package/scripts/smoke-post-publish.mjs +346 -0
  211. package/public/assets/Dashboard-rkWp-CXd.js +0 -1
  212. package/public/assets/HermesConfigForm-anDnwUp_.js +0 -4
  213. package/public/assets/InitPassword-ZU9_-hDr.js +0 -1
  214. package/public/assets/InstanceDetail-CN0FH1aw.js +0 -92
  215. package/public/assets/Login-BItXqYAJ.js +0 -1
  216. package/public/assets/NewInstance-BousE6kY.js +0 -1
  217. package/public/assets/ProviderRecommendations-DFYj7Fb6.js +0 -1
  218. package/public/assets/Settings-Bttc6QmM.js +0 -1
  219. package/public/assets/Setup-Bsxx1zgj.js +0 -1
  220. package/public/assets/WeixinLoginPanel-DPZpAKgO.js +0 -9
  221. package/public/assets/index-8xZy1z5k.css +0 -1
  222. package/public/assets/index-Dw3HhUYE.js +0 -19
  223. package/public/assets/input-paste-CrNVAyOy.js +0 -1
  224. package/public/assets/providers-DtNXh9JD.js +0 -1
  225. package/public/assets/registry-5s2UB6is.js +0 -2
  226. package/public/assets/usePolling-Do5Erqm_.js +0 -1
@@ -0,0 +1,50 @@
1
+ import type { OrganizeRule, OnCollision } from "./rules.js";
2
+ export interface OrganizeSuggestion {
3
+ id: string;
4
+ from: string;
5
+ to: string;
6
+ rule_id: string;
7
+ extracted: Record<string, string>;
8
+ selected: boolean;
9
+ applied?: boolean;
10
+ reverted?: boolean;
11
+ conflict?: "collision";
12
+ }
13
+ export interface OrganizeBatch {
14
+ id: string;
15
+ created_at: number;
16
+ status: "pending" | "applied" | "reverted" | "expired";
17
+ source_path: string;
18
+ rules_hash: string;
19
+ suggestions: OrganizeSuggestion[];
20
+ applied_at?: number;
21
+ expires_at?: number;
22
+ reverted_at?: number;
23
+ }
24
+ export interface ScanOptions {
25
+ /** Maximum file count to scan (per call). Default 10000. */
26
+ maxFiles?: number;
27
+ /** Skip files larger than this (bytes). Default Infinity. */
28
+ maxFileSize?: number;
29
+ }
30
+ /**
31
+ * Compile a small subset of glob syntax we actually use:
32
+ * - `*` one path segment, no /
33
+ * - `**` any number of segments, including /
34
+ * - `?` one char (no /)
35
+ * - `{a,b,c}` alternation (no nesting)
36
+ *
37
+ * We deliberately avoid bringing in minimatch/picomatch as a runtime
38
+ * dep — those libs are 100KB+ and we only need ~5% of their behaviour.
39
+ */
40
+ export declare function compileGlob(pattern: string): RegExp;
41
+ export declare function matchGlob(relPath: string, pattern: string): boolean;
42
+ export declare function matchMime(mime: string, patterns: string[]): boolean;
43
+ export declare function formatDate(d: Date, fmt: string): string;
44
+ /**
45
+ * Replace {var} placeholders in a template string. Returns null if any
46
+ * placeholder is unresolved — the caller should skip or warn the user.
47
+ */
48
+ export declare function interpolate(template: string, vars: Record<string, string>): string | null;
49
+ export declare function scan(filesRoot: string, rules: OrganizeRule[], rootRel?: string, opts?: ScanOptions): Promise<OrganizeBatch>;
50
+ export type { OnCollision };
@@ -0,0 +1,366 @@
1
+ /**
2
+ * File scanner for the W1.5 organizer.
3
+ *
4
+ * Walks a subtree of the user's files/, matches each file against the
5
+ * provided rules, and produces a list of move suggestions ready for
6
+ * the diff UI. No mutations: scanning is side-effect-free other than
7
+ * stat / EXIF reads.
8
+ *
9
+ * Performance budget: 5000 files in < 10 s on a Pi 5. We achieve this
10
+ * by:
11
+ * - skipping symlinks and hidden dirs entirely
12
+ * - reading EXIF only for image extensions, only for the first match
13
+ * (single 64KB read via exifr's selective parsing)
14
+ * - bailing on the first matching rule (rules are evaluated in order)
15
+ *
16
+ * The scanner is the only place that owns rule semantics. Both the API
17
+ * route (W1.5 PR-4) and the W7 Agent organizer call into it identically.
18
+ */
19
+ import * as fs from "node:fs";
20
+ import * as path from "node:path";
21
+ // exifr is loaded lazily (CJS interop quirks under Node ESM); see readExif.
22
+ import { resolveSafe, validateRelativePath, } from "../../utils/path-safety.js";
23
+ import { hashRules } from "./rules.js";
24
+ import { randomUUID } from "node:crypto";
25
+ // ── glob ─────────────────────────────────────────────
26
+ /**
27
+ * Compile a small subset of glob syntax we actually use:
28
+ * - `*` one path segment, no /
29
+ * - `**` any number of segments, including /
30
+ * - `?` one char (no /)
31
+ * - `{a,b,c}` alternation (no nesting)
32
+ *
33
+ * We deliberately avoid bringing in minimatch/picomatch as a runtime
34
+ * dep — those libs are 100KB+ and we only need ~5% of their behaviour.
35
+ */
36
+ export function compileGlob(pattern) {
37
+ // Expand {a,b,c} groups first. We support a single-level alternation,
38
+ // matching what the rule examples need.
39
+ const _expanded = [];
40
+ let buf = "";
41
+ let i = 0;
42
+ while (i < pattern.length) {
43
+ const c = pattern[i];
44
+ if (c === "{") {
45
+ const close = pattern.indexOf("}", i);
46
+ if (close === -1) {
47
+ buf += "\\{";
48
+ i++;
49
+ continue;
50
+ }
51
+ const alts = pattern.slice(i + 1, close).split(",");
52
+ const altRegex = "(?:" + alts.map(escapeRegexLiteral).join("|") + ")";
53
+ buf += altRegex;
54
+ i = close + 1;
55
+ continue;
56
+ }
57
+ if (c === "*") {
58
+ if (pattern[i + 1] === "*") {
59
+ // ** — match any chars including /
60
+ buf += ".*";
61
+ i += 2;
62
+ // collapse the slash that immediately follows ** so that
63
+ // "inbox/**/*.jpg" matches "inbox/x.jpg" too
64
+ if (pattern[i] === "/")
65
+ i++;
66
+ continue;
67
+ }
68
+ buf += "[^/]*";
69
+ i++;
70
+ continue;
71
+ }
72
+ if (c === "?") {
73
+ buf += "[^/]";
74
+ i++;
75
+ continue;
76
+ }
77
+ if (c === ".") {
78
+ buf += "\\.";
79
+ i++;
80
+ continue;
81
+ }
82
+ if ("+()^$|\\".includes(c)) {
83
+ buf += "\\" + c;
84
+ i++;
85
+ continue;
86
+ }
87
+ buf += c;
88
+ i++;
89
+ }
90
+ return new RegExp("^" + buf + "$", "i");
91
+ }
92
+ function escapeRegexLiteral(s) {
93
+ return s.replace(/[.+*?^$()[\]{}|\\]/g, "\\$&");
94
+ }
95
+ export function matchGlob(relPath, pattern) {
96
+ return compileGlob(pattern).test(relPath);
97
+ }
98
+ export function matchMime(mime, patterns) {
99
+ for (const p of patterns) {
100
+ if (p.endsWith("/*")) {
101
+ const prefix = p.slice(0, -1); // keep "image/"
102
+ if (mime.startsWith(prefix))
103
+ return true;
104
+ }
105
+ else if (p === mime)
106
+ return true;
107
+ }
108
+ return false;
109
+ }
110
+ const IMAGE_EXTS = new Set([
111
+ ".jpg",
112
+ ".jpeg",
113
+ ".png",
114
+ ".heic",
115
+ ".heif",
116
+ ".tiff",
117
+ ".tif",
118
+ ".webp",
119
+ ]);
120
+ const MIME_BY_EXT = {
121
+ ".jpg": "image/jpeg",
122
+ ".jpeg": "image/jpeg",
123
+ ".png": "image/png",
124
+ ".heic": "image/heic",
125
+ ".heif": "image/heif",
126
+ ".tiff": "image/tiff",
127
+ ".tif": "image/tiff",
128
+ ".webp": "image/webp",
129
+ ".gif": "image/gif",
130
+ ".bmp": "image/bmp",
131
+ ".pdf": "application/pdf",
132
+ ".mp4": "video/mp4",
133
+ ".mov": "video/quicktime",
134
+ ".webm": "video/webm",
135
+ ".mkv": "video/x-matroska",
136
+ ".mp3": "audio/mpeg",
137
+ ".wav": "audio/wav",
138
+ ".txt": "text/plain",
139
+ ".md": "text/markdown",
140
+ ".json": "application/json",
141
+ };
142
+ function inferMime(ext) {
143
+ return MIME_BY_EXT[ext.toLowerCase()] ?? "application/octet-stream";
144
+ }
145
+ let _exifrModule = null;
146
+ async function readExifSafe(absPath) {
147
+ // Only attempt EXIF on known image formats — exifr handles many but
148
+ // failed parses cost ~1ms each which adds up over thousands of files.
149
+ const ext = path.extname(absPath).toLowerCase();
150
+ if (!IMAGE_EXTS.has(ext))
151
+ return null;
152
+ try {
153
+ if (!_exifrModule) {
154
+ // Dynamic import to keep the test surface light when EXIF is irrelevant
155
+ _exifrModule = await import("exifr");
156
+ }
157
+ const exif = await _exifrModule.parse(absPath, {
158
+ pick: ["DateTimeOriginal", "CreateDate", "ModifyDate"],
159
+ });
160
+ return exif ?? null;
161
+ }
162
+ catch {
163
+ return null;
164
+ }
165
+ }
166
+ async function extractValue(spec, meta, exifCache) {
167
+ const from = spec.from;
168
+ if (from === "mtime") {
169
+ return formatDate(new Date(meta.mtimeSec * 1000), spec.format ?? "Y-MM-DD");
170
+ }
171
+ if (from === "ext") {
172
+ return meta.ext.replace(/^\./, "");
173
+ }
174
+ if (from === "basename")
175
+ return meta.basename;
176
+ if (from === "dirname")
177
+ return meta.dirname;
178
+ if (from.startsWith("exif.")) {
179
+ let exif = exifCache.get(meta.absPath);
180
+ if (exif === undefined) {
181
+ exif = await readExifSafe(meta.absPath);
182
+ exifCache.set(meta.absPath, exif);
183
+ }
184
+ if (!exif)
185
+ return null;
186
+ const key = from.slice(5);
187
+ let v = exif[key] ?? exif.DateTimeOriginal ?? exif.CreateDate;
188
+ if (v instanceof Date) {
189
+ return formatDate(v, spec.format ?? "Y-MM-DD");
190
+ }
191
+ if (typeof v === "string" && /^\d{4}/.test(v)) {
192
+ // exifr sometimes returns "YYYY:MM:DD HH:MM:SS" raw
193
+ const m = v.match(/^(\d{4})[:-](\d{2})[:-](\d{2})/);
194
+ if (m) {
195
+ const d = new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
196
+ return formatDate(d, spec.format ?? "Y-MM-DD");
197
+ }
198
+ }
199
+ return null;
200
+ }
201
+ return null;
202
+ }
203
+ export function formatDate(d, fmt) {
204
+ const Y = d.getFullYear().toString();
205
+ const MM = String(d.getMonth() + 1).padStart(2, "0");
206
+ const DD = String(d.getDate()).padStart(2, "0");
207
+ // We support the small set of placeholders that the rules use.
208
+ return fmt
209
+ .replace(/Y-MM-DD/g, `${Y}-${MM}-${DD}`)
210
+ .replace(/Y-MM/g, `${Y}-${MM}`)
211
+ .replace(/Y/g, Y)
212
+ .replace(/MM/g, MM)
213
+ .replace(/DD/g, DD);
214
+ }
215
+ // ── Interpolate ──────────────────────────────────────
216
+ /**
217
+ * Replace {var} placeholders in a template string. Returns null if any
218
+ * placeholder is unresolved — the caller should skip or warn the user.
219
+ */
220
+ export function interpolate(template, vars) {
221
+ let unresolved = false;
222
+ const out = template.replace(/\{([a-z_][a-z0-9_]*)\}/gi, (_m, name) => {
223
+ if (vars[name] === undefined) {
224
+ unresolved = true;
225
+ return "";
226
+ }
227
+ return vars[name];
228
+ });
229
+ return unresolved ? null : out;
230
+ }
231
+ // ── Scan ─────────────────────────────────────────────
232
+ const TRASH_DIR = ".trash";
233
+ const META_FILE = ".organize-rules.json";
234
+ const ORGANIZE_STATE_FILE = ".pending-organize.json";
235
+ export async function scan(filesRoot, rules, rootRel = "", opts = {}) {
236
+ const maxFiles = opts.maxFiles ?? 10000;
237
+ const maxFileSize = opts.maxFileSize ?? Number.POSITIVE_INFINITY;
238
+ // Validate the scan root using the same path-safety guards as Files.
239
+ if (rootRel !== "")
240
+ validateRelativePath(rootRel);
241
+ const rootAbs = rootRel ? resolveSafe(filesRoot, rootRel) : filesRoot;
242
+ const exifCache = new Map();
243
+ const suggestions = [];
244
+ const used = [0]; // boxed counter for recursion
245
+ async function walk(dirAbs) {
246
+ if (used[0] >= maxFiles)
247
+ return;
248
+ let entries;
249
+ try {
250
+ entries = fs.readdirSync(dirAbs, { withFileTypes: true });
251
+ }
252
+ catch {
253
+ return;
254
+ }
255
+ for (const ent of entries) {
256
+ if (used[0] >= maxFiles)
257
+ return;
258
+ // Skip hidden dirs (.trash, .organize-rules.json, etc.)
259
+ if (ent.name.startsWith("."))
260
+ continue;
261
+ const full = path.join(dirAbs, ent.name);
262
+ const rel = path.relative(filesRoot, full);
263
+ if (ent.isSymbolicLink())
264
+ continue;
265
+ if (ent.isDirectory()) {
266
+ await walk(full);
267
+ continue;
268
+ }
269
+ if (!ent.isFile())
270
+ continue;
271
+ let stat;
272
+ try {
273
+ stat = fs.statSync(full);
274
+ }
275
+ catch {
276
+ continue;
277
+ }
278
+ if (stat.size > maxFileSize)
279
+ continue;
280
+ used[0]++;
281
+ const ext = path.extname(rel).toLowerCase();
282
+ const meta = {
283
+ relPath: rel,
284
+ absPath: full,
285
+ size: stat.size,
286
+ mtimeSec: Math.floor(stat.mtimeMs / 1000),
287
+ ext,
288
+ basename: path.basename(rel),
289
+ dirname: path.dirname(rel),
290
+ mime: inferMime(ext),
291
+ };
292
+ const matched = await matchAndBuild(meta, rules, exifCache);
293
+ if (matched)
294
+ suggestions.push(matched);
295
+ }
296
+ }
297
+ await walk(rootAbs);
298
+ return {
299
+ id: randomUUID(),
300
+ created_at: Math.floor(Date.now() / 1000),
301
+ status: "pending",
302
+ source_path: rootRel,
303
+ rules_hash: hashRules(rules),
304
+ suggestions,
305
+ };
306
+ }
307
+ async function matchAndBuild(meta, rules, exifCache) {
308
+ for (const rule of rules) {
309
+ if (rule.enabled === false)
310
+ continue;
311
+ // Match
312
+ if (rule.match.glob && !matchGlob(meta.relPath, rule.match.glob))
313
+ continue;
314
+ if (rule.match.mime && !matchMime(meta.mime, rule.match.mime))
315
+ continue;
316
+ // Extract
317
+ const vars = {
318
+ basename: meta.basename,
319
+ ext: meta.ext.replace(/^\./, ""),
320
+ dirname: meta.dirname,
321
+ };
322
+ let extractFailed = false;
323
+ for (const ex of rule.extract ?? []) {
324
+ const v = await extractValue(ex, meta, exifCache);
325
+ if (v === null) {
326
+ extractFailed = true;
327
+ break;
328
+ }
329
+ vars[ex.as] = v;
330
+ }
331
+ if (extractFailed)
332
+ continue;
333
+ // Interpolate target
334
+ const to = interpolate(rule.action.move_to, vars);
335
+ if (to === null)
336
+ continue;
337
+ if (to === meta.relPath)
338
+ continue; // no-op
339
+ // Safety: ensure resolved target stays inside files/, no traversal,
340
+ // no system directories.
341
+ try {
342
+ validateRelativePath(to);
343
+ }
344
+ catch {
345
+ continue;
346
+ }
347
+ if (to.startsWith(`${TRASH_DIR}/`) ||
348
+ to === TRASH_DIR ||
349
+ to === META_FILE ||
350
+ to === ORGANIZE_STATE_FILE) {
351
+ continue;
352
+ }
353
+ if (to.startsWith(meta.relPath + "/"))
354
+ continue; // moving into self
355
+ return {
356
+ id: randomUUID(),
357
+ from: meta.relPath,
358
+ to,
359
+ rule_id: rule.id,
360
+ extracted: vars,
361
+ selected: true,
362
+ };
363
+ }
364
+ return null;
365
+ }
366
+ //# sourceMappingURL=scanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scanner.js","sourceRoot":"","sources":["../../../src/services/organize/scanner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { OrganizeBatch } from "./scanner.js";
2
+ export declare class BatchStore {
3
+ private file;
4
+ private batches;
5
+ constructor(stateDir: string);
6
+ private load;
7
+ private snapshot;
8
+ put(batch: OrganizeBatch): Promise<void>;
9
+ get(id: string): OrganizeBatch | undefined;
10
+ list(): OrganizeBatch[];
11
+ update(id: string, mutator: (b: OrganizeBatch) => void): Promise<OrganizeBatch | undefined>;
12
+ /** Test-only */
13
+ _clear(): void;
14
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Persistence layer for organize batches.
3
+ *
4
+ * In-memory primary, snapshot to disk after every mutation. The disk
5
+ * file is only used to survive a panel restart mid-undo-window; it is
6
+ * not the source of truth.
7
+ *
8
+ * File: ~/.jishushell/organize-batches.json
9
+ * {
10
+ * "version": 1,
11
+ * "batches": [ OrganizeBatch ... ]
12
+ * }
13
+ *
14
+ * We keep at most MAX_BATCHES (50) — older ones are evicted FIFO. This
15
+ * is plenty for the 30-second-undo-window pattern; nobody needs the
16
+ * 51st most recent batch.
17
+ */
18
+ import * as fs from "node:fs";
19
+ import * as path from "node:path";
20
+ import { safeWriteBuffer } from "../../utils/safe-write.js";
21
+ const MAX_BATCHES = 50;
22
+ export class BatchStore {
23
+ file;
24
+ batches = new Map();
25
+ constructor(stateDir) {
26
+ this.file = path.join(stateDir, "organize-batches.json");
27
+ this.load();
28
+ }
29
+ load() {
30
+ if (!fs.existsSync(this.file))
31
+ return;
32
+ try {
33
+ const raw = JSON.parse(fs.readFileSync(this.file, "utf8"));
34
+ if (Array.isArray(raw?.batches)) {
35
+ for (const b of raw.batches) {
36
+ if (b?.id)
37
+ this.batches.set(b.id, b);
38
+ }
39
+ }
40
+ }
41
+ catch (e) {
42
+ console.warn(`[organize] failed to load batches: ${e.message}`);
43
+ }
44
+ }
45
+ async snapshot() {
46
+ // Evict oldest if over cap (keep most recent MAX_BATCHES by created_at)
47
+ if (this.batches.size > MAX_BATCHES) {
48
+ const sorted = Array.from(this.batches.values()).sort((a, b) => b.created_at - a.created_at);
49
+ this.batches = new Map(sorted.slice(0, MAX_BATCHES).map((b) => [b.id, b]));
50
+ }
51
+ const payload = {
52
+ version: 1,
53
+ batches: Array.from(this.batches.values()),
54
+ };
55
+ const buf = Buffer.from(JSON.stringify(payload, null, 2) + "\n", "utf8");
56
+ fs.mkdirSync(path.dirname(this.file), { recursive: true });
57
+ await safeWriteBuffer(this.file, buf);
58
+ }
59
+ async put(batch) {
60
+ this.batches.set(batch.id, batch);
61
+ await this.snapshot();
62
+ }
63
+ get(id) {
64
+ return this.batches.get(id);
65
+ }
66
+ list() {
67
+ return Array.from(this.batches.values()).sort((a, b) => b.created_at - a.created_at);
68
+ }
69
+ async update(id, mutator) {
70
+ const b = this.batches.get(id);
71
+ if (!b)
72
+ return undefined;
73
+ mutator(b);
74
+ await this.snapshot();
75
+ return b;
76
+ }
77
+ /** Test-only */
78
+ _clear() {
79
+ this.batches.clear();
80
+ }
81
+ }
82
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../../src/services/organize/store.ts"],"names":[],"mappings":"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"}
@@ -26,6 +26,39 @@ export const LAUNCHD_PLIST = join(homedir(), "Library/LaunchAgents/com.jishushel
26
26
  const __filename = fileURLToPath(import.meta.url);
27
27
  // services/ is one level below the root; cli.js lives at ../cli.js
28
28
  export const CLI_PATH = join(dirname(__filename), "../cli.js");
29
+ const DEFAULT_PROCESS_PATH_ENTRIES = [
30
+ "/opt/homebrew/bin",
31
+ "/opt/homebrew/sbin",
32
+ "/usr/local/bin",
33
+ "/usr/local/sbin",
34
+ "/usr/bin",
35
+ "/bin",
36
+ "/usr/sbin",
37
+ "/sbin",
38
+ ];
39
+ function buildProcessPath(basePath) {
40
+ return [basePath ?? "", ...DEFAULT_PROCESS_PATH_ENTRIES, dirname(process.execPath)]
41
+ .flatMap((entry) => entry.split(":"))
42
+ .map((entry) => entry.trim())
43
+ .filter(Boolean)
44
+ .filter((entry, index, entries) => entries.indexOf(entry) === index)
45
+ .join(":");
46
+ }
47
+ function buildNodeProcessPath(basePath) {
48
+ return [dirname(process.execPath), basePath ?? "", ...DEFAULT_PROCESS_PATH_ENTRIES]
49
+ .flatMap((entry) => entry.split(":"))
50
+ .map((entry) => entry.trim())
51
+ .filter(Boolean)
52
+ .filter((entry, index, entries) => entries.indexOf(entry) === index)
53
+ .join(":");
54
+ }
55
+ function buildNodeProcessEnv(extraEnv = {}) {
56
+ return {
57
+ ...process.env,
58
+ ...extraEnv,
59
+ PATH: buildNodeProcessPath(process.env.PATH),
60
+ };
61
+ }
29
62
  export function detectRunMode() {
30
63
  if (process.platform !== "darwin") {
31
64
  if (existsSync(SYSTEMD_SERVICE)) {
@@ -209,11 +242,12 @@ function launchctlExec(action) {
209
242
  function processStart(port) {
210
243
  ensureDirHost(dirname(LOG_FILE));
211
244
  const logFd = openSync(LOG_FILE, "a");
245
+ const deterministicPath = buildProcessPath(process.env.PATH);
212
246
  const child = spawn(process.execPath, [CLI_PATH, "serve", "--port", String(port)], {
213
247
  detached: true,
214
248
  stdio: ["ignore", logFd, logFd],
215
249
  env: {
216
- PATH: process.env.PATH,
250
+ PATH: deterministicPath,
217
251
  HOME: process.env.HOME,
218
252
  USER: process.env.USER,
219
253
  LANG: process.env.LANG,
@@ -355,7 +389,9 @@ function getNpmRegistryUrl() {
355
389
  const nodeBinDir = dirname(process.execPath);
356
390
  const npmBin = join(nodeBinDir, "npm");
357
391
  const reg = execFileSync(npmBin, ["config", "get", "registry"], {
358
- encoding: "utf-8", timeout: 5000,
392
+ encoding: "utf-8",
393
+ timeout: 5000,
394
+ env: buildNodeProcessEnv(),
359
395
  }).trim();
360
396
  if (reg && reg !== "undefined")
361
397
  return reg.replace(/\/+$/, "");
@@ -416,10 +452,7 @@ export async function checkUpdate() {
416
452
  export async function upgradePackage() {
417
453
  const nodeBinDir = dirname(process.execPath);
418
454
  const npmBin = join(nodeBinDir, "npm");
419
- const childEnv = {
420
- ...process.env,
421
- PATH: `${nodeBinDir}${process.env.PATH ? `:${process.env.PATH}` : ""}`,
422
- };
455
+ const childEnv = buildNodeProcessEnv();
423
456
  try {
424
457
  const out = execFileSync(npmBin, ["install", "-g", "jishushell", "--registry", getNpmRegistryUrl() + "/"], { encoding: "utf-8", timeout: 120_000, stdio: ["ignore", "pipe", "pipe"], env: childEnv });
425
458
  _updateCache = null; // invalidate
@@ -431,11 +464,7 @@ export async function upgradePackage() {
431
464
  }
432
465
  }
433
466
  export function scheduleRestart(delayMs = 2000) {
434
- const nodeBinDir = dirname(process.execPath);
435
- const childEnv = {
436
- ...process.env,
437
- PATH: `${nodeBinDir}${process.env.PATH ? `:${process.env.PATH}` : ""}`,
438
- };
467
+ const childEnv = buildNodeProcessEnv();
439
468
  const delaySecs = Math.max(1, Math.round(delayMs / 1000));
440
469
  const child = spawn("sh", ["-c", `sleep ${delaySecs} && exec ${JSON.stringify(process.execPath)} ${JSON.stringify(CLI_PATH)} restart`], { detached: true, stdio: "ignore", env: childEnv });
441
470
  child.unref();