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.
- package/Dockerfile.hermes-slim +2 -5
- package/apps/anythingllm-container.yaml +287 -0
- package/apps/browserless-chromium-container.yaml +18 -6
- package/apps/filebrowser-container.yaml +164 -0
- package/apps/ollama-binary.yaml +44 -0
- package/apps/ollama-with-hollama-binary.yaml +45 -1
- package/apps/openclaw-binary.yaml +8 -0
- package/apps/openclaw-container.yaml +9 -1
- package/apps/openclaw-with-searxng-container.yaml +4 -0
- package/apps/searxng-container.yaml +5 -4
- package/apps/weknora-container.yaml +471 -0
- package/dist/cli/doctor.js +144 -16
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/panel.js.map +1 -1
- package/dist/config.d.ts +19 -0
- package/dist/config.js +99 -1
- package/dist/config.js.map +1 -1
- package/dist/install.js +4 -4
- package/dist/install.js.map +1 -1
- package/dist/routes/auth.js +2 -2
- package/dist/routes/auth.js.map +1 -1
- package/dist/routes/backup.js +64 -11
- package/dist/routes/backup.js.map +1 -1
- package/dist/routes/external-mounts.d.ts +17 -0
- package/dist/routes/external-mounts.js +73 -0
- package/dist/routes/external-mounts.js.map +1 -0
- package/dist/routes/file-mounts.d.ts +13 -0
- package/dist/routes/file-mounts.js +90 -0
- package/dist/routes/file-mounts.js.map +1 -0
- package/dist/routes/files-organize.d.ts +28 -0
- package/dist/routes/files-organize.js +167 -0
- package/dist/routes/files-organize.js.map +1 -0
- package/dist/routes/files.d.ts +31 -0
- package/dist/routes/files.js +321 -0
- package/dist/routes/files.js.map +1 -0
- package/dist/routes/instances.js +87 -12
- package/dist/routes/instances.js.map +1 -1
- package/dist/routes/internal.d.ts +2 -0
- package/dist/routes/internal.js +59 -0
- package/dist/routes/internal.js.map +1 -0
- package/dist/routes/llm.js +29 -0
- package/dist/routes/llm.js.map +1 -1
- package/dist/routes/setup.js +9 -9
- package/dist/routes/setup.js.map +1 -1
- package/dist/routes/system.js +1 -1
- package/dist/routes/system.js.map +1 -1
- package/dist/routes/webdav.d.ts +17 -0
- package/dist/routes/webdav.js +114 -0
- package/dist/routes/webdav.js.map +1 -0
- package/dist/server.js +358 -6
- package/dist/server.js.map +1 -1
- package/dist/services/agent-apps/catalog.d.ts +3 -0
- package/dist/services/agent-apps/catalog.js +40 -13
- package/dist/services/agent-apps/catalog.js.map +1 -1
- package/dist/services/agent-apps/installers/shell-script.d.ts +1 -1
- package/dist/services/agent-apps/installers/shell-script.js +19 -2
- package/dist/services/agent-apps/installers/shell-script.js.map +1 -1
- package/dist/services/agent-apps/types.d.ts +3 -0
- package/dist/services/app/app-compiler.d.ts +1 -1
- package/dist/services/app/app-compiler.js +5 -5
- package/dist/services/app/app-compiler.js.map +1 -1
- package/dist/services/app/app-manager.d.ts +9 -0
- package/dist/services/app/app-manager.js +248 -43
- package/dist/services/app/app-manager.js.map +1 -1
- package/dist/services/app/custom-manager.js.map +1 -1
- package/dist/services/app/hermes-agent-manager.js +1 -0
- package/dist/services/app/hermes-agent-manager.js.map +1 -1
- package/dist/services/app/ollama-manager.js +1 -1
- package/dist/services/app/ollama-manager.js.map +1 -1
- package/dist/services/app/openclaw-manager.js +37 -5
- package/dist/services/app/openclaw-manager.js.map +1 -1
- package/dist/services/app/platform-transform.d.ts +32 -0
- package/dist/services/app/platform-transform.js +65 -0
- package/dist/services/app/platform-transform.js.map +1 -0
- package/dist/services/app-passwords.d.ts +61 -0
- package/dist/services/app-passwords.js +173 -0
- package/dist/services/app-passwords.js.map +1 -0
- package/dist/services/backup-manager.d.ts +11 -0
- package/dist/services/backup-manager.js +220 -8
- package/dist/services/backup-manager.js.map +1 -1
- package/dist/services/capability-endpoint-validator.js +26 -7
- package/dist/services/capability-endpoint-validator.js.map +1 -1
- package/dist/services/connection-apply.d.ts +2 -0
- package/dist/services/connection-apply.js +55 -1
- package/dist/services/connection-apply.js.map +1 -1
- package/dist/services/connection-resolver.js +1 -1
- package/dist/services/connection-resolver.js.map +1 -1
- package/dist/services/connection-transactor.d.ts +2 -0
- package/dist/services/connection-transactor.js +12 -2
- package/dist/services/connection-transactor.js.map +1 -1
- package/dist/services/external-mounts.d.ts +40 -0
- package/dist/services/external-mounts.js +187 -0
- package/dist/services/external-mounts.js.map +1 -0
- package/dist/services/files-manager.d.ts +252 -0
- package/dist/services/files-manager.js +1075 -0
- package/dist/services/files-manager.js.map +1 -0
- package/dist/services/files-mounts.d.ts +42 -0
- package/dist/services/files-mounts.js +207 -0
- package/dist/services/files-mounts.js.map +1 -0
- package/dist/services/instance-manager.js +90 -32
- package/dist/services/instance-manager.js.map +1 -1
- package/dist/services/llm-proxy/index.d.ts +28 -0
- package/dist/services/llm-proxy/index.js +76 -3
- package/dist/services/llm-proxy/index.js.map +1 -1
- package/dist/services/llm-proxy/ssrf.js +6 -2
- package/dist/services/llm-proxy/ssrf.js.map +1 -1
- package/dist/services/llm-proxy/validate-key.d.ts +41 -0
- package/dist/services/llm-proxy/validate-key.js +672 -0
- package/dist/services/llm-proxy/validate-key.js.map +1 -0
- package/dist/services/macos-launchd.d.ts +89 -0
- package/dist/services/macos-launchd.js +273 -0
- package/dist/services/macos-launchd.js.map +1 -0
- package/dist/services/nomad-manager.d.ts +11 -0
- package/dist/services/nomad-manager.js +343 -98
- package/dist/services/nomad-manager.js.map +1 -1
- package/dist/services/organize/applier.d.ts +46 -0
- package/dist/services/organize/applier.js +218 -0
- package/dist/services/organize/applier.js.map +1 -0
- package/dist/services/organize/rules.d.ts +57 -0
- package/dist/services/organize/rules.js +286 -0
- package/dist/services/organize/rules.js.map +1 -0
- package/dist/services/organize/scanner.d.ts +50 -0
- package/dist/services/organize/scanner.js +366 -0
- package/dist/services/organize/scanner.js.map +1 -0
- package/dist/services/organize/store.d.ts +14 -0
- package/dist/services/organize/store.js +82 -0
- package/dist/services/organize/store.js.map +1 -0
- package/dist/services/panel-manager.js +40 -11
- package/dist/services/panel-manager.js.map +1 -1
- package/dist/services/process-manager.js +3 -2
- package/dist/services/process-manager.js.map +1 -1
- package/dist/services/runtime/adapters/custom.js +56 -0
- package/dist/services/runtime/adapters/custom.js.map +1 -1
- package/dist/services/runtime/adapters/hermes.d.ts +4 -3
- package/dist/services/runtime/adapters/hermes.js +166 -64
- package/dist/services/runtime/adapters/hermes.js.map +1 -1
- package/dist/services/runtime/adapters/openclaw-routes.d.ts +8 -2
- package/dist/services/runtime/adapters/openclaw-routes.js +68 -0
- package/dist/services/runtime/adapters/openclaw-routes.js.map +1 -1
- package/dist/services/runtime/adapters/openclaw.d.ts +118 -0
- package/dist/services/runtime/adapters/openclaw.js +1459 -49
- package/dist/services/runtime/adapters/openclaw.js.map +1 -1
- package/dist/services/runtime/instance.d.ts +1 -1
- package/dist/services/runtime/instance.js +1 -1
- package/dist/services/runtime/instance.js.map +1 -1
- package/dist/services/runtime/mcp-shims/anythingllm-shim.d.ts +46 -0
- package/dist/services/runtime/mcp-shims/anythingllm-shim.js +281 -0
- package/dist/services/runtime/mcp-shims/anythingllm-shim.js.map +1 -0
- package/dist/services/runtime/mcp-shims/drive-shim.d.ts +54 -0
- package/dist/services/runtime/mcp-shims/drive-shim.js +489 -0
- package/dist/services/runtime/mcp-shims/drive-shim.js.map +1 -0
- package/dist/services/runtime/types.d.ts +31 -0
- package/dist/services/setup-manager.js +190 -68
- package/dist/services/setup-manager.js.map +1 -1
- package/dist/services/suggestions.js.map +1 -1
- package/dist/services/update-manager.js +32 -14
- package/dist/services/update-manager.js.map +1 -1
- package/dist/services/webdav/server.d.ts +24 -0
- package/dist/services/webdav/server.js +420 -0
- package/dist/services/webdav/server.js.map +1 -0
- package/dist/services/webdav/xml-builder.d.ts +73 -0
- package/dist/services/webdav/xml-builder.js +156 -0
- package/dist/services/webdav/xml-builder.js.map +1 -0
- package/dist/services/workspace-builder.d.ts +29 -0
- package/dist/services/workspace-builder.js +188 -0
- package/dist/services/workspace-builder.js.map +1 -0
- package/dist/types.d.ts +61 -0
- package/dist/utils/path-locks.d.ts +30 -0
- package/dist/utils/path-locks.js +63 -0
- package/dist/utils/path-locks.js.map +1 -0
- package/dist/utils/path-safety.d.ts +41 -0
- package/dist/utils/path-safety.js +119 -0
- package/dist/utils/path-safety.js.map +1 -0
- package/dist/utils/safe-write.d.ts +24 -0
- package/dist/utils/safe-write.js +82 -0
- package/dist/utils/safe-write.js.map +1 -0
- package/install/jishu-install.sh +247 -35
- package/install/jishu-uninstall.sh +45 -5
- package/package.json +20 -2
- package/public/assets/ApiKeyField-CvyAOcJS.js +1 -0
- package/public/assets/Dashboard-AuJESBlJ.js +1 -0
- package/public/assets/{HermesChatPanel-_GHoklgo.js → HermesChatPanel-CByPREwb.js} +1 -1
- package/public/assets/HermesConfigForm-DRda8FKX.js +4 -0
- package/public/assets/InitPassword-ka4wNpM5.js +1 -0
- package/public/assets/InstanceDetail-Cg1nS8HX.js +92 -0
- package/public/assets/Login-aPajuQzf.js +1 -0
- package/public/assets/NewInstance-Dd1ebNIx.js +1 -0
- package/public/assets/ProviderRecommendations-DFmADQ7V.js +1 -0
- package/public/assets/Settings-BYQnbLYL.js +1 -0
- package/public/assets/Setup-D05lwDOV.js +1 -0
- package/public/assets/WeixinLoginPanel-D89kdhP4.js +9 -0
- package/public/assets/index-HSXCsceK.css +1 -0
- package/public/assets/index-bnBu0nlQ.js +19 -0
- package/public/assets/registry-C_qeFTkZ.js +2 -0
- package/public/assets/usePolling-Bn93fe7M.js +1 -0
- package/public/assets/{vendor-i18n-ucpM0OR0.js → vendor-i18n-flxcMVeP.js} +2 -2
- package/public/assets/{vendor-react-Bk1hRGiY.js → vendor-react-ZC5T_huj.js} +7 -7
- package/public/index.html +4 -4
- package/scripts/check-app-spec.mjs +18 -4
- package/scripts/check-colima-launchd.mjs +230 -0
- package/scripts/check-new-file-tests.mjs +230 -0
- package/scripts/check-quarantine-expiry.mjs +105 -0
- package/scripts/perf/README.md +49 -0
- package/scripts/perf/auth.js +99 -0
- package/scripts/perf/config.js +63 -0
- package/scripts/perf/instances.js +143 -0
- package/scripts/perf/proxy.js +96 -0
- package/scripts/smoke/files-w1.sh +142 -0
- package/scripts/smoke-backend.mjs +122 -0
- package/scripts/smoke-post-publish.mjs +346 -0
- package/public/assets/Dashboard-rkWp-CXd.js +0 -1
- package/public/assets/HermesConfigForm-anDnwUp_.js +0 -4
- package/public/assets/InitPassword-ZU9_-hDr.js +0 -1
- package/public/assets/InstanceDetail-CN0FH1aw.js +0 -92
- package/public/assets/Login-BItXqYAJ.js +0 -1
- package/public/assets/NewInstance-BousE6kY.js +0 -1
- package/public/assets/ProviderRecommendations-DFYj7Fb6.js +0 -1
- package/public/assets/Settings-Bttc6QmM.js +0 -1
- package/public/assets/Setup-Bsxx1zgj.js +0 -1
- package/public/assets/WeixinLoginPanel-DPZpAKgO.js +0 -9
- package/public/assets/index-8xZy1z5k.css +0 -1
- package/public/assets/index-Dw3HhUYE.js +0 -19
- package/public/assets/input-paste-CrNVAyOy.js +0 -1
- package/public/assets/providers-DtNXh9JD.js +0 -1
- package/public/assets/registry-5s2UB6is.js +0 -2
- 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:
|
|
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",
|
|
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
|
|
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();
|