@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.
- package/bin/alphaclaw.js +82 -3
- package/lib/public/css/explorer.css +385 -9
- package/lib/public/css/theme.css +1 -1
- package/lib/public/js/app.js +102 -8
- package/lib/public/js/components/channels.js +1 -0
- package/lib/public/js/components/file-tree.js +74 -38
- package/lib/public/js/components/file-viewer/constants.js +6 -0
- package/lib/public/js/components/file-viewer/diff-viewer.js +46 -0
- package/lib/public/js/components/file-viewer/editor-surface.js +120 -0
- package/lib/public/js/components/file-viewer/frontmatter-panel.js +56 -0
- package/lib/public/js/components/file-viewer/index.js +164 -0
- package/lib/public/js/components/file-viewer/markdown-split-view.js +51 -0
- package/lib/public/js/components/file-viewer/media-preview.js +44 -0
- package/lib/public/js/components/file-viewer/scroll-sync.js +95 -0
- package/lib/public/js/components/file-viewer/sqlite-viewer.js +167 -0
- package/lib/public/js/components/file-viewer/status-banners.js +59 -0
- package/lib/public/js/components/file-viewer/storage.js +58 -0
- package/lib/public/js/components/file-viewer/toolbar.js +77 -0
- package/lib/public/js/components/file-viewer/use-editor-selection-restore.js +87 -0
- package/lib/public/js/components/file-viewer/use-file-diff.js +49 -0
- package/lib/public/js/components/file-viewer/use-file-loader.js +302 -0
- package/lib/public/js/components/file-viewer/use-file-viewer-draft-sync.js +32 -0
- package/lib/public/js/components/file-viewer/use-file-viewer-hotkeys.js +25 -0
- package/lib/public/js/components/file-viewer/use-file-viewer.js +379 -0
- package/lib/public/js/components/file-viewer/utils.js +11 -0
- package/lib/public/js/components/gateway.js +95 -48
- package/lib/public/js/components/icons.js +26 -0
- package/lib/public/js/components/sidebar-git-panel.js +219 -31
- package/lib/public/js/components/sidebar.js +1 -1
- package/lib/public/js/components/usage-tab.js +4 -1
- package/lib/public/js/components/watchdog-tab.js +6 -2
- package/lib/public/js/lib/api.js +31 -0
- package/lib/public/js/lib/browse-file-policies.js +34 -0
- package/lib/scripts/git +40 -0
- package/lib/scripts/git-askpass +6 -0
- package/lib/server/constants.js +8 -0
- package/lib/server/helpers.js +18 -5
- package/lib/server/internal-files-migration.js +93 -0
- package/lib/server/onboarding/cron.js +6 -4
- package/lib/server/onboarding/index.js +7 -0
- package/lib/server/onboarding/openclaw.js +6 -1
- package/lib/server/routes/browse/constants.js +51 -0
- package/lib/server/routes/browse/file-helpers.js +43 -0
- package/lib/server/routes/browse/git.js +131 -0
- package/lib/server/routes/browse/index.js +572 -0
- package/lib/server/routes/browse/path-utils.js +53 -0
- package/lib/server/routes/browse/sqlite.js +140 -0
- package/lib/server/routes/pairings.js +8 -2
- package/lib/server/routes/proxy.js +11 -5
- package/lib/server/routes/system.js +5 -1
- package/lib/server.js +7 -0
- package/lib/setup/core-prompts/TOOLS.md +0 -4
- package/package.json +1 -1
- package/lib/public/js/components/file-viewer.js +0 -864
- 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
|
|
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(
|
|
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(
|
|
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(
|
|
22
|
+
app.all(kAssetsPathPattern, requireAuth, (req, res) => proxy.web(req, res));
|
|
17
23
|
|
|
18
|
-
app.all(
|
|
19
|
-
app.all(
|
|
24
|
+
app.all(kHooksPathPattern, webhookMiddleware);
|
|
25
|
+
app.all(kWebhookPathPattern, webhookMiddleware);
|
|
20
26
|
|
|
21
|
-
app.all(
|
|
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 =
|
|
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
|
|