@tengx5383/aitool-sync-cli 0.1.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.
Files changed (153) hide show
  1. package/.claude/settings.local.json +8 -0
  2. package/README.md +203 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +87 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/commands/add.d.ts +2 -0
  8. package/dist/commands/add.d.ts.map +1 -0
  9. package/dist/commands/add.js +24 -0
  10. package/dist/commands/add.js.map +1 -0
  11. package/dist/commands/doctor.d.ts +2 -0
  12. package/dist/commands/doctor.d.ts.map +1 -0
  13. package/dist/commands/doctor.js +89 -0
  14. package/dist/commands/doctor.js.map +1 -0
  15. package/dist/commands/init.d.ts +7 -0
  16. package/dist/commands/init.d.ts.map +1 -0
  17. package/dist/commands/init.js +105 -0
  18. package/dist/commands/init.js.map +1 -0
  19. package/dist/commands/list.d.ts +2 -0
  20. package/dist/commands/list.d.ts.map +1 -0
  21. package/dist/commands/list.js +22 -0
  22. package/dist/commands/list.js.map +1 -0
  23. package/dist/commands/pull.d.ts +6 -0
  24. package/dist/commands/pull.d.ts.map +1 -0
  25. package/dist/commands/pull.js +52 -0
  26. package/dist/commands/pull.js.map +1 -0
  27. package/dist/commands/push.d.ts +6 -0
  28. package/dist/commands/push.d.ts.map +1 -0
  29. package/dist/commands/push.js +60 -0
  30. package/dist/commands/push.js.map +1 -0
  31. package/dist/commands/remove.d.ts +2 -0
  32. package/dist/commands/remove.d.ts.map +1 -0
  33. package/dist/commands/remove.js +20 -0
  34. package/dist/commands/remove.js.map +1 -0
  35. package/dist/commands/status.d.ts +2 -0
  36. package/dist/commands/status.d.ts.map +1 -0
  37. package/dist/commands/status.js +46 -0
  38. package/dist/commands/status.js.map +1 -0
  39. package/dist/config/config-file.d.ts +22 -0
  40. package/dist/config/config-file.d.ts.map +1 -0
  41. package/dist/config/config-file.js +86 -0
  42. package/dist/config/config-file.js.map +1 -0
  43. package/dist/config/schema.d.ts +101 -0
  44. package/dist/config/schema.d.ts.map +1 -0
  45. package/dist/config/schema.js +24 -0
  46. package/dist/config/schema.js.map +1 -0
  47. package/dist/core/backup.d.ts +8 -0
  48. package/dist/core/backup.d.ts.map +1 -0
  49. package/dist/core/backup.js +50 -0
  50. package/dist/core/backup.js.map +1 -0
  51. package/dist/core/checksum.d.ts +14 -0
  52. package/dist/core/checksum.d.ts.map +1 -0
  53. package/dist/core/checksum.js +23 -0
  54. package/dist/core/checksum.js.map +1 -0
  55. package/dist/core/exclusions.d.ts +8 -0
  56. package/dist/core/exclusions.d.ts.map +1 -0
  57. package/dist/core/exclusions.js +29 -0
  58. package/dist/core/exclusions.js.map +1 -0
  59. package/dist/core/file-collector.d.ts +16 -0
  60. package/dist/core/file-collector.d.ts.map +1 -0
  61. package/dist/core/file-collector.js +104 -0
  62. package/dist/core/file-collector.js.map +1 -0
  63. package/dist/core/manifest.d.ts +18 -0
  64. package/dist/core/manifest.d.ts.map +1 -0
  65. package/dist/core/manifest.js +42 -0
  66. package/dist/core/manifest.js.map +1 -0
  67. package/dist/core/path-expander.d.ts +6 -0
  68. package/dist/core/path-expander.d.ts.map +1 -0
  69. package/dist/core/path-expander.js +25 -0
  70. package/dist/core/path-expander.js.map +1 -0
  71. package/dist/core/sync-engine.d.ts +24 -0
  72. package/dist/core/sync-engine.d.ts.map +1 -0
  73. package/dist/core/sync-engine.js +206 -0
  74. package/dist/core/sync-engine.js.map +1 -0
  75. package/dist/core/sync-package.d.ts +12 -0
  76. package/dist/core/sync-package.d.ts.map +1 -0
  77. package/dist/core/sync-package.js +95 -0
  78. package/dist/core/sync-package.js.map +1 -0
  79. package/dist/crypto/encryptor.d.ts +15 -0
  80. package/dist/crypto/encryptor.d.ts.map +1 -0
  81. package/dist/crypto/encryptor.js +77 -0
  82. package/dist/crypto/encryptor.js.map +1 -0
  83. package/dist/crypto/key-resolver.d.ts +28 -0
  84. package/dist/crypto/key-resolver.d.ts.map +1 -0
  85. package/dist/crypto/key-resolver.js +62 -0
  86. package/dist/crypto/key-resolver.js.map +1 -0
  87. package/dist/gist/client.d.ts +18 -0
  88. package/dist/gist/client.d.ts.map +1 -0
  89. package/dist/gist/client.js +58 -0
  90. package/dist/gist/client.js.map +1 -0
  91. package/dist/gist/mock-client.d.ts +19 -0
  92. package/dist/gist/mock-client.d.ts.map +1 -0
  93. package/dist/gist/mock-client.js +61 -0
  94. package/dist/gist/mock-client.js.map +1 -0
  95. package/dist/index.d.ts +3 -0
  96. package/dist/index.d.ts.map +1 -0
  97. package/dist/index.js +3 -0
  98. package/dist/index.js.map +1 -0
  99. package/dist/presets/claude-code.d.ts +3 -0
  100. package/dist/presets/claude-code.d.ts.map +1 -0
  101. package/dist/presets/claude-code.js +27 -0
  102. package/dist/presets/claude-code.js.map +1 -0
  103. package/dist/presets/hermes.d.ts +3 -0
  104. package/dist/presets/hermes.d.ts.map +1 -0
  105. package/dist/presets/hermes.js +27 -0
  106. package/dist/presets/hermes.js.map +1 -0
  107. package/dist/presets/index.d.ts +15 -0
  108. package/dist/presets/index.d.ts.map +1 -0
  109. package/dist/presets/index.js +42 -0
  110. package/dist/presets/index.js.map +1 -0
  111. package/dist/presets/openclaw.d.ts +3 -0
  112. package/dist/presets/openclaw.d.ts.map +1 -0
  113. package/dist/presets/openclaw.js +27 -0
  114. package/dist/presets/openclaw.js.map +1 -0
  115. package/dist/types/config.d.ts +45 -0
  116. package/dist/types/config.d.ts.map +1 -0
  117. package/dist/types/config.js +2 -0
  118. package/dist/types/config.js.map +1 -0
  119. package/dist/types/gist.d.ts +25 -0
  120. package/dist/types/gist.d.ts.map +1 -0
  121. package/dist/types/gist.js +2 -0
  122. package/dist/types/gist.js.map +1 -0
  123. package/dist/types/index.d.ts +4 -0
  124. package/dist/types/index.d.ts.map +1 -0
  125. package/dist/types/index.js +2 -0
  126. package/dist/types/index.js.map +1 -0
  127. package/dist/types/sync.d.ts +78 -0
  128. package/dist/types/sync.d.ts.map +1 -0
  129. package/dist/types/sync.js +2 -0
  130. package/dist/types/sync.js.map +1 -0
  131. package/dist/utils/diff.d.ts +7 -0
  132. package/dist/utils/diff.d.ts.map +1 -0
  133. package/dist/utils/diff.js +99 -0
  134. package/dist/utils/diff.js.map +1 -0
  135. package/dist/utils/env.d.ts +13 -0
  136. package/dist/utils/env.d.ts.map +1 -0
  137. package/dist/utils/env.js +29 -0
  138. package/dist/utils/env.js.map +1 -0
  139. package/dist/utils/logger.d.ts +14 -0
  140. package/dist/utils/logger.d.ts.map +1 -0
  141. package/dist/utils/logger.js +39 -0
  142. package/dist/utils/logger.js.map +1 -0
  143. package/dist/utils/table.d.ts +5 -0
  144. package/dist/utils/table.d.ts.map +1 -0
  145. package/dist/utils/table.js +59 -0
  146. package/dist/utils/table.js.map +1 -0
  147. package/package.json +41 -0
  148. package/tests/integration/encryption-flow.test.ts +142 -0
  149. package/tests/integration/init-flow.test.ts +69 -0
  150. package/tests/integration/push-pull-flow.test.ts +183 -0
  151. package/tests/integration/status-flow.test.ts +149 -0
  152. package/tsconfig.json +19 -0
  153. package/vitest.config.ts +10 -0
@@ -0,0 +1,25 @@
1
+ export interface GistFile {
2
+ filename: string;
3
+ content: string;
4
+ }
5
+ export interface GistDescriptor {
6
+ gistId: string;
7
+ description: string;
8
+ files: GistFile[];
9
+ updatedAt: string;
10
+ }
11
+ export interface GistClientInterface {
12
+ /** Create a new Gist */
13
+ createGist(description: string, files: Record<string, {
14
+ content: string;
15
+ }>): Promise<GistDescriptor>;
16
+ /** Get a Gist by ID */
17
+ getGist(gistId: string): Promise<GistDescriptor>;
18
+ /** Update an existing Gist */
19
+ updateGist(gistId: string, files: Record<string, {
20
+ content: string;
21
+ }>): Promise<GistDescriptor>;
22
+ /** Delete a Gist (for cleanup) */
23
+ deleteGist(gistId: string): Promise<void>;
24
+ }
25
+ //# sourceMappingURL=gist.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gist.d.ts","sourceRoot":"","sources":["../../src/types/gist.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,QAAQ;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,wBAAwB;IACxB,UAAU,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IACrG,uBAAuB;IACvB,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IACjD,8BAA8B;IAC9B,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IAChG,kCAAkC;IAClC,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3C"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=gist.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gist.js","sourceRoot":"","sources":["../../src/types/gist.ts"],"names":[],"mappings":""}
@@ -0,0 +1,4 @@
1
+ export type { AppConfig, SyncPathEntry, ExclusionRule, EncryptionRule, ToolPresetId, ToolPreset, } from './config.js';
2
+ export type { Manifest, ManifestEntry, SyncFile, SyncPackage, DiffEntry, ConflictEntry, SyncDiff, SyncResult, } from './sync.js';
3
+ export type { GistFile, GistDescriptor, GistClientInterface, } from './gist.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,SAAS,EACT,aAAa,EACb,aAAa,EACb,cAAc,EACd,YAAY,EACZ,UAAU,GACX,MAAM,aAAa,CAAC;AAErB,YAAY,EACV,QAAQ,EACR,aAAa,EACb,QAAQ,EACR,WAAW,EACX,SAAS,EACT,aAAa,EACb,QAAQ,EACR,UAAU,GACX,MAAM,WAAW,CAAC;AAEnB,YAAY,EACV,QAAQ,EACR,cAAc,EACd,mBAAmB,GACpB,MAAM,WAAW,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":""}
@@ -0,0 +1,78 @@
1
+ export interface ManifestEntry {
2
+ /** Relative path of the file within the sync root */
3
+ relativePath: string;
4
+ /** SHA-256 checksum of file contents */
5
+ checksum: string;
6
+ /** File size in bytes */
7
+ size: number;
8
+ /** Last modified timestamp (ISO 8601) */
9
+ modifiedAt: string;
10
+ /** Originating SyncPathEntry name */
11
+ sourceName: string;
12
+ /** Whether this file content is encrypted */
13
+ encrypted: boolean;
14
+ /** Encryption rule pattern that matched (null if not encrypted) */
15
+ encryptionPattern: string | null;
16
+ }
17
+ export interface Manifest {
18
+ /** Manifest format version */
19
+ version: 1;
20
+ /** Timestamp when manifest was generated */
21
+ generatedAt: string;
22
+ /** All file entries */
23
+ entries: ManifestEntry[];
24
+ }
25
+ export interface SyncFile {
26
+ /** Relative path of the file */
27
+ relativePath: string;
28
+ /** File content as Base64 string */
29
+ content: string;
30
+ /** Whether content is encrypted */
31
+ encrypted: boolean;
32
+ /** Entry metadata */
33
+ entry: ManifestEntry;
34
+ }
35
+ export interface SyncPackage {
36
+ /** Package format version */
37
+ version: 1;
38
+ /** Timestamp of package creation */
39
+ createdAt: string;
40
+ /** Manifest of all synced files */
41
+ manifest: Manifest;
42
+ /** File contents */
43
+ files: SyncFile[];
44
+ }
45
+ export interface DiffEntry {
46
+ relativePath: string;
47
+ status: 'added' | 'removed' | 'modified' | 'renamed';
48
+ localChecksum: string | null;
49
+ remoteChecksum: string | null;
50
+ }
51
+ export interface ConflictEntry {
52
+ relativePath: string;
53
+ localChecksum: string;
54
+ remoteChecksum: string;
55
+ lastKnownChecksum: string | null;
56
+ }
57
+ export interface SyncDiff {
58
+ /** Only in local manifest */
59
+ added: DiffEntry[];
60
+ /** Only in remote manifest */
61
+ removed: DiffEntry[];
62
+ /** Present in both, checksum differs, lastKnown matches one side */
63
+ modified: DiffEntry[];
64
+ /** Present in both, checksum differs, lastKnown matches neither (both sides changed) */
65
+ conflicts: ConflictEntry[];
66
+ /** Whether any conflicts exist */
67
+ hasConflicts: boolean;
68
+ /** Whether there are any differences at all */
69
+ hasChanges: boolean;
70
+ }
71
+ export interface SyncResult {
72
+ success: boolean;
73
+ action: 'push' | 'pull' | 'force-push' | 'force-pull';
74
+ filesUploaded: number;
75
+ filesDownloaded: number;
76
+ errors: string[];
77
+ }
78
+ //# sourceMappingURL=sync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/types/sync.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,aAAa;IAC5B,qDAAqD;IACrD,YAAY,EAAE,MAAM,CAAC;IACrB,wCAAwC;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,yBAAyB;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,yCAAyC;IACzC,UAAU,EAAE,MAAM,CAAC;IACnB,qCAAqC;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,6CAA6C;IAC7C,SAAS,EAAE,OAAO,CAAC;IACnB,mEAAmE;IACnE,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED,MAAM,WAAW,QAAQ;IACvB,8BAA8B;IAC9B,OAAO,EAAE,CAAC,CAAC;IACX,4CAA4C;IAC5C,WAAW,EAAE,MAAM,CAAC;IACpB,uBAAuB;IACvB,OAAO,EAAE,aAAa,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,QAAQ;IACvB,gCAAgC;IAChC,YAAY,EAAE,MAAM,CAAC;IACrB,oCAAoC;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,mCAAmC;IACnC,SAAS,EAAE,OAAO,CAAC;IACnB,qBAAqB;IACrB,KAAK,EAAE,aAAa,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,6BAA6B;IAC7B,OAAO,EAAE,CAAC,CAAC;IACX,oCAAoC;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,mCAAmC;IACnC,QAAQ,EAAE,QAAQ,CAAC;IACnB,oBAAoB;IACpB,KAAK,EAAE,QAAQ,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,SAAS;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,OAAO,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,CAAC;IACrD,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED,MAAM,WAAW,QAAQ;IACvB,6BAA6B;IAC7B,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,8BAA8B;IAC9B,OAAO,EAAE,SAAS,EAAE,CAAC;IACrB,oEAAoE;IACpE,QAAQ,EAAE,SAAS,EAAE,CAAC;IACtB,wFAAwF;IACxF,SAAS,EAAE,aAAa,EAAE,CAAC;IAC3B,kCAAkC;IAClC,YAAY,EAAE,OAAO,CAAC;IACtB,+CAA+C;IAC/C,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,YAAY,GAAG,YAAY,CAAC;IACtD,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.js","sourceRoot":"","sources":["../../src/types/sync.ts"],"names":[],"mappings":""}
@@ -0,0 +1,7 @@
1
+ import type { Manifest, SyncDiff } from '../types/sync.js';
2
+ /**
3
+ * Compare two manifests and produce a SyncDiff.
4
+ * Uses lastKnownManifest for three-way merge detection.
5
+ */
6
+ export declare function diffManifests(local: Manifest, remote: Manifest, lastKnown: Manifest | null): SyncDiff;
7
+ //# sourceMappingURL=diff.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../src/utils/diff.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAiB,QAAQ,EAA4B,MAAM,kBAAkB,CAAC;AAEpG;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,QAAQ,EACf,MAAM,EAAE,QAAQ,EAChB,SAAS,EAAE,QAAQ,GAAG,IAAI,GACzB,QAAQ,CAqGV"}
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Compare two manifests and produce a SyncDiff.
3
+ * Uses lastKnownManifest for three-way merge detection.
4
+ */
5
+ export function diffManifests(local, remote, lastKnown) {
6
+ const localMap = new Map();
7
+ const remoteMap = new Map();
8
+ const lastKnownMap = new Map();
9
+ for (const entry of local.entries) {
10
+ localMap.set(entry.relativePath, entry);
11
+ }
12
+ for (const entry of remote.entries) {
13
+ remoteMap.set(entry.relativePath, entry);
14
+ }
15
+ if (lastKnown) {
16
+ for (const entry of lastKnown.entries) {
17
+ lastKnownMap.set(entry.relativePath, entry);
18
+ }
19
+ }
20
+ const allPaths = new Set([
21
+ ...localMap.keys(),
22
+ ...remoteMap.keys(),
23
+ ]);
24
+ const added = [];
25
+ const removed = [];
26
+ const modified = [];
27
+ const conflicts = [];
28
+ for (const path of allPaths) {
29
+ const localEntry = localMap.get(path);
30
+ const remoteEntry = remoteMap.get(path);
31
+ const lastKnownEntry = lastKnownMap.get(path);
32
+ // Only local
33
+ if (localEntry && !remoteEntry) {
34
+ added.push({
35
+ relativePath: path,
36
+ status: 'added',
37
+ localChecksum: localEntry.checksum,
38
+ remoteChecksum: null,
39
+ });
40
+ continue;
41
+ }
42
+ // Only remote
43
+ if (!localEntry && remoteEntry) {
44
+ removed.push({
45
+ relativePath: path,
46
+ status: 'removed',
47
+ localChecksum: null,
48
+ remoteChecksum: remoteEntry.checksum,
49
+ });
50
+ continue;
51
+ }
52
+ // Both exist
53
+ if (localEntry && remoteEntry) {
54
+ if (localEntry.checksum === remoteEntry.checksum) {
55
+ // Same — no diff
56
+ continue;
57
+ }
58
+ // Different checksums — use three-way merge detection
59
+ const localChanged = !lastKnownEntry || localEntry.checksum !== lastKnownEntry.checksum;
60
+ const remoteChanged = !lastKnownEntry || remoteEntry.checksum !== lastKnownEntry.checksum;
61
+ if (localChanged && !remoteChanged) {
62
+ // Only local changed — safe to push
63
+ modified.push({
64
+ relativePath: path,
65
+ status: 'modified',
66
+ localChecksum: localEntry.checksum,
67
+ remoteChecksum: remoteEntry.checksum,
68
+ });
69
+ }
70
+ else if (!localChanged && remoteChanged) {
71
+ // Only remote changed — safe to pull
72
+ modified.push({
73
+ relativePath: path,
74
+ status: 'modified',
75
+ localChecksum: localEntry.checksum,
76
+ remoteChecksum: remoteEntry.checksum,
77
+ });
78
+ }
79
+ else {
80
+ // Both changed or no base — conflict
81
+ conflicts.push({
82
+ relativePath: path,
83
+ localChecksum: localEntry.checksum,
84
+ remoteChecksum: remoteEntry.checksum,
85
+ lastKnownChecksum: lastKnownEntry?.checksum || null,
86
+ });
87
+ }
88
+ }
89
+ }
90
+ return {
91
+ added,
92
+ removed,
93
+ modified,
94
+ conflicts,
95
+ hasConflicts: conflicts.length > 0,
96
+ hasChanges: added.length > 0 || removed.length > 0 || modified.length > 0 || conflicts.length > 0,
97
+ };
98
+ }
99
+ //# sourceMappingURL=diff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/utils/diff.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { AppConfig } from '../types/config.js';
2
+ /**
3
+ * Environment variable helpers.
4
+ */
5
+ export declare function getEnv(key: string, defaultValue?: string): string | undefined;
6
+ export declare function requireEnv(key: string): string;
7
+ export declare function getGithubToken(): string | undefined;
8
+ /**
9
+ * Resolve the GitHub token from config file first, then environment variable.
10
+ */
11
+ export declare function resolveGithubToken(config?: AppConfig | null): string | undefined;
12
+ export declare function getEncryptionPassword(): string | undefined;
13
+ //# sourceMappingURL=env.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../src/utils/env.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAEpD;;GAEG;AAEH,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAE7E;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAM9C;AAED,wBAAgB,cAAc,IAAI,MAAM,GAAG,SAAS,CAEnD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,CAAC,EAAE,SAAS,GAAG,IAAI,GAAG,MAAM,GAAG,SAAS,CAKhF;AAED,wBAAgB,qBAAqB,IAAI,MAAM,GAAG,SAAS,CAE1D"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Environment variable helpers.
3
+ */
4
+ export function getEnv(key, defaultValue) {
5
+ return process.env[key] || defaultValue;
6
+ }
7
+ export function requireEnv(key) {
8
+ const value = process.env[key];
9
+ if (!value) {
10
+ throw new Error(`Required environment variable ${key} is not set.`);
11
+ }
12
+ return value;
13
+ }
14
+ export function getGithubToken() {
15
+ return getEnv('GITHUB_TOKEN');
16
+ }
17
+ /**
18
+ * Resolve the GitHub token from config file first, then environment variable.
19
+ */
20
+ export function resolveGithubToken(config) {
21
+ if (config?.githubToken) {
22
+ return config.githubToken;
23
+ }
24
+ return getGithubToken();
25
+ }
26
+ export function getEncryptionPassword() {
27
+ return getEnv('AITOOLSYNC_PASSWORD');
28
+ }
29
+ //# sourceMappingURL=env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.js","sourceRoot":"","sources":["../../src/utils/env.ts"],"names":[],"mappings":"AAEA;;GAEG;AAEH,MAAM,UAAU,MAAM,CAAC,GAAW,EAAE,YAAqB;IACvD,OAAO,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,YAAY,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,GAAW;IACpC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC/B,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,iCAAiC,GAAG,cAAc,CAAC,CAAC;IACtE,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,OAAO,MAAM,CAAC,cAAc,CAAC,CAAC;AAChC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAyB;IAC1D,IAAI,MAAM,EAAE,WAAW,EAAE,CAAC;QACxB,OAAO,MAAM,CAAC,WAAW,CAAC;IAC5B,CAAC;IACD,OAAO,cAAc,EAAE,CAAC;AAC1B,CAAC;AAED,MAAM,UAAU,qBAAqB;IACnC,OAAO,MAAM,CAAC,qBAAqB,CAAC,CAAC;AACvC,CAAC"}
@@ -0,0 +1,14 @@
1
+ export declare enum LogLevel {
2
+ DEBUG = 0,
3
+ INFO = 1,
4
+ WARN = 2,
5
+ ERROR = 3
6
+ }
7
+ export declare function setLogLevel(level: LogLevel): void;
8
+ export declare function debug(msg: string): void;
9
+ export declare function info(msg: string): void;
10
+ export declare function success(msg: string): void;
11
+ export declare function warn(msg: string): void;
12
+ export declare function error(msg: string): void;
13
+ export declare function plain(msg: string): void;
14
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAEA,oBAAY,QAAQ;IAClB,KAAK,IAAI;IACT,IAAI,IAAI;IACR,IAAI,IAAI;IACR,KAAK,IAAI;CACV;AAID,wBAAgB,WAAW,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,CAEjD;AAED,wBAAgB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAIvC;AAED,wBAAgB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAItC;AAED,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAEzC;AAED,wBAAgB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAItC;AAED,wBAAgB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAIvC;AAED,wBAAgB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAEvC"}
@@ -0,0 +1,39 @@
1
+ import chalk from 'chalk';
2
+ export var LogLevel;
3
+ (function (LogLevel) {
4
+ LogLevel[LogLevel["DEBUG"] = 0] = "DEBUG";
5
+ LogLevel[LogLevel["INFO"] = 1] = "INFO";
6
+ LogLevel[LogLevel["WARN"] = 2] = "WARN";
7
+ LogLevel[LogLevel["ERROR"] = 3] = "ERROR";
8
+ })(LogLevel || (LogLevel = {}));
9
+ let currentLevel = LogLevel.INFO;
10
+ export function setLogLevel(level) {
11
+ currentLevel = level;
12
+ }
13
+ export function debug(msg) {
14
+ if (currentLevel <= LogLevel.DEBUG) {
15
+ process.stderr.write(chalk.gray(`[debug] ${msg}\n`));
16
+ }
17
+ }
18
+ export function info(msg) {
19
+ if (currentLevel <= LogLevel.INFO) {
20
+ process.stderr.write(chalk.blue(`[info] ${msg}\n`));
21
+ }
22
+ }
23
+ export function success(msg) {
24
+ process.stderr.write(chalk.green(`[success] ${msg}\n`));
25
+ }
26
+ export function warn(msg) {
27
+ if (currentLevel <= LogLevel.WARN) {
28
+ process.stderr.write(chalk.yellow(`[warn] ${msg}\n`));
29
+ }
30
+ }
31
+ export function error(msg) {
32
+ if (currentLevel <= LogLevel.ERROR) {
33
+ process.stderr.write(chalk.red(`[error] ${msg}\n`));
34
+ }
35
+ }
36
+ export function plain(msg) {
37
+ process.stdout.write(`${msg}\n`);
38
+ }
39
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,CAAN,IAAY,QAKX;AALD,WAAY,QAAQ;IAClB,yCAAS,CAAA;IACT,uCAAQ,CAAA;IACR,uCAAQ,CAAA;IACR,yCAAS,CAAA;AACX,CAAC,EALW,QAAQ,KAAR,QAAQ,QAKnB;AAED,IAAI,YAAY,GAAG,QAAQ,CAAC,IAAI,CAAC;AAEjC,MAAM,UAAU,WAAW,CAAC,KAAe;IACzC,YAAY,GAAG,KAAK,CAAC;AACvB,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,GAAW;IAC/B,IAAI,YAAY,IAAI,QAAQ,CAAC,KAAK,EAAE,CAAC;QACnC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC,CAAC;IACvD,CAAC;AACH,CAAC;AAED,MAAM,UAAU,IAAI,CAAC,GAAW;IAC9B,IAAI,YAAY,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAC;QAClC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC,CAAC;IACtD,CAAC;AACH,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,GAAW;IACjC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC,CAAC;AAC1D,CAAC;AAED,MAAM,UAAU,IAAI,CAAC,GAAW;IAC9B,IAAI,YAAY,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAC;QAClC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC,CAAC;IACxD,CAAC;AACH,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,GAAW;IAC/B,IAAI,YAAY,IAAI,QAAQ,CAAC,KAAK,EAAE,CAAC;QACnC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC,CAAC;IACtD,CAAC;AACH,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,GAAW;IAC/B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC;AACnC,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { SyncPathEntry } from '../types/config.js';
2
+ import type { DiffEntry, ConflictEntry } from '../types/sync.js';
3
+ export declare function renderPathList(entries: SyncPathEntry[]): void;
4
+ export declare function renderDiffTable(added: DiffEntry[], removed: DiffEntry[], modified: DiffEntry[], conflicts: ConflictEntry[]): void;
5
+ //# sourceMappingURL=table.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"table.d.ts","sourceRoot":"","sources":["../../src/utils/table.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjE,wBAAgB,cAAc,CAAC,OAAO,EAAE,aAAa,EAAE,GAAG,IAAI,CAqB7D;AAED,wBAAgB,eAAe,CAC7B,KAAK,EAAE,SAAS,EAAE,EAClB,OAAO,EAAE,SAAS,EAAE,EACpB,QAAQ,EAAE,SAAS,EAAE,EACrB,SAAS,EAAE,aAAa,EAAE,GACzB,IAAI,CA2CN"}
@@ -0,0 +1,59 @@
1
+ import Table from 'cli-table3';
2
+ import chalk from 'chalk';
3
+ export function renderPathList(entries) {
4
+ if (entries.length === 0) {
5
+ console.log(chalk.gray(' No paths configured.'));
6
+ return;
7
+ }
8
+ const table = new Table({
9
+ head: [chalk.bold('Name'), chalk.bold('Path'), chalk.bold('Enabled')],
10
+ style: { head: [] },
11
+ colWidths: [20, 50, 10],
12
+ });
13
+ for (const entry of entries) {
14
+ table.push([
15
+ entry.name,
16
+ entry.path,
17
+ entry.enabled ? chalk.green('yes') : chalk.gray('no'),
18
+ ]);
19
+ }
20
+ console.log(table.toString());
21
+ }
22
+ export function renderDiffTable(added, removed, modified, conflicts) {
23
+ if (added.length === 0 && removed.length === 0 && modified.length === 0 && conflicts.length === 0) {
24
+ console.log(chalk.green(' No differences found. Local and remote are in sync.'));
25
+ return;
26
+ }
27
+ const table = new Table({
28
+ head: [chalk.bold('File'), chalk.bold('Status'), chalk.bold('Local'), chalk.bold('Remote')],
29
+ style: { head: [] },
30
+ colWidths: [40, 12, 14, 14],
31
+ });
32
+ for (const entry of added) {
33
+ table.push([entry.relativePath, chalk.green('added'), entry.localChecksum?.slice(0, 8) || '-', '-']);
34
+ }
35
+ for (const entry of removed) {
36
+ table.push([entry.relativePath, chalk.red('removed'), '-', entry.remoteChecksum?.slice(0, 8) || '-']);
37
+ }
38
+ for (const entry of modified) {
39
+ table.push([
40
+ entry.relativePath,
41
+ chalk.yellow('modified'),
42
+ entry.localChecksum?.slice(0, 8) || '-',
43
+ entry.remoteChecksum?.slice(0, 8) || '-',
44
+ ]);
45
+ }
46
+ for (const entry of conflicts) {
47
+ table.push([
48
+ entry.relativePath,
49
+ chalk.red('CONFLICT'),
50
+ entry.localChecksum.slice(0, 8),
51
+ entry.remoteChecksum.slice(0, 8),
52
+ ]);
53
+ }
54
+ console.log(table.toString());
55
+ if (conflicts.length > 0) {
56
+ console.log(chalk.red(`\n ${conflicts.length} conflict(s) detected. Use --force to override.`));
57
+ }
58
+ }
59
+ //# sourceMappingURL=table.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"table.js","sourceRoot":"","sources":["../../src/utils/table.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@tengx5383/aitool-sync-cli",
3
+ "version": "0.1.0",
4
+ "description": "CLI for syncing AI agent tool configs across devices via GitHub Gist",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "aitool-sync": "./dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "postbuild": "node -e \"require('fs').chmodSync('dist/index.js', 0o755)\"",
13
+ "prepare": "npm run build",
14
+ "dev": "tsx src/index.ts",
15
+ "test": "vitest run",
16
+ "test:watch": "vitest",
17
+ "prettier": "prettier --write ."
18
+ },
19
+ "keywords": ["cli", "sync", "ai-tools", "claude-code", "config"],
20
+ "license": "MIT",
21
+ "dependencies": {
22
+ "@octokit/rest": "^21.0.0",
23
+ "chalk": "^5.3.0",
24
+ "cli-table3": "^0.6.5",
25
+ "commander": "^12.1.0",
26
+ "dayjs": "^1.11.13",
27
+ "inquirer": "^12.0.0",
28
+ "micromatch": "^4.0.8",
29
+ "ora": "^8.1.0",
30
+ "zod": "^3.23.0"
31
+ },
32
+ "devDependencies": {
33
+ "@types/inquirer": "^9.0.7",
34
+ "@types/micromatch": "^4.0.9",
35
+ "@types/node": "^22.0.0",
36
+ "prettier": "^3.3.0",
37
+ "tsx": "^4.19.0",
38
+ "typescript": "^5.5.0",
39
+ "vitest": "^2.0.0"
40
+ }
41
+ }
@@ -0,0 +1,142 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import os from 'node:os';
5
+ import { encrypt, decrypt, isEncrypted } from '../../src/crypto/encryptor.js';
6
+ import { setPassword, clearPassword, resolvePassword, encryptWithPassword, decryptWithPassword, hasPassword } from '../../src/crypto/key-resolver.js';
7
+ import { createDefaultConfig, writeConfig } from '../../src/config/config-file.js';
8
+ import { buildSyncPackage, parseSyncPackage } from '../../src/core/sync-package.js';
9
+ import { createManifest, serializeManifest } from '../../src/core/manifest.js';
10
+ import { pushSync, pullSync, writeRemoteFiles } from '../../src/core/sync-engine.js';
11
+ import { MockGistClient } from '../../src/gist/mock-client.js';
12
+ import type { AppConfig } from '../../src/types/index.js';
13
+
14
+ const TEST_PASSWORD = 'test-secret-password-123';
15
+
16
+ function createTestDir(base: string): string {
17
+ const dir = path.join(base, 'encrypted-tools');
18
+ fs.mkdirSync(dir, { recursive: true });
19
+ fs.writeFileSync(path.join(dir, 'config.json'), JSON.stringify({ version: 1 }));
20
+ fs.writeFileSync(path.join(dir, 'token.json'), JSON.stringify({ apiKey: 'secret-token-123' }));
21
+ return dir;
22
+ }
23
+
24
+ describe('encryption flow', () => {
25
+ let tmpDir: string;
26
+ let mockClient: MockGistClient;
27
+
28
+ beforeEach(() => {
29
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'aitoolsync-crypto-'));
30
+ process.chdir(tmpDir);
31
+ mockClient = new MockGistClient();
32
+ setPassword(TEST_PASSWORD);
33
+ });
34
+
35
+ afterEach(() => {
36
+ fs.rmSync(tmpDir, { recursive: true, force: true });
37
+ clearPassword();
38
+ });
39
+
40
+ it('encrypts and decrypts a string', () => {
41
+ const plaintext = 'Hello, World!';
42
+ const encrypted = encrypt(plaintext, TEST_PASSWORD);
43
+ expect(encrypted).not.toBe(plaintext);
44
+
45
+ const decrypted = decrypt(encrypted, TEST_PASSWORD);
46
+ expect(decrypted).toBe(plaintext);
47
+ });
48
+
49
+ it('detects encrypted payloads', () => {
50
+ const encrypted = encrypt('test data', TEST_PASSWORD);
51
+ expect(isEncrypted(encrypted)).toBe(true);
52
+ expect(isEncrypted('plain text')).toBe(false);
53
+ });
54
+
55
+ it('key resolver caches and resolves password', () => {
56
+ clearPassword();
57
+ expect(hasPassword()).toBe(false);
58
+
59
+ setPassword('my-password');
60
+ expect(hasPassword()).toBe(true);
61
+ expect(resolvePassword()).toBe('my-password');
62
+
63
+ clearPassword();
64
+ expect(hasPassword()).toBe(false);
65
+ });
66
+
67
+ it('encrypt with password convenience function', () => {
68
+ const plaintext = 'sensitive data';
69
+ const encrypted = encryptWithPassword(plaintext);
70
+ expect(encrypted).not.toBe(plaintext);
71
+
72
+ const decrypted = decryptWithPassword(encrypted);
73
+ expect(decrypted).toBe(plaintext);
74
+ });
75
+
76
+ it('push and pull with encrypted files', async () => {
77
+ const testDir = createTestDir(tmpDir);
78
+ const config = createDefaultConfig();
79
+ config.syncPaths = [{ name: 'enc-tools', path: testDir, enabled: true }];
80
+ config.encryption = [{ pattern: '**/token.json' }];
81
+
82
+ const gist = await mockClient.createGist('aitoolsync', {
83
+ 'aitoolsync-package.json': { content: '{}' },
84
+ });
85
+ config.gistId = gist.gistId;
86
+ writeConfig(config);
87
+
88
+ // Push with encryption
89
+ const pushResult = await pushSync(config, mockClient, false, encryptWithPassword);
90
+ expect(pushResult.success).toBe(true);
91
+
92
+ // Verify Gist content is encrypted for token.json
93
+ const fetched = await mockClient.getGist(gist.gistId);
94
+ const pkgFile = fetched.files.find((f) => f.filename === 'aitoolsync-package.json');
95
+ const pkg = parseSyncPackage(pkgFile!.content);
96
+ const tokenFile = pkg!.files.find((f) => f.relativePath === 'token.json');
97
+ expect(tokenFile!.encrypted).toBe(true);
98
+ expect(tokenFile!.content).not.toContain('secret-token');
99
+
100
+ const configFile = pkg!.files.find((f) => f.relativePath === 'config.json');
101
+ expect(configFile!.encrypted).toBe(false);
102
+ });
103
+
104
+ it('pull and decrypt files', async () => {
105
+ const testDir = createTestDir(tmpDir);
106
+ const config = createDefaultConfig();
107
+ config.syncPaths = [{ name: 'enc-tools', path: testDir, enabled: true }];
108
+ config.encryption = [{ pattern: '**/token.json' }];
109
+
110
+ const gist = await mockClient.createGist('aitoolsync', {
111
+ 'aitoolsync-package.json': { content: '{}' },
112
+ });
113
+ config.gistId = gist.gistId;
114
+ writeConfig(config);
115
+
116
+ // Push with encryption first
117
+ await pushSync(config, mockClient, false, encryptWithPassword);
118
+
119
+ // Corrupt local files
120
+ fs.writeFileSync(path.join(testDir, 'token.json'), 'CORRUPTED');
121
+
122
+ // Pull and decrypt
123
+ const pullResult = await pullSync(config, mockClient, false, decryptWithPassword);
124
+ expect(pullResult.success).toBe(true);
125
+
126
+ // Verify file was restored correctly
127
+ const content = JSON.parse(fs.readFileSync(path.join(testDir, 'token.json'), 'utf-8'));
128
+ expect(content.apiKey).toBe('secret-token-123');
129
+ });
130
+
131
+ it('fails to decrypt with wrong password', () => {
132
+ const encrypted = encrypt('secret', TEST_PASSWORD);
133
+ expect(() => decrypt(encrypted, 'wrong-password')).toThrow();
134
+ });
135
+
136
+ it('encrypted content does not leak plaintext', () => {
137
+ const plaintext = 'super-secret-api-key-12345';
138
+ const encrypted = encrypt(plaintext, TEST_PASSWORD);
139
+ expect(encrypted).not.toContain('super-secret-api-key');
140
+ expect(encrypted).not.toContain('12345');
141
+ });
142
+ });