@ai-support-agent/cli 0.1.25-beta.2 → 0.1.26-beta.0

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.
@@ -1,13 +1,22 @@
1
1
  /**
2
2
  * Dockerfile synchronization to config directory
3
3
  *
4
- * Copies the bundled Dockerfile to ~/.ai-support-agent/ on first run
5
- * so users can customise it without modifying the package.
4
+ * Copies the bundled Dockerfile and entrypoint.sh as one snapshot to
5
+ * ~/.ai-support-agent/ using hash-based comparison so user customisations
6
+ * are preserved across updates. The Dockerfile COPYs docker/entrypoint.sh
7
+ * at image build time, so both files must always be updated together —
8
+ * a stale entrypoint.sh would otherwise be baked into the next image build.
6
9
  */
7
10
  /**
8
- * Copy the bundled Dockerfile (and entrypoint.sh) to the config directory
9
- * on first run so users can customise it.
10
- * Does NOT overwrite an existing file — user edits are preserved.
11
+ * Copy the bundled Dockerfile + entrypoint.sh pair to the config directory.
12
+ * The pair is treated as one sync unit: a combined hash over both files is
13
+ * stored in .dockerfile-sync-hash to detect user customisations:
14
+ * - No hash file or any destination file missing → overwrite the pair
15
+ * unconditionally and record the bundled combined hash
16
+ * - Hash matches current config pair → update if the bundled pair differs
17
+ * - Hash mismatch → user has customised either file, warn and skip
18
+ * entrypoint.sh may be absent from the bundle, in which case the Dockerfile
19
+ * alone forms the sync unit.
11
20
  */
12
21
  export declare function syncDockerfileToConfigDir(): void;
13
22
  //# sourceMappingURL=dockerfile-sync.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"dockerfile-sync.d.ts","sourceRoot":"","sources":["../../src/docker/dockerfile-sync.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAWH;;;;GAIG;AACH,wBAAgB,yBAAyB,IAAI,IAAI,CAuBhD"}
1
+ {"version":3,"file":"dockerfile-sync.d.ts","sourceRoot":"","sources":["../../src/docker/dockerfile-sync.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAwCH;;;;;;;;;;GAUG;AACH,wBAAgB,yBAAyB,IAAI,IAAI,CAwDhD"}
@@ -2,8 +2,11 @@
2
2
  /**
3
3
  * Dockerfile synchronization to config directory
4
4
  *
5
- * Copies the bundled Dockerfile to ~/.ai-support-agent/ on first run
6
- * so users can customise it without modifying the package.
5
+ * Copies the bundled Dockerfile and entrypoint.sh as one snapshot to
6
+ * ~/.ai-support-agent/ using hash-based comparison so user customisations
7
+ * are preserved across updates. The Dockerfile COPYs docker/entrypoint.sh
8
+ * at image build time, so both files must always be updated together —
9
+ * a stale entrypoint.sh would otherwise be baked into the next image build.
7
10
  */
8
11
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
12
  if (k2 === undefined) k2 = k;
@@ -40,6 +43,7 @@ var __importStar = (this && this.__importStar) || (function () {
40
43
  })();
41
44
  Object.defineProperty(exports, "__esModule", { value: true });
42
45
  exports.syncDockerfileToConfigDir = syncDockerfileToConfigDir;
46
+ const crypto = __importStar(require("crypto"));
43
47
  const fs = __importStar(require("fs"));
44
48
  const path = __importStar(require("path"));
45
49
  const config_manager_1 = require("../config-manager");
@@ -47,28 +51,83 @@ const dockerfile_path_1 = require("./dockerfile-path");
47
51
  const i18n_1 = require("../i18n");
48
52
  const logger_1 = require("../logger");
49
53
  const utils_1 = require("../utils");
54
+ /** Combined SHA-256 over the given files, hashed in order. */
55
+ function combinedSha256(filePaths) {
56
+ const hash = crypto.createHash('sha256');
57
+ for (const filePath of filePaths) {
58
+ const content = fs.readFileSync(filePath);
59
+ // Length prefix keeps file boundaries distinct so different splits of the
60
+ // same concatenated bytes cannot collide
61
+ hash.update(`${content.length}\n`);
62
+ hash.update(content);
63
+ }
64
+ return hash.digest('hex');
65
+ }
66
+ /** Copy every pair, creating the destination directories as needed. */
67
+ function copyPairs(configDir, pairs) {
68
+ fs.mkdirSync(configDir, { recursive: true, mode: 0o700 });
69
+ for (const pair of pairs) {
70
+ fs.mkdirSync(path.dirname(pair.dest), { recursive: true });
71
+ fs.copyFileSync(pair.src, pair.dest);
72
+ }
73
+ }
50
74
  /**
51
- * Copy the bundled Dockerfile (and entrypoint.sh) to the config directory
52
- * on first run so users can customise it.
53
- * Does NOT overwrite an existing file — user edits are preserved.
75
+ * Copy the bundled Dockerfile + entrypoint.sh pair to the config directory.
76
+ * The pair is treated as one sync unit: a combined hash over both files is
77
+ * stored in .dockerfile-sync-hash to detect user customisations:
78
+ * - No hash file or any destination file missing → overwrite the pair
79
+ * unconditionally and record the bundled combined hash
80
+ * - Hash matches current config pair → update if the bundled pair differs
81
+ * - Hash mismatch → user has customised either file, warn and skip
82
+ * entrypoint.sh may be absent from the bundle, in which case the Dockerfile
83
+ * alone forms the sync unit.
54
84
  */
55
85
  function syncDockerfileToConfigDir() {
56
86
  const configDir = (0, config_manager_1.getConfigDir)();
57
87
  const destDockerfile = path.join(configDir, 'Dockerfile');
58
- if (fs.existsSync(destDockerfile))
59
- return; // preserve existing file
88
+ const hashFile = path.join(configDir, '.dockerfile-sync-hash');
60
89
  try {
90
+ // getDockerfilePath throws when the bundled Dockerfile is missing —
91
+ // keep it inside the try so a broken bundle degrades to a warning
61
92
  const srcDockerfile = (0, dockerfile_path_1.getDockerfilePath)();
62
- fs.mkdirSync(configDir, { recursive: true, mode: 0o700 });
63
- fs.copyFileSync(srcDockerfile, destDockerfile);
64
- // Also copy entrypoint.sh which the bundled Dockerfile references via COPY
65
93
  const srcEntrypoint = path.join((0, dockerfile_path_1.getDockerContextDir)(), 'docker', 'entrypoint.sh');
66
- const destEntrypoint = path.join(configDir, 'docker', 'entrypoint.sh');
94
+ const pairs = [{ src: srcDockerfile, dest: destDockerfile }];
67
95
  if (fs.existsSync(srcEntrypoint)) {
68
- fs.mkdirSync(path.dirname(destEntrypoint), { recursive: true });
69
- fs.copyFileSync(srcEntrypoint, destEntrypoint);
96
+ pairs.push({ src: srcEntrypoint, dest: path.join(configDir, 'docker', 'entrypoint.sh') });
97
+ }
98
+ const destMissing = pairs.some((pair) => !fs.existsSync(pair.dest));
99
+ if (!fs.existsSync(hashFile) || destMissing) {
100
+ // First run, existing user without hash file, or part of the config
101
+ // pair was deleted — overwrite the pair unconditionally.
102
+ // Remove the hash file before copying: if any copy fails midway, the
103
+ // next run sees no hash file and retries this branch (self-heal)
104
+ fs.rmSync(hashFile, { force: true });
105
+ copyPairs(configDir, pairs);
106
+ (0, utils_1.atomicWriteFile)(hashFile, combinedSha256(pairs.map((pair) => pair.src)));
107
+ logger_1.logger.info((0, i18n_1.t)('docker.dockerfileSynced', { path: destDockerfile }));
108
+ return;
109
+ }
110
+ // trim() guards against a stray trailing newline in the hash file
111
+ const savedHash = fs.readFileSync(hashFile, 'utf-8').trim();
112
+ const currentConfigHash = combinedSha256(pairs.map((pair) => pair.dest));
113
+ if (savedHash !== currentConfigHash) {
114
+ // User has customised the Dockerfile or entrypoint.sh — warn and skip
115
+ // (no need to hash the bundled pair on this path)
116
+ logger_1.logger.warn((0, i18n_1.t)('docker.dockerfileCustomized', { path: destDockerfile }));
117
+ return;
118
+ }
119
+ // Not customised
120
+ const bundledHash = combinedSha256(pairs.map((pair) => pair.src));
121
+ if (bundledHash === currentConfigHash) {
122
+ // Already up to date — no-op
123
+ return;
70
124
  }
71
- logger_1.logger.info((0, i18n_1.t)('docker.dockerfileSynced', { path: destDockerfile }));
125
+ // Bundled pair is newer — update. Remove the hash file first so a
126
+ // partial copy is retried (not misjudged as customised) on the next run
127
+ fs.rmSync(hashFile, { force: true });
128
+ copyPairs(configDir, pairs);
129
+ (0, utils_1.atomicWriteFile)(hashFile, bundledHash);
130
+ logger_1.logger.info((0, i18n_1.t)('docker.dockerfileUpdated', { path: destDockerfile }));
72
131
  }
73
132
  catch (err) {
74
133
  logger_1.logger.warn((0, i18n_1.t)('docker.dockerfileSyncFailed', { message: (0, utils_1.getErrorMessage)(err) }));
@@ -1 +1 @@
1
- {"version":3,"file":"dockerfile-sync.js","sourceRoot":"","sources":["../../src/docker/dockerfile-sync.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgBH,8DAuBC;AArCD,uCAAwB;AACxB,2CAA4B;AAE5B,sDAAgD;AAChD,uDAA0E;AAC1E,kCAA2B;AAC3B,sCAAkC;AAClC,oCAA0C;AAE1C;;;;GAIG;AACH,SAAgB,yBAAyB;IACvC,MAAM,SAAS,GAAG,IAAA,6BAAY,GAAE,CAAA;IAChC,MAAM,cAAc,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC,CAAA;IAEzD,IAAI,EAAE,CAAC,UAAU,CAAC,cAAc,CAAC;QAAE,OAAM,CAAC,yBAAyB;IAEnE,IAAI,CAAC;QACH,MAAM,aAAa,GAAG,IAAA,mCAAiB,GAAE,CAAA;QACzC,EAAE,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;QACzD,EAAE,CAAC,YAAY,CAAC,aAAa,EAAE,cAAc,CAAC,CAAA;QAE9C,2EAA2E;QAC3E,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,IAAA,qCAAmB,GAAE,EAAE,QAAQ,EAAE,eAAe,CAAC,CAAA;QACjF,MAAM,cAAc,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,EAAE,eAAe,CAAC,CAAA;QACtE,IAAI,EAAE,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;YACjC,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;YAC/D,EAAE,CAAC,YAAY,CAAC,aAAa,EAAE,cAAc,CAAC,CAAA;QAChD,CAAC;QAED,eAAM,CAAC,IAAI,CAAC,IAAA,QAAC,EAAC,yBAAyB,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC,CAAC,CAAA;IACrE,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,eAAM,CAAC,IAAI,CAAC,IAAA,QAAC,EAAC,6BAA6B,EAAE,EAAE,OAAO,EAAE,IAAA,uBAAe,EAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAA;IAClF,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"dockerfile-sync.js","sourceRoot":"","sources":["../../src/docker/dockerfile-sync.ts"],"names":[],"mappings":"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"}
@@ -164,6 +164,8 @@
164
164
  "docker.loginStep3": "The token is long-lived. Add the export to .bashrc or .zshrc to persist it.",
165
165
  "docker.usingCustomDockerfile": "Using custom Dockerfile: {{path}}",
166
166
  "docker.dockerfileSynced": "Default Dockerfile written to: {{path}}",
167
+ "docker.dockerfileUpdated": "Bundled Dockerfile changed — updated config: {{path}}",
168
+ "docker.dockerfileCustomized": "Skipped Dockerfile update (customised). To apply the latest bundled version manually: {{path}}",
167
169
  "docker.dockerfileSyncFailed": "Failed to write Dockerfile to config dir: {{message}}",
168
170
  "docker.diffNoTarget": "No Dockerfile to diff. Specify a path or set dockerfilePath in config.",
169
171
  "docker.diffTargetNotFound": "Dockerfile not found: {{path}}",
@@ -164,6 +164,8 @@
164
164
  "docker.loginStep3": "トークンは長期間有効です。毎回設定が必要な場合は .bashrc や .zshrc に export を追加してください。",
165
165
  "docker.usingCustomDockerfile": "カスタムDockerfileを使用: {{path}}",
166
166
  "docker.dockerfileSynced": "デフォルトDockerfileを書き出しました: {{path}}",
167
+ "docker.dockerfileUpdated": "バンドル版Dockerfileが変更されたため設定ファイルを更新しました: {{path}}",
168
+ "docker.dockerfileCustomized": "Dockerfileがカスタマイズされているため自動更新をスキップしました。最新版を手動で反映してください: {{path}}",
167
169
  "docker.dockerfileSyncFailed": "Dockerfileの書き出しに失敗しました: {{message}}",
168
170
  "docker.diffNoTarget": "比較対象のDockerfileがありません。パスを指定するか設定にdockerfilePathを設定してください。",
169
171
  "docker.diffTargetNotFound": "Dockerfileが見つかりません: {{path}}",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-support-agent/cli",
3
- "version": "0.1.25-beta.2",
3
+ "version": "0.1.26-beta.0",
4
4
  "description": "AI Support Agent CLI client",
5
5
  "main": "dist/index.js",
6
6
  "bin": {