@chrysb/alphaclaw 0.3.5-beta.0 → 0.3.5-beta.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 (44) hide show
  1. package/bin/alphaclaw.js +65 -1
  2. package/lib/public/css/explorer.css +201 -6
  3. package/lib/public/js/app.js +45 -1
  4. package/lib/public/js/components/channels.js +1 -0
  5. package/lib/public/js/components/file-tree.js +56 -67
  6. package/lib/public/js/components/file-viewer/constants.js +6 -0
  7. package/lib/public/js/components/file-viewer/diff-viewer.js +46 -0
  8. package/lib/public/js/components/file-viewer/editor-surface.js +120 -0
  9. package/lib/public/js/components/file-viewer/frontmatter-panel.js +56 -0
  10. package/lib/public/js/components/file-viewer/index.js +164 -0
  11. package/lib/public/js/components/file-viewer/markdown-split-view.js +51 -0
  12. package/lib/public/js/components/file-viewer/media-preview.js +44 -0
  13. package/lib/public/js/components/file-viewer/scroll-sync.js +95 -0
  14. package/lib/public/js/components/file-viewer/sqlite-viewer.js +167 -0
  15. package/lib/public/js/components/file-viewer/status-banners.js +59 -0
  16. package/lib/public/js/components/file-viewer/storage.js +58 -0
  17. package/lib/public/js/components/file-viewer/toolbar.js +77 -0
  18. package/lib/public/js/components/file-viewer/use-editor-selection-restore.js +87 -0
  19. package/lib/public/js/components/file-viewer/use-file-diff.js +49 -0
  20. package/lib/public/js/components/file-viewer/use-file-loader.js +302 -0
  21. package/lib/public/js/components/file-viewer/use-file-viewer-draft-sync.js +32 -0
  22. package/lib/public/js/components/file-viewer/use-file-viewer-hotkeys.js +25 -0
  23. package/lib/public/js/components/file-viewer/use-file-viewer.js +379 -0
  24. package/lib/public/js/components/file-viewer/utils.js +11 -0
  25. package/lib/public/js/components/gateway.js +83 -30
  26. package/lib/public/js/components/icons.js +13 -0
  27. package/lib/public/js/components/sidebar-git-panel.js +72 -11
  28. package/lib/public/js/components/usage-tab.js +4 -1
  29. package/lib/public/js/components/watchdog-tab.js +6 -0
  30. package/lib/public/js/lib/api.js +16 -0
  31. package/lib/public/js/lib/browse-file-policies.js +34 -0
  32. package/lib/scripts/git +40 -0
  33. package/lib/scripts/git-askpass +6 -0
  34. package/lib/server/constants.js +8 -0
  35. package/lib/server/routes/browse/constants.js +51 -0
  36. package/lib/server/routes/browse/file-helpers.js +43 -0
  37. package/lib/server/routes/browse/git.js +131 -0
  38. package/lib/server/routes/{browse.js → browse/index.js} +290 -218
  39. package/lib/server/routes/browse/path-utils.js +53 -0
  40. package/lib/server/routes/browse/sqlite.js +140 -0
  41. package/lib/server/routes/proxy.js +11 -5
  42. package/lib/setup/core-prompts/TOOLS.md +0 -4
  43. package/package.json +1 -1
  44. package/lib/public/js/components/file-viewer.js +0 -1095
@@ -0,0 +1,53 @@
1
+ const path = require("path");
2
+
3
+ const normalizeRelativePath = (inputPath) => {
4
+ const rawPath = String(inputPath || "").trim();
5
+ if (!rawPath) return "";
6
+ return rawPath.replace(/\\/g, "/").replace(/^\/+/, "");
7
+ };
8
+
9
+ const normalizePolicyPath = (inputPath) =>
10
+ String(inputPath || "")
11
+ .replace(/\\/g, "/")
12
+ .replace(/^\.\/+/, "")
13
+ .replace(/^\/+/, "")
14
+ .trim()
15
+ .toLowerCase();
16
+
17
+ const resolveSafePath = (inputPath, kRootResolved, kRootWithSep, kRootDisplayName) => {
18
+ const relativePath = normalizeRelativePath(inputPath);
19
+ const absolutePath = path.resolve(kRootResolved, relativePath);
20
+ const isInsideRoot =
21
+ absolutePath === kRootResolved || absolutePath.startsWith(kRootWithSep);
22
+ if (!isInsideRoot) {
23
+ return { ok: false, error: `Path must stay within ${kRootDisplayName}` };
24
+ }
25
+ return { ok: true, relativePath, absolutePath };
26
+ };
27
+
28
+ const toRelativePath = (absolutePath, kRootResolved) => {
29
+ const relative = path.relative(kRootResolved, absolutePath);
30
+ return relative === "" ? "" : relative.split(path.sep).join("/");
31
+ };
32
+
33
+ const matchesPolicyPath = (policyPathSet, normalizedPath) => {
34
+ const safeNormalizedPath = String(normalizedPath || "").trim();
35
+ if (!safeNormalizedPath) return false;
36
+ for (const policyPath of policyPathSet) {
37
+ if (
38
+ safeNormalizedPath === policyPath ||
39
+ safeNormalizedPath.endsWith(`/${policyPath}`)
40
+ ) {
41
+ return true;
42
+ }
43
+ }
44
+ return false;
45
+ };
46
+
47
+ module.exports = {
48
+ normalizeRelativePath,
49
+ normalizePolicyPath,
50
+ resolveSafePath,
51
+ toRelativePath,
52
+ matchesPolicyPath,
53
+ };
@@ -0,0 +1,140 @@
1
+ const { kSqliteTablePageSize } = require("./constants");
2
+
3
+ const quoteSqliteIdentifier = (value) =>
4
+ `"${String(value || "").replaceAll('"', '""')}"`;
5
+
6
+ const readSqliteSummary = (targetPath) => {
7
+ let DatabaseSync = null;
8
+ try {
9
+ ({ DatabaseSync } = require("node:sqlite"));
10
+ } catch {
11
+ throw new Error("SQLite preview is unavailable on this Node runtime");
12
+ }
13
+ const database = new DatabaseSync(targetPath, { readOnly: true });
14
+ try {
15
+ const allObjects = database
16
+ .prepare(
17
+ `
18
+ SELECT name, type
19
+ FROM sqlite_master
20
+ WHERE type IN ('table', 'view')
21
+ AND name NOT LIKE 'sqlite_%'
22
+ ORDER BY type, name
23
+ `,
24
+ )
25
+ .all();
26
+ const maxObjects = 12;
27
+ const objects = allObjects.slice(0, maxObjects).map((entry) => {
28
+ const objectName = String(entry?.name || "").trim();
29
+ const objectType = String(entry?.type || "table").trim() || "table";
30
+ if (!objectName) return null;
31
+ const quotedName = quoteSqliteIdentifier(objectName);
32
+ const columns = database
33
+ .prepare(`PRAGMA table_info(${quotedName})`)
34
+ .all()
35
+ .map((column) => ({
36
+ name: String(column?.name || "").trim(),
37
+ type: String(column?.type || "").trim(),
38
+ notNull: Number(column?.notnull || 0) === 1,
39
+ isPrimaryKey: Number(column?.pk || 0) > 0,
40
+ }));
41
+ let sampleRows = [];
42
+ if (objectType === "table") {
43
+ try {
44
+ sampleRows = database.prepare(`SELECT * FROM ${quotedName} LIMIT 5`).all();
45
+ } catch {
46
+ sampleRows = [];
47
+ }
48
+ }
49
+ return {
50
+ name: objectName,
51
+ type: objectType,
52
+ columns,
53
+ sampleRows,
54
+ };
55
+ });
56
+ return {
57
+ totalObjects: allObjects.length,
58
+ truncated: allObjects.length > maxObjects,
59
+ objects: objects.filter(Boolean),
60
+ };
61
+ } finally {
62
+ database.close();
63
+ }
64
+ };
65
+
66
+ const clampSqlitePageValue = (value, fallbackValue, maxValue) => {
67
+ const parsedValue = Number.parseInt(String(value ?? ""), 10);
68
+ if (!Number.isFinite(parsedValue)) return fallbackValue;
69
+ return Math.max(0, Math.min(maxValue, parsedValue));
70
+ };
71
+
72
+ const readSqliteTableData = (targetPath, tableName, limit, offset) => {
73
+ const safeTableName = String(tableName || "").trim();
74
+ if (!safeTableName) {
75
+ return { ok: false, error: "table is required" };
76
+ }
77
+ let DatabaseSync = null;
78
+ try {
79
+ ({ DatabaseSync } = require("node:sqlite"));
80
+ } catch {
81
+ return { ok: false, error: "SQLite preview is unavailable on this Node runtime" };
82
+ }
83
+ const database = new DatabaseSync(targetPath, { readOnly: true });
84
+ try {
85
+ const quotedTableName = quoteSqliteIdentifier(safeTableName);
86
+ const tableExists = database
87
+ .prepare(
88
+ `
89
+ SELECT 1
90
+ FROM sqlite_master
91
+ WHERE type IN ('table', 'view')
92
+ AND name = ?
93
+ LIMIT 1
94
+ `,
95
+ )
96
+ .get(safeTableName);
97
+ if (!tableExists) {
98
+ return { ok: false, error: "table not found" };
99
+ }
100
+ const columns = database
101
+ .prepare(`PRAGMA table_info(${quotedTableName})`)
102
+ .all()
103
+ .map((column) => ({
104
+ name: String(column?.name || "").trim(),
105
+ type: String(column?.type || "").trim(),
106
+ notNull: Number(column?.notnull || 0) === 1,
107
+ isPrimaryKey: Number(column?.pk || 0) > 0,
108
+ }));
109
+ const totalRowsResult = database
110
+ .prepare(`SELECT COUNT(*) AS count FROM ${quotedTableName}`)
111
+ .get();
112
+ const totalRows = Number(totalRowsResult?.count || 0);
113
+ const safeLimit =
114
+ clampSqlitePageValue(limit, kSqliteTablePageSize, 200) || kSqliteTablePageSize;
115
+ const safeOffset = clampSqlitePageValue(offset, 0, Number.MAX_SAFE_INTEGER);
116
+ const rows = database
117
+ .prepare(`SELECT * FROM ${quotedTableName} LIMIT ? OFFSET ?`)
118
+ .all(safeLimit, safeOffset);
119
+ return {
120
+ ok: true,
121
+ table: safeTableName,
122
+ columns,
123
+ rows,
124
+ limit: safeLimit,
125
+ offset: safeOffset,
126
+ totalRows,
127
+ };
128
+ } catch (error) {
129
+ return { ok: false, error: error.message || "Could not read sqlite table" };
130
+ } finally {
131
+ database.close();
132
+ }
133
+ };
134
+
135
+ module.exports = {
136
+ quoteSqliteIdentifier,
137
+ readSqliteSummary,
138
+ clampSqlitePageValue,
139
+ readSqliteTableData,
140
+ };
@@ -5,20 +5,26 @@ const registerProxyRoutes = ({
5
5
  requireAuth,
6
6
  webhookMiddleware,
7
7
  }) => {
8
+ const kOpenClawPathPattern = /^\/openclaw\/.+/;
9
+ const kAssetsPathPattern = /^\/assets\/.+/;
10
+ const kHooksPathPattern = /^\/hooks\/.+/;
11
+ const kWebhookPathPattern = /^\/webhook\/.+/;
12
+ const kApiPathPattern = /^\/api\/.+/;
13
+
8
14
  app.all("/openclaw", requireAuth, (req, res) => {
9
15
  req.url = "/";
10
16
  proxy.web(req, res);
11
17
  });
12
- app.all("/openclaw/*", requireAuth, (req, res) => {
18
+ app.all(kOpenClawPathPattern, requireAuth, (req, res) => {
13
19
  req.url = req.url.replace(/^\/openclaw/, "");
14
20
  proxy.web(req, res);
15
21
  });
16
- app.all("/assets/*", requireAuth, (req, res) => proxy.web(req, res));
22
+ app.all(kAssetsPathPattern, requireAuth, (req, res) => proxy.web(req, res));
17
23
 
18
- app.all("/hooks/*", webhookMiddleware);
19
- app.all("/webhook/*", webhookMiddleware);
24
+ app.all(kHooksPathPattern, webhookMiddleware);
25
+ app.all(kWebhookPathPattern, webhookMiddleware);
20
26
 
21
- app.all("/api/*", (req, res) => {
27
+ app.all(kApiPathPattern, (req, res) => {
22
28
  if (SETUP_API_PREFIXES.some((p) => req.path.startsWith(p))) return;
23
29
  proxy.web(req, res);
24
30
  });
@@ -27,10 +27,6 @@ Google Workspace is connected via the **General** tab (`{{SETUP_UI_URL}}#general
27
27
 
28
28
  **Commit and push after every set of changes.** Your entire .openclaw directory (config, cron, workspace) is version controlled. This is how your work survives container restarts.
29
29
 
30
- ```bash
31
- alphaclaw git-sync --message "description"
32
- ```
33
-
34
30
  Never force push. Always pull before pushing if there might be remote changes.
35
31
  After pushing, include a link to the commit using the abbreviated hash: [abc1234](https://github.com/owner/repo/commit/abc1234) format. No backticks.
36
32
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chrysb/alphaclaw",
3
- "version": "0.3.5-beta.0",
3
+ "version": "0.3.5-beta.1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },