claude-comms 2.0.5 → 2.0.7
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/package.json +1 -1
- package/src/cli/interfaces.js +7 -5
- package/src/cli/prompts.js +2 -1
- package/src/cli/runner.js +6 -5
- package/src/fetcher/github.js +27 -17
- package/src/orchestrator/installer.js +26 -19
- package/src/orchestrator/main.js +5 -4
- package/src/orchestrator/mocks/index.js +6 -0
- package/src/utils/config.js +2 -1
- package/src/utils/constants.js +15 -2
package/package.json
CHANGED
package/src/cli/interfaces.js
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
* These interfaces serve as the integration points between CLI and core modules.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
const { ROOT_FILES } = require('../utils/constants');
|
|
13
|
+
|
|
12
14
|
/**
|
|
13
15
|
* GitHub Fetcher Interface
|
|
14
16
|
* Will be implemented in WP03 - GitHub API Integration
|
|
@@ -279,14 +281,14 @@ class MockGitHubFetcher extends GitHubFetcherInterface {
|
|
|
279
281
|
encoding: 'utf-8',
|
|
280
282
|
sha: 'mock-sha-1'
|
|
281
283
|
},
|
|
282
|
-
{
|
|
283
|
-
path:
|
|
284
|
-
content:
|
|
284
|
+
...ROOT_FILES.map((name, i) => ({
|
|
285
|
+
path: name,
|
|
286
|
+
content: `# ${name}\n\nProject configured for Claude.`,
|
|
285
287
|
mode: 0o644,
|
|
286
288
|
size: 45,
|
|
287
289
|
encoding: 'utf-8',
|
|
288
|
-
sha:
|
|
289
|
-
}
|
|
290
|
+
sha: `mock-sha-${i + 2}`
|
|
291
|
+
}))
|
|
290
292
|
],
|
|
291
293
|
directories: ['.claude', '.claude/hooks', '.claude/agents'],
|
|
292
294
|
metadata: {
|
package/src/cli/prompts.js
CHANGED
|
@@ -12,6 +12,7 @@ const prompts = require('prompts');
|
|
|
12
12
|
const path = require('path');
|
|
13
13
|
const fs = require('fs').promises;
|
|
14
14
|
const chalk = require('chalk');
|
|
15
|
+
const { ROOT_FILES } = require('../utils/constants');
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Main prompt orchestrator for installation flow
|
|
@@ -124,7 +125,7 @@ async function checkExistingFiles(targetDir) {
|
|
|
124
125
|
const conflictFiles = [];
|
|
125
126
|
const checkFiles = [
|
|
126
127
|
'.claude',
|
|
127
|
-
|
|
128
|
+
...ROOT_FILES,
|
|
128
129
|
'.claude/settings.json',
|
|
129
130
|
'.claude/hooks',
|
|
130
131
|
'.claude/agents'
|
package/src/cli/runner.js
CHANGED
|
@@ -17,6 +17,7 @@ const fs = require('fs').promises;
|
|
|
17
17
|
|
|
18
18
|
const { parseArguments, displayHelp } = require('./parser');
|
|
19
19
|
const { runInstallationPrompts, setupInterruptHandler } = require('./prompts');
|
|
20
|
+
const { ROOT_FILES } = require('../utils/constants');
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* Main CLI entry point
|
|
@@ -321,11 +322,11 @@ function generateMockFileStructure(_tag) {
|
|
|
321
322
|
content: '# Architect Agent\n\nSystem design and architecture specialist.\n',
|
|
322
323
|
mode: 0o644
|
|
323
324
|
},
|
|
324
|
-
{
|
|
325
|
-
path:
|
|
326
|
-
content:
|
|
325
|
+
...ROOT_FILES.map(name => ({
|
|
326
|
+
path: name,
|
|
327
|
+
content: `# ${name}\n\nThis project has been configured for Claude multi-agent orchestration.\n`,
|
|
327
328
|
mode: 0o644
|
|
328
|
-
}
|
|
329
|
+
}))
|
|
329
330
|
];
|
|
330
331
|
}
|
|
331
332
|
|
|
@@ -402,7 +403,7 @@ async function setExecutablePermissions(targetDir) {
|
|
|
402
403
|
async function validateInstallation(targetDir) {
|
|
403
404
|
const requiredFiles = [
|
|
404
405
|
'.claude/settings.json',
|
|
405
|
-
|
|
406
|
+
...ROOT_FILES
|
|
406
407
|
];
|
|
407
408
|
|
|
408
409
|
for (const file of requiredFiles) {
|
package/src/fetcher/github.js
CHANGED
|
@@ -7,6 +7,7 @@ const fetch = require('node-fetch');
|
|
|
7
7
|
const tar = require('tar');
|
|
8
8
|
const { EventEmitter } = require('events');
|
|
9
9
|
const { GitHubAPIError } = require('../utils/errors');
|
|
10
|
+
const { ROOT_FILES } = require('../utils/constants');
|
|
10
11
|
const fs = require('fs');
|
|
11
12
|
const path = require('path');
|
|
12
13
|
const os = require('os');
|
|
@@ -336,8 +337,8 @@ class GitHubFetcher extends EventEmitter {
|
|
|
336
337
|
'.agents/AGENTS.md',
|
|
337
338
|
'.agents/repo.md',
|
|
338
339
|
|
|
339
|
-
// Root
|
|
340
|
-
|
|
340
|
+
// Root files (single source of truth: ROOT_FILES)
|
|
341
|
+
...ROOT_FILES
|
|
341
342
|
];
|
|
342
343
|
|
|
343
344
|
const files = [];
|
|
@@ -346,7 +347,7 @@ class GitHubFetcher extends EventEmitter {
|
|
|
346
347
|
if (filePath.startsWith(path) ||
|
|
347
348
|
(path === '.claude' && filePath.startsWith('.claude')) ||
|
|
348
349
|
(path === '.agents' && filePath.startsWith('.agents')) ||
|
|
349
|
-
(path
|
|
350
|
+
(ROOT_FILES.includes(path) && filePath === path)) {
|
|
350
351
|
try {
|
|
351
352
|
const content = await this._fetchSingleFileRaw(filePath, options);
|
|
352
353
|
files.push(content);
|
|
@@ -913,11 +914,11 @@ class GitHubFetcher extends EventEmitter {
|
|
|
913
914
|
// Only extract files we care about
|
|
914
915
|
const isClaudeFile = actualPath.startsWith('.claude/') || actualPath === '.claude';
|
|
915
916
|
const isAgentsFile = actualPath.startsWith('.agents/') || actualPath === '.agents';
|
|
916
|
-
const
|
|
917
|
+
const isRootFile = ROOT_FILES.includes(actualPath);
|
|
917
918
|
|
|
918
|
-
console.log(`DEBUG: ${actualPath} -> claude: ${isClaudeFile}, agents: ${isAgentsFile},
|
|
919
|
+
console.log(`DEBUG: ${actualPath} -> claude: ${isClaudeFile}, agents: ${isAgentsFile}, root: ${isRootFile}, type: ${entry.type}`);
|
|
919
920
|
|
|
920
|
-
if ((isClaudeFile || isAgentsFile ||
|
|
921
|
+
if ((isClaudeFile || isAgentsFile || isRootFile) && entry.type === 'File') {
|
|
921
922
|
wantedFiles.set(filePath, actualPath);
|
|
922
923
|
console.log(`DEBUG: Will extract: ${filePath} -> ${actualPath}`);
|
|
923
924
|
return true;
|
|
@@ -954,15 +955,15 @@ class GitHubFetcher extends EventEmitter {
|
|
|
954
955
|
const content = await fs.promises.readFile(fullPath, 'utf-8');
|
|
955
956
|
console.log(`DEBUG: Read ${actualPath} (${content.length} bytes)`);
|
|
956
957
|
|
|
957
|
-
if (actualPath
|
|
958
|
-
const
|
|
959
|
-
path:
|
|
958
|
+
if (ROOT_FILES.includes(actualPath)) {
|
|
959
|
+
const rootFileObj = {
|
|
960
|
+
path: actualPath,
|
|
960
961
|
content: content,
|
|
961
962
|
encoding: 'utf-8',
|
|
962
963
|
type: 'file'
|
|
963
964
|
};
|
|
964
|
-
extractedFiles[
|
|
965
|
-
console.log(
|
|
965
|
+
extractedFiles[actualPath] = rootFileObj;
|
|
966
|
+
console.log(`DEBUG: Added ${actualPath} to result`);
|
|
966
967
|
} else if (actualPath.startsWith('.claude/')) {
|
|
967
968
|
// Add to .claude files
|
|
968
969
|
const fileObj = {
|
|
@@ -1001,7 +1002,10 @@ class GitHubFetcher extends EventEmitter {
|
|
|
1001
1002
|
}
|
|
1002
1003
|
}
|
|
1003
1004
|
|
|
1004
|
-
|
|
1005
|
+
const rootFilesSummary = ROOT_FILES
|
|
1006
|
+
.map(name => `${name}: ${extractedFiles[name] ? 'present' : 'missing'}`)
|
|
1007
|
+
.join(', ');
|
|
1008
|
+
console.log(`DEBUG: Final result - .claude has ${extractedFiles['.claude'].files.length} files, .agents has ${extractedFiles['.agents'].files.length} files, ${rootFilesSummary}`);
|
|
1005
1009
|
|
|
1006
1010
|
// Clean up temp directory
|
|
1007
1011
|
try {
|
|
@@ -1086,14 +1090,18 @@ async function fetchRepository(gitRef = 'main', options = {}) {
|
|
|
1086
1090
|
console.log('Attempting tarball fetch strategy...');
|
|
1087
1091
|
const tarballFiles = await fetcher.fetchAsTarball({ version: gitRef });
|
|
1088
1092
|
|
|
1093
|
+
const rootFiles = ROOT_FILES.map(name => tarballFiles[name]);
|
|
1094
|
+
|
|
1089
1095
|
return {
|
|
1090
1096
|
claudeDirectory: tarballFiles['.claude'],
|
|
1091
1097
|
agentsDirectory: tarballFiles['.agents'],
|
|
1098
|
+
// Per-filename root file keys remain top-level siblings on the result.
|
|
1092
1099
|
claudeFile: tarballFiles['CLAUDE.md'],
|
|
1100
|
+
...Object.fromEntries(ROOT_FILES.map(name => [name, tarballFiles[name]])),
|
|
1093
1101
|
files: [
|
|
1094
1102
|
...(tarballFiles['.claude'].files || []),
|
|
1095
1103
|
...(tarballFiles['.agents'].files || []),
|
|
1096
|
-
|
|
1104
|
+
...rootFiles
|
|
1097
1105
|
]
|
|
1098
1106
|
};
|
|
1099
1107
|
|
|
@@ -1103,17 +1111,19 @@ async function fetchRepository(gitRef = 'main', options = {}) {
|
|
|
1103
1111
|
|
|
1104
1112
|
try {
|
|
1105
1113
|
// Strategy 2: Individual file fetch (fallback)
|
|
1106
|
-
const [claudeDir, agentsDir,
|
|
1114
|
+
const [claudeDir, agentsDir, ...rootFiles] = await Promise.all([
|
|
1107
1115
|
fetcher.fetchDirectory('.claude', { version: gitRef }),
|
|
1108
1116
|
fetcher.fetchDirectory('.agents', { version: gitRef }),
|
|
1109
|
-
fetcher.fetchFile(
|
|
1117
|
+
...ROOT_FILES.map(name => fetcher.fetchFile(name, { version: gitRef }))
|
|
1110
1118
|
]);
|
|
1111
1119
|
|
|
1112
1120
|
return {
|
|
1113
1121
|
claudeDirectory: claudeDir,
|
|
1114
1122
|
agentsDirectory: agentsDir,
|
|
1115
|
-
|
|
1116
|
-
|
|
1123
|
+
// Per-filename root file keys remain top-level siblings on the result.
|
|
1124
|
+
claudeFile: rootFiles[ROOT_FILES.indexOf('CLAUDE.md')],
|
|
1125
|
+
...Object.fromEntries(ROOT_FILES.map((name, i) => [name, rootFiles[i]])),
|
|
1126
|
+
files: [...(claudeDir.files || []), ...(agentsDir.files || []), ...rootFiles]
|
|
1117
1127
|
};
|
|
1118
1128
|
|
|
1119
1129
|
} catch (fallbackError) {
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
const EventEmitter = require('events');
|
|
16
16
|
const path = require('path');
|
|
17
17
|
const { InstallerError } = require('../utils/errors');
|
|
18
|
+
const { ROOT_FILES } = require('../utils/constants');
|
|
18
19
|
|
|
19
20
|
// Error codes enum - matches interface contracts
|
|
20
21
|
const ErrorCode = {
|
|
@@ -281,18 +282,19 @@ class Installer extends EventEmitter {
|
|
|
281
282
|
retryCount: 3
|
|
282
283
|
});
|
|
283
284
|
|
|
284
|
-
// Fetch
|
|
285
|
-
const claudeMd = await this.fetcher.fetchFile('CLAUDE.md', {
|
|
286
|
-
version: this.options.version,
|
|
287
|
-
useCache: this.options.cache
|
|
288
|
-
});
|
|
289
|
-
|
|
285
|
+
// Fetch root files (single source of truth: ROOT_FILES) as sibling keys
|
|
290
286
|
const fetchedFiles = {
|
|
291
287
|
'.claude': claudeFiles,
|
|
292
|
-
'.agents': agentsFiles
|
|
293
|
-
'CLAUDE.md': claudeMd
|
|
288
|
+
'.agents': agentsFiles
|
|
294
289
|
};
|
|
295
290
|
|
|
291
|
+
for (const rootFile of ROOT_FILES) {
|
|
292
|
+
fetchedFiles[rootFile] = await this.fetcher.fetchFile(rootFile, {
|
|
293
|
+
version: this.options.version,
|
|
294
|
+
useCache: this.options.cache
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
296
298
|
// Calculate total files for progress tracking
|
|
297
299
|
this.progress.total = this._countFiles(fetchedFiles);
|
|
298
300
|
|
|
@@ -419,7 +421,7 @@ class Installer extends EventEmitter {
|
|
|
419
421
|
const requiredPaths = [
|
|
420
422
|
path.join(this.options.targetDir, '.claude'),
|
|
421
423
|
path.join(this.options.targetDir, '.agents'),
|
|
422
|
-
path.join(this.options.targetDir,
|
|
424
|
+
...ROOT_FILES.map(name => path.join(this.options.targetDir, name))
|
|
423
425
|
];
|
|
424
426
|
|
|
425
427
|
const missingPaths = [];
|
|
@@ -434,7 +436,7 @@ class Installer extends EventEmitter {
|
|
|
434
436
|
if (this.writer.validateInstallationCompleteness) {
|
|
435
437
|
const completenessResult = await this.writer.validateInstallationCompleteness(
|
|
436
438
|
this.options.targetDir,
|
|
437
|
-
[
|
|
439
|
+
[...ROOT_FILES]
|
|
438
440
|
);
|
|
439
441
|
|
|
440
442
|
if (!completenessResult.complete) {
|
|
@@ -677,7 +679,6 @@ class Installer extends EventEmitter {
|
|
|
677
679
|
// Check for existing directories and files that will be overwritten
|
|
678
680
|
const claudeDir = path.join(this.options.targetDir, '.claude');
|
|
679
681
|
const agentsDir = path.join(this.options.targetDir, '.agents');
|
|
680
|
-
const claudeMd = path.join(this.options.targetDir, 'CLAUDE.md');
|
|
681
682
|
|
|
682
683
|
const existingPaths = [];
|
|
683
684
|
if (await this.validator.pathExists(claudeDir)) {
|
|
@@ -686,8 +687,10 @@ class Installer extends EventEmitter {
|
|
|
686
687
|
if (await this.validator.pathExists(agentsDir)) {
|
|
687
688
|
existingPaths.push('.agents/');
|
|
688
689
|
}
|
|
689
|
-
|
|
690
|
-
|
|
690
|
+
for (const rootFile of ROOT_FILES) {
|
|
691
|
+
if (await this.validator.pathExists(path.join(this.options.targetDir, rootFile))) {
|
|
692
|
+
existingPaths.push(rootFile);
|
|
693
|
+
}
|
|
691
694
|
}
|
|
692
695
|
|
|
693
696
|
if (existingPaths.length > 0) {
|
|
@@ -718,12 +721,16 @@ class Installer extends EventEmitter {
|
|
|
718
721
|
});
|
|
719
722
|
}
|
|
720
723
|
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
724
|
+
// Root files are required (same semantics as CLAUDE.md): if any ROOT_FILES
|
|
725
|
+
// entry is missing after fetch, validation reports it incomplete.
|
|
726
|
+
for (const rootFile of ROOT_FILES) {
|
|
727
|
+
if (!fetchedFiles[rootFile]) {
|
|
728
|
+
issues.push({
|
|
729
|
+
type: 'missing_component',
|
|
730
|
+
component: rootFile,
|
|
731
|
+
message: `Missing ${rootFile} file`
|
|
732
|
+
});
|
|
733
|
+
}
|
|
727
734
|
}
|
|
728
735
|
|
|
729
736
|
// Check .claude structure if present
|
package/src/orchestrator/main.js
CHANGED
|
@@ -16,6 +16,7 @@ const ora = require('ora');
|
|
|
16
16
|
// Import core modules (these will be implemented in WP02-06)
|
|
17
17
|
const logger = require('./utils/logger');
|
|
18
18
|
const { InstallationError } = require('./utils/errors');
|
|
19
|
+
const { ROOT_FILES } = require('../utils/constants');
|
|
19
20
|
const fetcher = require('./fetcher/github');
|
|
20
21
|
const fileWriter = require('./installer/file-writer');
|
|
21
22
|
const dependencyChecker = require('./installer/dependency-check');
|
|
@@ -104,11 +105,12 @@ async function validateEnvironment(targetDir, checkPython) {
|
|
|
104
105
|
*/
|
|
105
106
|
async function handleExistingFiles(targetDir, force, verify, dryRun) {
|
|
106
107
|
const claudeDir = path.join(targetDir, '.claude');
|
|
107
|
-
const claudeMd = path.join(targetDir, 'CLAUDE.md');
|
|
108
108
|
|
|
109
109
|
const existingFiles = [];
|
|
110
110
|
if (await fs.pathExists(claudeDir)) existingFiles.push('.claude/');
|
|
111
|
-
|
|
111
|
+
for (const rootFile of ROOT_FILES) {
|
|
112
|
+
if (await fs.pathExists(path.join(targetDir, rootFile))) existingFiles.push(rootFile);
|
|
113
|
+
}
|
|
112
114
|
|
|
113
115
|
if (existingFiles.length === 0) {
|
|
114
116
|
logger.info('No existing files found - proceeding with fresh installation');
|
|
@@ -232,13 +234,12 @@ async function verifyInstallation(targetDir) {
|
|
|
232
234
|
|
|
233
235
|
try {
|
|
234
236
|
const claudeDir = path.join(targetDir, '.claude');
|
|
235
|
-
const claudeMd = path.join(targetDir, 'CLAUDE.md');
|
|
236
237
|
const settingsPath = path.join(claudeDir, 'settings.local.json');
|
|
237
238
|
|
|
238
239
|
// Check required files exist
|
|
239
240
|
const checks = [
|
|
240
241
|
{ path: claudeDir, name: '.claude directory' },
|
|
241
|
-
{ path:
|
|
242
|
+
...ROOT_FILES.map(name => ({ path: path.join(targetDir, name), name })),
|
|
242
243
|
{ path: settingsPath, name: 'settings.local.json' }
|
|
243
244
|
];
|
|
244
245
|
|
|
@@ -133,6 +133,12 @@ class MockFetcher {
|
|
|
133
133
|
content: '# Claude Code Multi-Agent Setup\n\nThis project is configured for Claude multi-agent orchestration.\n\n## Features\n- Multi-agent team coordination\n- Observability and monitoring\n- Hook-based extensibility\n\n## Getting Started\nReview .claude/settings.local.json for configuration.',
|
|
134
134
|
encoding: 'utf-8',
|
|
135
135
|
sha: 'mock-claude-md-sha'
|
|
136
|
+
},
|
|
137
|
+
'GEMINI.md': {
|
|
138
|
+
path: 'GEMINI.md',
|
|
139
|
+
content: '# Gemini Multi-Agent Setup\n\nThis project is configured for Gemini multi-agent orchestration.\n\n## Features\n- Multi-agent team coordination\n- Observability and monitoring\n- Hook-based extensibility\n\n## Getting Started\nReview .claude/settings.local.json for configuration.',
|
|
140
|
+
encoding: 'utf-8',
|
|
141
|
+
sha: 'mock-gemini-md-sha'
|
|
136
142
|
}
|
|
137
143
|
}
|
|
138
144
|
};
|
package/src/utils/config.js
CHANGED
|
@@ -7,6 +7,7 @@ const path = require('path');
|
|
|
7
7
|
const fs = require('fs').promises;
|
|
8
8
|
const { ErrorFactory } = require('./errors');
|
|
9
9
|
const platform = require('./platform');
|
|
10
|
+
const { ROOT_FILES } = require('./constants');
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Configuration manager with defaults, validation, and persistence
|
|
@@ -55,7 +56,7 @@ class Config {
|
|
|
55
56
|
// File operations
|
|
56
57
|
files: {
|
|
57
58
|
excludePatterns: ['.git', 'node_modules', '.DS_Store'],
|
|
58
|
-
includePatterns: ['.claude/**/*',
|
|
59
|
+
includePatterns: ['.claude/**/*', ...ROOT_FILES],
|
|
59
60
|
encoding: 'utf8',
|
|
60
61
|
lineEnding: 'auto', // auto, lf, crlf
|
|
61
62
|
maxFileSize: 10 * 1024 * 1024 // 10MB
|
package/src/utils/constants.js
CHANGED
|
@@ -44,6 +44,15 @@ const GITHUB_API = {
|
|
|
44
44
|
}
|
|
45
45
|
};
|
|
46
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Root-level files fetched/installed/validated as siblings.
|
|
49
|
+
*
|
|
50
|
+
* SINGLE SOURCE OF TRUTH for root filenames: adding the next root file is a
|
|
51
|
+
* one-line change to this array. Every fetch strategy (tarball, raw-URL,
|
|
52
|
+
* individual), every validation, and every verify step derives from this list.
|
|
53
|
+
*/
|
|
54
|
+
const ROOT_FILES = ['CLAUDE.md', 'GEMINI.md'];
|
|
55
|
+
|
|
47
56
|
/**
|
|
48
57
|
* File paths and patterns
|
|
49
58
|
*/
|
|
@@ -52,6 +61,9 @@ const FILE_PATHS = {
|
|
|
52
61
|
CLAUDE_DIR: '.claude',
|
|
53
62
|
CLAUDE_MD: 'CLAUDE.md',
|
|
54
63
|
|
|
64
|
+
// Root-level files (single source of truth)
|
|
65
|
+
ROOT_FILES,
|
|
66
|
+
|
|
55
67
|
// Claude directory structure
|
|
56
68
|
CLAUDE_SUBDIRS: [
|
|
57
69
|
'.claude/agents',
|
|
@@ -60,12 +72,12 @@ const FILE_PATHS = {
|
|
|
60
72
|
'.claude/hooks/comms'
|
|
61
73
|
],
|
|
62
74
|
|
|
63
|
-
// Key files to install
|
|
75
|
+
// Key files to install (root files derived from ROOT_FILES)
|
|
64
76
|
REQUIRED_FILES: [
|
|
65
77
|
'.claude/settings.json',
|
|
66
78
|
'.claude/hooks/comms/get_unread_messages.py',
|
|
67
79
|
'.claude/hooks/comms/send_message.py',
|
|
68
|
-
|
|
80
|
+
...ROOT_FILES
|
|
69
81
|
],
|
|
70
82
|
|
|
71
83
|
// Configuration files
|
|
@@ -382,6 +394,7 @@ module.exports = {
|
|
|
382
394
|
GITHUB_API,
|
|
383
395
|
GITHUB_URLS,
|
|
384
396
|
FILE_PATHS,
|
|
397
|
+
ROOT_FILES,
|
|
385
398
|
ERROR_CODES,
|
|
386
399
|
HTTP_STATUS,
|
|
387
400
|
RETRY_CONFIG,
|