@girardmedia/bootspring 2.0.21 → 2.0.23
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/bootspring.js +5 -0
- package/cli/org.js +474 -0
- package/cli/preseed/index.js +16 -0
- package/cli/preseed/interactive.js +143 -0
- package/cli/preseed/templates.js +227 -0
- package/cli/preseed.js +9 -301
- package/cli/seed/builders/ai-context-builder.js +85 -0
- package/cli/seed/builders/index.js +13 -0
- package/cli/seed/builders/seed-builder.js +272 -0
- package/cli/seed/extractors/content-extractors.js +383 -0
- package/cli/seed/extractors/index.js +47 -0
- package/cli/seed/extractors/metadata-extractors.js +167 -0
- package/cli/seed/extractors/section-extractor.js +54 -0
- package/cli/seed/extractors/stack-extractors.js +228 -0
- package/cli/seed/index.js +18 -0
- package/cli/seed/utils/folder-structure.js +84 -0
- package/cli/seed/utils/index.js +11 -0
- package/cli/seed.js +23 -1074
- package/core/api-client.js +77 -0
- package/core/entitlements.js +36 -0
- package/core/organizations.js +223 -0
- package/core/policies.js +51 -6
- package/core/policy-matrix.js +303 -0
- package/core/project-context.js +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.js +3220 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/context-McpJQa_2.d.ts +5710 -0
- package/dist/core/index.d.ts +635 -0
- package/dist/core/index.js +2593 -0
- package/dist/core/index.js.map +1 -0
- package/dist/index-QqbeEiDm.d.ts +857 -0
- package/dist/index-UiYCgwiH.d.ts +174 -0
- package/dist/index.d.ts +453 -0
- package/dist/index.js +44228 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.js +41173 -0
- package/dist/mcp/index.js.map +1 -0
- package/generators/index.ts +82 -0
- package/intelligence/orchestrator/config/failure-signatures.js +48 -0
- package/intelligence/orchestrator/config/index.js +23 -0
- package/intelligence/orchestrator/config/pack-lifecycle.js +262 -0
- package/intelligence/orchestrator/config/phases.js +111 -0
- package/intelligence/orchestrator/config/remediation.js +150 -0
- package/intelligence/orchestrator/config/workflows.js +168 -0
- package/intelligence/orchestrator/core/index.js +16 -0
- package/intelligence/orchestrator/core/state-manager.js +88 -0
- package/intelligence/orchestrator/core/telemetry.js +24 -0
- package/intelligence/orchestrator/index.js +17 -0
- package/intelligence/orchestrator.js +17 -512
- package/mcp/contracts/mcp-contract.v1.json +1 -1
- package/package.json +16 -3
- package/src/cli/agent.ts +703 -0
- package/src/cli/analyze.ts +640 -0
- package/src/cli/audit.ts +707 -0
- package/src/cli/auth.ts +930 -0
- package/src/cli/billing.ts +364 -0
- package/src/cli/build.ts +1089 -0
- package/src/cli/business.ts +508 -0
- package/src/cli/checkpoint-utils.ts +236 -0
- package/src/cli/checkpoint.ts +757 -0
- package/src/cli/cloud-sync.ts +534 -0
- package/src/cli/content.ts +273 -0
- package/src/cli/context.ts +667 -0
- package/src/cli/dashboard.ts +133 -0
- package/src/cli/deploy.ts +704 -0
- package/src/cli/doctor.ts +480 -0
- package/src/cli/fundraise.ts +494 -0
- package/src/cli/generate.ts +346 -0
- package/src/cli/github-cmd.ts +566 -0
- package/src/cli/health.ts +599 -0
- package/src/cli/index.ts +113 -0
- package/src/cli/init.ts +838 -0
- package/src/cli/legal.ts +495 -0
- package/src/cli/log.ts +316 -0
- package/src/cli/loop.ts +1660 -0
- package/src/cli/manager.ts +878 -0
- package/src/cli/mcp.ts +275 -0
- package/src/cli/memory.ts +346 -0
- package/src/cli/metrics.ts +590 -0
- package/src/cli/monitor.ts +960 -0
- package/src/cli/mvp.ts +662 -0
- package/src/cli/onboard.ts +663 -0
- package/src/cli/orchestrator.ts +622 -0
- package/src/cli/plugin.ts +483 -0
- package/src/cli/prd.ts +671 -0
- package/src/cli/preseed-start.ts +1633 -0
- package/src/cli/preseed.ts +2434 -0
- package/src/cli/project.ts +526 -0
- package/src/cli/quality.ts +885 -0
- package/src/cli/security.ts +1079 -0
- package/src/cli/seed.ts +1224 -0
- package/src/cli/skill.ts +537 -0
- package/src/cli/suggest.ts +1225 -0
- package/src/cli/switch.ts +518 -0
- package/src/cli/task.ts +780 -0
- package/src/cli/telemetry.ts +172 -0
- package/src/cli/todo.ts +627 -0
- package/src/cli/types.ts +15 -0
- package/src/cli/update.ts +334 -0
- package/src/cli/visualize.ts +609 -0
- package/src/cli/watch.ts +895 -0
- package/src/cli/workspace.ts +709 -0
- package/src/core/action-recorder.ts +673 -0
- package/src/core/analyze-workflow.ts +1453 -0
- package/src/core/api-client.ts +1120 -0
- package/src/core/audit-workflow.ts +1681 -0
- package/src/core/auth.ts +471 -0
- package/src/core/build-orchestrator.ts +509 -0
- package/src/core/build-state.ts +621 -0
- package/src/core/checkpoint-engine.ts +482 -0
- package/src/core/config.ts +1285 -0
- package/src/core/context-loader.ts +694 -0
- package/src/core/context.ts +410 -0
- package/src/core/deploy-workflow.ts +1085 -0
- package/src/core/entitlements.ts +322 -0
- package/src/core/github-sync.ts +720 -0
- package/src/core/index.ts +981 -0
- package/src/core/ingest.ts +1186 -0
- package/src/core/metrics-engine.ts +886 -0
- package/src/core/mvp.ts +847 -0
- package/src/core/onboard-workflow.ts +1293 -0
- package/src/core/policies.ts +81 -0
- package/src/core/preseed-workflow.ts +1163 -0
- package/src/core/preseed.ts +1826 -0
- package/src/core/project-context.ts +380 -0
- package/src/core/project-state.ts +699 -0
- package/src/core/r2-sync.ts +691 -0
- package/src/core/scaffold.ts +1715 -0
- package/src/core/session.ts +286 -0
- package/src/core/task-extractor.ts +799 -0
- package/src/core/telemetry.ts +371 -0
- package/src/core/tier-enforcement.ts +737 -0
- package/src/core/utils.ts +437 -0
- package/src/index.ts +29 -0
- package/src/intelligence/agent-collab.ts +2376 -0
- package/src/intelligence/auto-suggest.ts +713 -0
- package/src/intelligence/content-gen.ts +1351 -0
- package/src/intelligence/cross-project.ts +1692 -0
- package/src/intelligence/git-memory.ts +529 -0
- package/src/intelligence/index.ts +318 -0
- package/src/intelligence/orchestrator.ts +534 -0
- package/src/intelligence/prd.ts +466 -0
- package/src/intelligence/recommendations.ts +982 -0
- package/src/intelligence/workflow-composer.ts +1472 -0
- package/src/mcp/capabilities.ts +233 -0
- package/src/mcp/index.ts +37 -0
- package/src/mcp/registry.ts +1268 -0
- package/src/mcp/response-formatter.ts +797 -0
- package/src/mcp/server.ts +240 -0
- package/src/types/agent.ts +69 -0
- package/src/types/config.ts +86 -0
- package/src/types/context.ts +77 -0
- package/src/types/index.ts +53 -0
- package/src/types/mcp.ts +91 -0
- package/src/types/skills.ts +47 -0
- package/src/types/workflow.ts +155 -0
- package/generators/index.js +0 -18
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bootspring Project Context Enforcement
|
|
3
|
+
*
|
|
4
|
+
* Ensures CLI commands run within a project context.
|
|
5
|
+
* All activity should be tracked to a project for:
|
|
6
|
+
* - Usage billing
|
|
7
|
+
* - Memory/learning isolation
|
|
8
|
+
* - Team collaboration
|
|
9
|
+
* - Audit trails
|
|
10
|
+
*
|
|
11
|
+
* @package bootspring
|
|
12
|
+
* @module core/project-context
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
// Module interfaces
|
|
16
|
+
interface Session {
|
|
17
|
+
getEffectiveProject: () => Project | null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface Auth {
|
|
21
|
+
isAuthenticated: () => boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface ApiClient {
|
|
25
|
+
listProjects: () => Promise<ProjectListResponse>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface Project {
|
|
29
|
+
id: string;
|
|
30
|
+
name?: string | undefined;
|
|
31
|
+
slug?: string | undefined;
|
|
32
|
+
role?: string | undefined;
|
|
33
|
+
source?: string | undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface ProjectListResponse {
|
|
37
|
+
owned?: Project[] | undefined;
|
|
38
|
+
shared?: Project[] | undefined;
|
|
39
|
+
projects?: Project[] | undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface ProjectContextError extends Error {
|
|
43
|
+
code?: string | undefined;
|
|
44
|
+
help?: string[] | undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface ValidationError {
|
|
48
|
+
code: string;
|
|
49
|
+
message: string;
|
|
50
|
+
help: string[];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface ValidationResult {
|
|
54
|
+
valid: boolean;
|
|
55
|
+
exempt?: boolean | undefined;
|
|
56
|
+
project?: Project | undefined;
|
|
57
|
+
error?: ValidationError | undefined;
|
|
58
|
+
requiresAccessCheck?: boolean | undefined;
|
|
59
|
+
warning?: string | undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
interface ValidateOptions {
|
|
63
|
+
projectOverride?: string | undefined;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface ParsedProjectFlag {
|
|
67
|
+
projectOverride: string | null;
|
|
68
|
+
cleanArgs: string[];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Lazy-loaded modules
|
|
72
|
+
let _session: Session | null = null;
|
|
73
|
+
let _auth: Auth | null = null;
|
|
74
|
+
let _api: ApiClient | null = null;
|
|
75
|
+
|
|
76
|
+
function getSession(): Session {
|
|
77
|
+
if (!_session) {
|
|
78
|
+
_session = require('./session') as Session;
|
|
79
|
+
}
|
|
80
|
+
return _session;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function getAuth(): Auth {
|
|
84
|
+
if (!_auth) {
|
|
85
|
+
_auth = require('./auth') as Auth;
|
|
86
|
+
}
|
|
87
|
+
return _auth;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function getApi(): ApiClient {
|
|
91
|
+
if (!_api) {
|
|
92
|
+
_api = require('./api-client') as ApiClient;
|
|
93
|
+
}
|
|
94
|
+
return _api;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Commands that don't require project context
|
|
98
|
+
export const EXEMPT_COMMANDS = [
|
|
99
|
+
'auth',
|
|
100
|
+
'switch',
|
|
101
|
+
'project',
|
|
102
|
+
'help',
|
|
103
|
+
'init',
|
|
104
|
+
'doctor',
|
|
105
|
+
'update',
|
|
106
|
+
'version',
|
|
107
|
+
'--version',
|
|
108
|
+
'-v',
|
|
109
|
+
'--help',
|
|
110
|
+
'-h',
|
|
111
|
+
'cloud-sync',
|
|
112
|
+
'skill', // Skills accessible in local mode without project
|
|
113
|
+
'agent', // Agent list accessible without project
|
|
114
|
+
'billing', // Billing status/info accessible without project
|
|
115
|
+
'preseed', // Preseed works locally, auth enhances features
|
|
116
|
+
'seed', // Seed works locally for scaffolding
|
|
117
|
+
];
|
|
118
|
+
|
|
119
|
+
// Sub-commands of auth that are exempt
|
|
120
|
+
const EXEMPT_AUTH_SUBCOMMANDS = [
|
|
121
|
+
'login',
|
|
122
|
+
'logout',
|
|
123
|
+
'register',
|
|
124
|
+
'signup',
|
|
125
|
+
'status',
|
|
126
|
+
'whoami',
|
|
127
|
+
'switch' // auth switch is also exempt
|
|
128
|
+
];
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Check if a command requires project context
|
|
132
|
+
*/
|
|
133
|
+
export function requiresProjectContext(command: string, subcommand?: string): boolean {
|
|
134
|
+
// Check if main command is exempt
|
|
135
|
+
if (EXEMPT_COMMANDS.includes(command)) {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Check exempt auth subcommands
|
|
140
|
+
if (command === 'auth' && subcommand && EXEMPT_AUTH_SUBCOMMANDS.includes(subcommand)) {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Get the current project context
|
|
149
|
+
* Returns null if no project is set
|
|
150
|
+
*/
|
|
151
|
+
export function getProjectContext(): Project | null {
|
|
152
|
+
return getSession().getEffectiveProject();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Check if project context is set
|
|
157
|
+
*/
|
|
158
|
+
export function hasProjectContext(): boolean {
|
|
159
|
+
return !!getProjectContext();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Get project ID for API requests
|
|
164
|
+
*/
|
|
165
|
+
export function getProjectId(): string | null {
|
|
166
|
+
const project = getProjectContext();
|
|
167
|
+
return project?.id || null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Get headers for API requests including project context
|
|
172
|
+
*/
|
|
173
|
+
export function getProjectHeaders(): Record<string, string> {
|
|
174
|
+
const projectId = getProjectId();
|
|
175
|
+
const headers: Record<string, string> = {};
|
|
176
|
+
|
|
177
|
+
if (projectId) {
|
|
178
|
+
headers['X-Project-Id'] = projectId;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return headers;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Require project context - throws if not set
|
|
186
|
+
*/
|
|
187
|
+
export function requireProject(): Project {
|
|
188
|
+
const project = getProjectContext();
|
|
189
|
+
|
|
190
|
+
if (!project) {
|
|
191
|
+
const error = new Error('No project context set') as ProjectContextError;
|
|
192
|
+
error.code = 'NO_PROJECT_CONTEXT';
|
|
193
|
+
error.help = [
|
|
194
|
+
'All Bootspring commands require a project context.',
|
|
195
|
+
'',
|
|
196
|
+
'To set a project:',
|
|
197
|
+
' bootspring switch # List and select a project',
|
|
198
|
+
' bootspring switch <name> # Switch to a specific project',
|
|
199
|
+
'',
|
|
200
|
+
'Or use the --project flag:',
|
|
201
|
+
' bootspring --project myapp <command>',
|
|
202
|
+
'',
|
|
203
|
+
'Create a project at: https://bootspring.com/dashboard/projects'
|
|
204
|
+
];
|
|
205
|
+
throw error;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return project;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Validate project context before running a command
|
|
213
|
+
*/
|
|
214
|
+
export function validateForCommand(
|
|
215
|
+
command: string,
|
|
216
|
+
args: string[] = [],
|
|
217
|
+
options: ValidateOptions = {}
|
|
218
|
+
): ValidationResult {
|
|
219
|
+
const subcommand = args[0];
|
|
220
|
+
|
|
221
|
+
// Check if command is exempt
|
|
222
|
+
if (!requiresProjectContext(command, subcommand)) {
|
|
223
|
+
return { valid: true, exempt: true };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Check if user is authenticated
|
|
227
|
+
if (!getAuth().isAuthenticated()) {
|
|
228
|
+
return {
|
|
229
|
+
valid: false,
|
|
230
|
+
error: {
|
|
231
|
+
code: 'NOT_AUTHENTICATED',
|
|
232
|
+
message: 'Authentication required',
|
|
233
|
+
help: ['Run: bootspring auth login']
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Check for project override flag
|
|
239
|
+
if (options.projectOverride) {
|
|
240
|
+
// Validation happens async in validateProjectAccessAsync if needed
|
|
241
|
+
return {
|
|
242
|
+
valid: true,
|
|
243
|
+
project: { id: options.projectOverride, source: 'flag' },
|
|
244
|
+
requiresAccessCheck: true
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Check for project context
|
|
249
|
+
const project = getProjectContext();
|
|
250
|
+
if (!project) {
|
|
251
|
+
return {
|
|
252
|
+
valid: false,
|
|
253
|
+
error: {
|
|
254
|
+
code: 'NO_PROJECT_CONTEXT',
|
|
255
|
+
message: 'No project context set',
|
|
256
|
+
help: [
|
|
257
|
+
'Run: bootspring switch',
|
|
258
|
+
'Or use: bootspring --project <name> ' + command
|
|
259
|
+
]
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return { valid: true, project };
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Format error message for display
|
|
269
|
+
*/
|
|
270
|
+
export function formatError(error: ValidationError): string {
|
|
271
|
+
const lines: string[] = [];
|
|
272
|
+
const RED = '\x1b[31m';
|
|
273
|
+
const YELLOW = '\x1b[33m';
|
|
274
|
+
const DIM = '\x1b[2m';
|
|
275
|
+
const RESET = '\x1b[0m';
|
|
276
|
+
|
|
277
|
+
lines.push(`${RED}Error: ${error.message}${RESET}`);
|
|
278
|
+
lines.push('');
|
|
279
|
+
|
|
280
|
+
if (error.help && error.help.length > 0) {
|
|
281
|
+
for (const line of error.help) {
|
|
282
|
+
if (line.startsWith(' ')) {
|
|
283
|
+
lines.push(`${YELLOW}${line}${RESET}`);
|
|
284
|
+
} else {
|
|
285
|
+
lines.push(`${DIM}${line}${RESET}`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return lines.join('\n');
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Parse --project flag from args
|
|
295
|
+
*/
|
|
296
|
+
export function parseProjectFlag(args: string[]): ParsedProjectFlag {
|
|
297
|
+
const cleanArgs: string[] = [];
|
|
298
|
+
let projectOverride: string | null = null;
|
|
299
|
+
|
|
300
|
+
for (let i = 0; i < args.length; i++) {
|
|
301
|
+
const arg = args[i];
|
|
302
|
+
|
|
303
|
+
if (arg === '--project' || arg === '-p') {
|
|
304
|
+
// Next arg is the project name/id
|
|
305
|
+
if (i + 1 < args.length) {
|
|
306
|
+
const nextArg = args[i + 1];
|
|
307
|
+
if (nextArg) {
|
|
308
|
+
projectOverride = nextArg;
|
|
309
|
+
}
|
|
310
|
+
i++; // Skip next arg
|
|
311
|
+
}
|
|
312
|
+
} else if (arg && arg.startsWith('--project=')) {
|
|
313
|
+
const value = arg.split('=')[1];
|
|
314
|
+
if (value) {
|
|
315
|
+
projectOverride = value;
|
|
316
|
+
}
|
|
317
|
+
} else if (arg) {
|
|
318
|
+
cleanArgs.push(arg);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return { projectOverride, cleanArgs };
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Validate user has access to a project (async)
|
|
327
|
+
* Call this after validateForCommand when requiresAccessCheck is true
|
|
328
|
+
*/
|
|
329
|
+
export async function validateProjectAccessAsync(projectId: string): Promise<ValidationResult> {
|
|
330
|
+
try {
|
|
331
|
+
const projects = await getApi().listProjects();
|
|
332
|
+
|
|
333
|
+
// Check both owned and shared projects
|
|
334
|
+
const allProjects = [
|
|
335
|
+
...(projects.owned || []),
|
|
336
|
+
...(projects.shared || []),
|
|
337
|
+
...(projects.projects || []) // fallback for flat list
|
|
338
|
+
];
|
|
339
|
+
|
|
340
|
+
// Find project by id, slug, or name (case-insensitive)
|
|
341
|
+
const project = allProjects.find(p =>
|
|
342
|
+
p.id === projectId ||
|
|
343
|
+
p.slug === projectId ||
|
|
344
|
+
p.name?.toLowerCase() === projectId.toLowerCase()
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
if (!project) {
|
|
348
|
+
return {
|
|
349
|
+
valid: false,
|
|
350
|
+
error: {
|
|
351
|
+
code: 'PROJECT_NOT_FOUND',
|
|
352
|
+
message: `Project '${projectId}' not found or you don't have access`,
|
|
353
|
+
help: [
|
|
354
|
+
'Check the project name/id is correct',
|
|
355
|
+
'Run: bootspring project list',
|
|
356
|
+
'Or request access from the project owner'
|
|
357
|
+
]
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return {
|
|
363
|
+
valid: true,
|
|
364
|
+
project: {
|
|
365
|
+
id: project.id,
|
|
366
|
+
name: project.name,
|
|
367
|
+
slug: project.slug,
|
|
368
|
+
role: project.role || 'owner',
|
|
369
|
+
source: 'flag'
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
} catch (_error) {
|
|
373
|
+
// If API call fails, allow access (fail open for offline mode)
|
|
374
|
+
return {
|
|
375
|
+
valid: true,
|
|
376
|
+
project: { id: projectId, source: 'flag' },
|
|
377
|
+
warning: 'Could not verify project access (offline mode)'
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
}
|