@chrysb/alphaclaw 0.3.4 → 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 (55) hide show
  1. package/bin/alphaclaw.js +82 -3
  2. package/lib/public/css/explorer.css +385 -9
  3. package/lib/public/css/theme.css +1 -1
  4. package/lib/public/js/app.js +102 -8
  5. package/lib/public/js/components/channels.js +1 -0
  6. package/lib/public/js/components/file-tree.js +74 -38
  7. package/lib/public/js/components/file-viewer/constants.js +6 -0
  8. package/lib/public/js/components/file-viewer/diff-viewer.js +46 -0
  9. package/lib/public/js/components/file-viewer/editor-surface.js +120 -0
  10. package/lib/public/js/components/file-viewer/frontmatter-panel.js +56 -0
  11. package/lib/public/js/components/file-viewer/index.js +164 -0
  12. package/lib/public/js/components/file-viewer/markdown-split-view.js +51 -0
  13. package/lib/public/js/components/file-viewer/media-preview.js +44 -0
  14. package/lib/public/js/components/file-viewer/scroll-sync.js +95 -0
  15. package/lib/public/js/components/file-viewer/sqlite-viewer.js +167 -0
  16. package/lib/public/js/components/file-viewer/status-banners.js +59 -0
  17. package/lib/public/js/components/file-viewer/storage.js +58 -0
  18. package/lib/public/js/components/file-viewer/toolbar.js +77 -0
  19. package/lib/public/js/components/file-viewer/use-editor-selection-restore.js +87 -0
  20. package/lib/public/js/components/file-viewer/use-file-diff.js +49 -0
  21. package/lib/public/js/components/file-viewer/use-file-loader.js +302 -0
  22. package/lib/public/js/components/file-viewer/use-file-viewer-draft-sync.js +32 -0
  23. package/lib/public/js/components/file-viewer/use-file-viewer-hotkeys.js +25 -0
  24. package/lib/public/js/components/file-viewer/use-file-viewer.js +379 -0
  25. package/lib/public/js/components/file-viewer/utils.js +11 -0
  26. package/lib/public/js/components/gateway.js +95 -48
  27. package/lib/public/js/components/icons.js +26 -0
  28. package/lib/public/js/components/sidebar-git-panel.js +219 -31
  29. package/lib/public/js/components/sidebar.js +1 -1
  30. package/lib/public/js/components/usage-tab.js +4 -1
  31. package/lib/public/js/components/watchdog-tab.js +6 -2
  32. package/lib/public/js/lib/api.js +31 -0
  33. package/lib/public/js/lib/browse-file-policies.js +34 -0
  34. package/lib/scripts/git +40 -0
  35. package/lib/scripts/git-askpass +6 -0
  36. package/lib/server/constants.js +8 -0
  37. package/lib/server/helpers.js +18 -5
  38. package/lib/server/internal-files-migration.js +93 -0
  39. package/lib/server/onboarding/cron.js +6 -4
  40. package/lib/server/onboarding/index.js +7 -0
  41. package/lib/server/onboarding/openclaw.js +6 -1
  42. package/lib/server/routes/browse/constants.js +51 -0
  43. package/lib/server/routes/browse/file-helpers.js +43 -0
  44. package/lib/server/routes/browse/git.js +131 -0
  45. package/lib/server/routes/browse/index.js +572 -0
  46. package/lib/server/routes/browse/path-utils.js +53 -0
  47. package/lib/server/routes/browse/sqlite.js +140 -0
  48. package/lib/server/routes/pairings.js +8 -2
  49. package/lib/server/routes/proxy.js +11 -5
  50. package/lib/server/routes/system.js +5 -1
  51. package/lib/server.js +7 -0
  52. package/lib/setup/core-prompts/TOOLS.md +0 -4
  53. package/package.json +1 -1
  54. package/lib/public/js/components/file-viewer.js +0 -864
  55. package/lib/server/routes/browse.js +0 -295
@@ -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
+ };
@@ -1,15 +1,21 @@
1
1
  const fs = require("fs");
2
2
  const { OPENCLAW_DIR } = require("../constants");
3
+ const { buildManagedPaths } = require("../internal-files-migration");
3
4
 
4
5
  const registerPairingRoutes = ({ app, clawCmd, isOnboarded, fsModule = fs, openclawDir = OPENCLAW_DIR }) => {
5
6
  let pairingCache = { pending: [], ts: 0 };
6
7
  const PAIRING_CACHE_TTL = 10000;
7
- const kCliAutoApproveMarkerPath = `${openclawDir}/.cli-device-auto-approved`;
8
+ const {
9
+ cliDeviceAutoApprovedPath: kCliAutoApproveMarkerPath,
10
+ internalDir: kManagedFilesDir,
11
+ } = buildManagedPaths({
12
+ openclawDir,
13
+ });
8
14
 
9
15
  const hasCliAutoApproveMarker = () => fsModule.existsSync(kCliAutoApproveMarkerPath);
10
16
 
11
17
  const writeCliAutoApproveMarker = () => {
12
- fsModule.mkdirSync(openclawDir, { recursive: true });
18
+ fsModule.mkdirSync(kManagedFilesDir, { recursive: true });
13
19
  fsModule.writeFileSync(
14
20
  kCliAutoApproveMarkerPath,
15
21
  JSON.stringify({ approvedAt: new Date().toISOString() }, null, 2),
@@ -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
  });
@@ -1,3 +1,5 @@
1
+ const { buildManagedPaths } = require("../internal-files-migration");
2
+
1
3
  const registerSystemRoutes = ({
2
4
  app,
3
5
  fs,
@@ -35,7 +37,9 @@ const registerSystemRoutes = ({
35
37
  kSystemVars.has(key) || kEnvVarsReservedForUserInput.has(key);
36
38
  const kSystemCronPath = "/etc/cron.d/openclaw-hourly-sync";
37
39
  const kSystemCronConfigPath = `${OPENCLAW_DIR}/cron/system-sync.json`;
38
- const kSystemCronScriptPath = `${OPENCLAW_DIR}/hourly-git-sync.sh`;
40
+ const { hourlyGitSyncPath: kSystemCronScriptPath } = buildManagedPaths({
41
+ openclawDir: OPENCLAW_DIR,
42
+ });
39
43
  const kDefaultSystemCronSchedule = "0 * * * *";
40
44
  const isValidCronSchedule = (value) =>
41
45
  typeof value === "string" && /^(\S+\s+){4}\S+$/.test(value.trim());
package/lib/server.js CHANGED
@@ -76,6 +76,9 @@ const {
76
76
  installControlUiSkill,
77
77
  syncBootstrapPromptFiles,
78
78
  } = require("./server/onboarding/workspace");
79
+ const {
80
+ migrateManagedInternalFiles,
81
+ } = require("./server/internal-files-migration");
79
82
  const { createTelegramApi } = require("./server/telegram-api");
80
83
  const { createDiscordApi } = require("./server/discord-api");
81
84
  const { createWatchdogNotifier } = require("./server/watchdog-notify");
@@ -100,6 +103,10 @@ const { PORT, GATEWAY_URL, kTrustProxyHops, SETUP_API_PREFIXES } = constants;
100
103
 
101
104
  startEnvWatcher();
102
105
  attachGatewaySignalHandlers();
106
+ migrateManagedInternalFiles({
107
+ fs,
108
+ openclawDir: constants.OPENCLAW_DIR,
109
+ });
103
110
 
104
111
  const app = express();
105
112
  app.set("trust proxy", kTrustProxyHops);
@@ -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.4",
3
+ "version": "0.3.5-beta.1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },