@chrysb/alphaclaw 0.9.13 → 0.9.15

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/README.md CHANGED
@@ -76,13 +76,14 @@ Or with Docker:
76
76
 
77
77
  ```dockerfile
78
78
  FROM node:22-slim
79
- RUN apt-get update && apt-get install -y git curl procps cron && rm -rf /var/lib/apt/lists/*
79
+ RUN apt-get update && apt-get install -y git curl procps cron tini && rm -rf /var/lib/apt/lists/*
80
80
  WORKDIR /app
81
81
  COPY package.json ./
82
82
  RUN npm install --omit=dev
83
83
  ENV PATH="/app/node_modules/.bin:$PATH"
84
84
  ENV ALPHACLAW_ROOT_DIR=/data
85
85
  EXPOSE 3000
86
+ ENTRYPOINT ["/usr/bin/tini", "--"]
86
87
  CMD ["alphaclaw", "start"]
87
88
  ```
88
89
 
package/bin/alphaclaw.js CHANGED
@@ -11,6 +11,10 @@ const {
11
11
  resolveRealGitPath,
12
12
  shouldRefreshHourlyGitSyncScript,
13
13
  } = require("../lib/cli/git-runtime");
14
+ const {
15
+ ensureMainUpstream,
16
+ restoreMissingOpenclawConfigFromRemote,
17
+ } = require("../lib/cli/openclaw-config-restore");
14
18
  const { buildSecretReplacements } = require("../lib/server/helpers");
15
19
  const {
16
20
  migrateManagedInternalFiles,
@@ -666,146 +670,60 @@ try {
666
670
  // ---------------------------------------------------------------------------
667
671
 
668
672
  const configPath = path.join(openclawDir, "openclaw.json");
669
-
670
- if (fs.existsSync(configPath)) {
671
- console.log("[alphaclaw] Config exists, reconciling channels...");
672
-
673
- const githubRepo = process.env.GITHUB_WORKSPACE_REPO;
674
- if (fs.existsSync(path.join(openclawDir, ".git"))) {
675
- if (githubRepo) {
676
- const repoUrl = githubRepo
677
- .replace(/^git@github\.com:/, "")
678
- .replace(/^https:\/\/github\.com\//, "")
679
- .replace(/\.git$/, "");
680
- const remoteUrl = `https://github.com/${repoUrl}.git`;
681
- try {
682
- execSync(`git remote set-url origin "${remoteUrl}"`, {
683
- cwd: openclawDir,
684
- stdio: "ignore",
685
- });
686
- console.log("[alphaclaw] Repo ready");
687
- } catch {}
688
- }
689
-
690
- // Migration path: scrub persisted PATs from existing GitHub origin URLs.
673
+ const githubRepo = process.env.GITHUB_WORKSPACE_REPO;
674
+
675
+ if (fs.existsSync(path.join(openclawDir, ".git"))) {
676
+ if (githubRepo) {
677
+ const repoUrl = githubRepo
678
+ .replace(/^git@github\.com:/, "")
679
+ .replace(/^https:\/\/github\.com\//, "")
680
+ .replace(/\.git$/, "");
681
+ const remoteUrl = `https://github.com/${repoUrl}.git`;
691
682
  try {
692
- const existingOrigin = execSync("git remote get-url origin", {
683
+ execSync(`git remote set-url origin "${remoteUrl}"`, {
693
684
  cwd: openclawDir,
694
- stdio: ["ignore", "pipe", "ignore"],
695
- encoding: "utf8",
696
- }).trim();
697
- const match = existingOrigin.match(
698
- /^https:\/\/[^/@]+@github\.com\/(.+)$/i,
699
- );
700
- if (match?.[1]) {
701
- const cleanedPath = String(match[1]).replace(/\.git$/i, "");
702
- const cleanedOrigin = `https://github.com/${cleanedPath}.git`;
703
- execSync(`git remote set-url origin "${cleanedOrigin}"`, {
704
- cwd: openclawDir,
705
- stdio: "ignore",
706
- });
707
- console.log("[alphaclaw] Scrubbed tokenized GitHub remote URL");
708
- }
685
+ stdio: "ignore",
686
+ });
687
+ console.log("[alphaclaw] Repo ready");
709
688
  } catch {}
689
+ }
710
690
 
711
- const bootRestoreConfigFromRemote = () => {
712
- const branch = (() => {
713
- try {
714
- return (
715
- String(
716
- execSync("git symbolic-ref --short HEAD", {
717
- cwd: openclawDir,
718
- stdio: ["ignore", "pipe", "ignore"],
719
- encoding: "utf8",
720
- }),
721
- ).trim() || "main"
722
- );
723
- } catch {
724
- return "main";
725
- }
726
- })();
727
- const githubToken = String(process.env.GITHUB_TOKEN || "").trim();
728
- const gitEnv = { ...process.env };
729
- const askPassPath = path.join(
730
- os.tmpdir(),
731
- `alphaclaw-boot-git-askpass-${process.pid}.sh`,
732
- );
733
- try {
734
- if (githubToken) {
735
- fs.writeFileSync(
736
- askPassPath,
737
- [
738
- "#!/usr/bin/env sh",
739
- 'case "$1" in',
740
- ' *Username*) echo "x-access-token" ;;',
741
- ' *Password*) echo "${GITHUB_TOKEN:-}" ;;',
742
- ' *) echo "" ;;',
743
- "esac",
744
- "",
745
- ].join("\n"),
746
- { mode: 0o700 },
747
- );
748
- gitEnv.GITHUB_TOKEN = githubToken;
749
- gitEnv.GIT_TERMINAL_PROMPT = "0";
750
- gitEnv.GIT_ASKPASS = askPassPath;
751
- }
752
- execSync(`git ls-remote --exit-code --heads origin "${branch}"`, {
753
- cwd: openclawDir,
754
- stdio: "ignore",
755
- env: gitEnv,
756
- });
757
- execSync(`git fetch --quiet origin "${branch}"`, {
758
- cwd: openclawDir,
759
- stdio: "ignore",
760
- env: gitEnv,
761
- });
762
- try {
763
- execSync("git show-ref --verify --quiet refs/heads/main", {
764
- cwd: openclawDir,
765
- stdio: "ignore",
766
- });
767
- try {
768
- execSync("git rev-parse --abbrev-ref --symbolic-full-name main@{upstream}", {
769
- cwd: openclawDir,
770
- stdio: "ignore",
771
- });
772
- } catch {
773
- execSync("git branch --set-upstream-to=origin/main main", {
774
- cwd: openclawDir,
775
- stdio: "ignore",
776
- env: gitEnv,
777
- });
778
- console.log("[alphaclaw] Set main upstream to origin/main");
779
- }
780
- } catch {}
781
- const remoteConfig = String(
782
- execSync(`git show "origin/${branch}:openclaw.json"`, {
783
- cwd: openclawDir,
784
- stdio: ["ignore", "pipe", "ignore"],
785
- encoding: "utf8",
786
- env: gitEnv,
787
- }),
788
- );
789
- if (remoteConfig.trim()) {
790
- fs.writeFileSync(configPath, remoteConfig);
791
- console.log(
792
- `[alphaclaw] Restored openclaw.json from origin/${branch}`,
793
- );
794
- }
795
- } catch (e) {
796
- console.log(
797
- `[alphaclaw] Remote config restore skipped: ${String(e.message || "").slice(0, 200)}`,
798
- );
799
- } finally {
800
- if (githubToken) {
801
- try {
802
- fs.rmSync(askPassPath, { force: true });
803
- } catch {}
804
- }
805
- }
806
- };
807
- bootRestoreConfigFromRemote();
691
+ // Migration path: scrub persisted PATs from existing GitHub origin URLs.
692
+ try {
693
+ const existingOrigin = execSync("git remote get-url origin", {
694
+ cwd: openclawDir,
695
+ stdio: ["ignore", "pipe", "ignore"],
696
+ encoding: "utf8",
697
+ }).trim();
698
+ const match = existingOrigin.match(/^https:\/\/[^/@]+@github\.com\/(.+)$/i);
699
+ if (match?.[1]) {
700
+ const cleanedPath = String(match[1]).replace(/\.git$/i, "");
701
+ const cleanedOrigin = `https://github.com/${cleanedPath}.git`;
702
+ execSync(`git remote set-url origin "${cleanedOrigin}"`, {
703
+ cwd: openclawDir,
704
+ stdio: "ignore",
705
+ });
706
+ console.log("[alphaclaw] Scrubbed tokenized GitHub remote URL");
707
+ }
708
+ } catch {}
709
+
710
+ restoreMissingOpenclawConfigFromRemote({
711
+ openclawDir,
712
+ configPath,
713
+ env: process.env,
714
+ });
715
+ if (
716
+ ensureMainUpstream({
717
+ openclawDir,
718
+ gitEnv: process.env,
719
+ })
720
+ ) {
721
+ console.log("[alphaclaw] Set main upstream to origin/main");
808
722
  }
723
+ }
724
+
725
+ if (fs.existsSync(configPath)) {
726
+ console.log("[alphaclaw] Config exists, reconciling channels...");
809
727
 
810
728
  try {
811
729
  const cfg = JSON.parse(fs.readFileSync(configPath, "utf8"));
@@ -0,0 +1,162 @@
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+ const os = require("os");
5
+ const path = require("path");
6
+ const { execSync } = require("child_process");
7
+
8
+ const kOpenclawConfigFile = "openclaw.json";
9
+
10
+ const quoteArg = (value) => `'${String(value || "").replace(/'/g, "'\"'\"'")}'`;
11
+
12
+ const resolveCurrentBranch = ({ execSyncImpl, openclawDir }) => {
13
+ try {
14
+ return (
15
+ String(
16
+ execSyncImpl("git symbolic-ref --short HEAD", {
17
+ cwd: openclawDir,
18
+ stdio: ["ignore", "pipe", "ignore"],
19
+ encoding: "utf8",
20
+ }),
21
+ ).trim() || "main"
22
+ );
23
+ } catch {
24
+ return "main";
25
+ }
26
+ };
27
+
28
+ const createGitEnv = ({ fsModule, osModule, env, processId }) => {
29
+ const githubToken = String(env.GITHUB_TOKEN || "").trim();
30
+ const gitEnv = { ...env };
31
+ if (!githubToken) {
32
+ return { gitEnv, askPassPath: "" };
33
+ }
34
+
35
+ const askPassPath = path.join(
36
+ osModule.tmpdir(),
37
+ `alphaclaw-boot-git-askpass-${processId}.sh`,
38
+ );
39
+ fsModule.writeFileSync(
40
+ askPassPath,
41
+ [
42
+ "#!/usr/bin/env sh",
43
+ 'case "$1" in',
44
+ ' *Username*) echo "x-access-token" ;;',
45
+ ' *Password*) echo "${GITHUB_TOKEN:-}" ;;',
46
+ ' *) echo "" ;;',
47
+ "esac",
48
+ "",
49
+ ].join("\n"),
50
+ { mode: 0o700 },
51
+ );
52
+ gitEnv.GITHUB_TOKEN = githubToken;
53
+ gitEnv.GIT_TERMINAL_PROMPT = "0";
54
+ gitEnv.GIT_ASKPASS = askPassPath;
55
+ return { gitEnv, askPassPath };
56
+ };
57
+
58
+ const restoreMissingOpenclawConfigFromRemote = ({
59
+ fsModule = fs,
60
+ osModule = os,
61
+ execSyncImpl = execSync,
62
+ env = process.env,
63
+ logger = console,
64
+ processId = process.pid,
65
+ openclawDir,
66
+ configPath = path.join(openclawDir || "", kOpenclawConfigFile),
67
+ } = {}) => {
68
+ if (!openclawDir) {
69
+ throw new Error("openclawDir is required");
70
+ }
71
+
72
+ if (fsModule.existsSync(configPath)) {
73
+ logger.log(
74
+ "[alphaclaw] Remote config restore skipped: local openclaw.json already exists",
75
+ );
76
+ return { restored: false, skipped: true, reason: "exists" };
77
+ }
78
+
79
+ const branch = resolveCurrentBranch({ execSyncImpl, openclawDir });
80
+ const { gitEnv, askPassPath } = createGitEnv({
81
+ fsModule,
82
+ osModule,
83
+ env,
84
+ processId,
85
+ });
86
+
87
+ try {
88
+ execSyncImpl(
89
+ `git ls-remote --exit-code --heads origin ${quoteArg(branch)}`,
90
+ {
91
+ cwd: openclawDir,
92
+ stdio: "ignore",
93
+ env: gitEnv,
94
+ },
95
+ );
96
+ execSyncImpl(`git fetch --quiet origin ${quoteArg(branch)}`, {
97
+ cwd: openclawDir,
98
+ stdio: "ignore",
99
+ env: gitEnv,
100
+ });
101
+ const remoteConfig = String(
102
+ execSyncImpl(`git show ${quoteArg(`origin/${branch}:openclaw.json`)}`, {
103
+ cwd: openclawDir,
104
+ stdio: ["ignore", "pipe", "ignore"],
105
+ encoding: "utf8",
106
+ env: gitEnv,
107
+ }),
108
+ );
109
+ if (!remoteConfig.trim()) {
110
+ logger.log("[alphaclaw] Remote config restore skipped: remote config empty");
111
+ return { restored: false, skipped: true, reason: "empty_remote", branch };
112
+ }
113
+ fsModule.writeFileSync(configPath, remoteConfig);
114
+ logger.log(`[alphaclaw] Restored missing openclaw.json from origin/${branch}`);
115
+ return { restored: true, skipped: false, reason: "missing", branch };
116
+ } catch (e) {
117
+ logger.log(
118
+ `[alphaclaw] Remote config restore skipped: ${String(e.message || "").slice(0, 200)}`,
119
+ );
120
+ return {
121
+ restored: false,
122
+ skipped: true,
123
+ reason: "error",
124
+ branch,
125
+ error: e,
126
+ };
127
+ } finally {
128
+ if (askPassPath) {
129
+ try {
130
+ fsModule.rmSync(askPassPath, { force: true });
131
+ } catch {}
132
+ }
133
+ }
134
+ };
135
+
136
+ const ensureMainUpstream = ({ execSyncImpl = execSync, openclawDir, gitEnv }) => {
137
+ try {
138
+ execSyncImpl("git show-ref --verify --quiet refs/heads/main", {
139
+ cwd: openclawDir,
140
+ stdio: "ignore",
141
+ });
142
+ try {
143
+ execSyncImpl("git rev-parse --abbrev-ref --symbolic-full-name main@{upstream}", {
144
+ cwd: openclawDir,
145
+ stdio: "ignore",
146
+ });
147
+ } catch {
148
+ execSyncImpl("git branch --set-upstream-to=origin/main main", {
149
+ cwd: openclawDir,
150
+ stdio: "ignore",
151
+ env: gitEnv,
152
+ });
153
+ return true;
154
+ }
155
+ } catch {}
156
+ return false;
157
+ };
158
+
159
+ module.exports = {
160
+ ensureMainUpstream,
161
+ restoreMissingOpenclawConfigFromRemote,
162
+ };
@@ -189,10 +189,12 @@
189
189
 
190
190
  .sidebar-agent-emoji {
191
191
  flex: 0 0 auto;
192
- width: 14px;
192
+ min-width: 14px;
193
+ max-width: 1.5em;
193
194
  line-height: 1;
194
195
  font-size: 14px;
195
196
  text-align: center;
197
+ overflow: hidden;
196
198
  }
197
199
 
198
200
  .sidebar-agent-name {