@sequenceholdings/studio-cli 0.1.21 → 0.1.24

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.
@@ -5,9 +5,34 @@
5
5
  import { getAccessToken, NotLoggedInError } from '../auth.js';
6
6
  import { AtlasApiError, getJson, postJson } from '../atlas-client.js';
7
7
  import { resolveEnvWithDiscovery } from '../config.js';
8
- import { isFullSha, requiresPinnedSha } from './pinning.js';
9
8
  const LOG = '[seq-studio]';
10
9
  const TERMINAL = new Set(['active', 'failed', 'retired']);
10
+ const POLL_INTERVAL_MS = 2000;
11
+ const HEARTBEAT_INTERVAL_MS = 20_000;
12
+ const FIRST_PARTY_TARGETS = [
13
+ { id: 'dev', label: 'Development', requiresApproval: false },
14
+ { id: 'staging', label: 'Staging', requiresApproval: false },
15
+ { id: 'production', label: 'Production', requiresApproval: true },
16
+ { id: 'banksouth', label: 'BankSouth', requiresApproval: true },
17
+ ];
18
+ function formatElapsed(ms) {
19
+ const totalSec = Math.floor(ms / 1000);
20
+ if (totalSec < 60)
21
+ return `${totalSec}s`;
22
+ const min = Math.floor(totalSec / 60);
23
+ const sec = totalSec % 60;
24
+ return sec > 0 ? `${min}m ${sec}s` : `${min}m`;
25
+ }
26
+ function deploymentProgressSuffix(detail) {
27
+ if (detail.triggerRunId)
28
+ return ` (trigger ${detail.triggerRunId})`;
29
+ return '';
30
+ }
31
+ function deploymentStatusLabel(detail) {
32
+ if (detail.statusDetail)
33
+ return `${detail.status} — ${detail.statusDetail}`;
34
+ return detail.status;
35
+ }
11
36
  async function envAndToken(args) {
12
37
  const requested = typeof args.flags.e === 'string' ? args.flags.e : typeof args.flags.env === 'string' ? args.flags.env : undefined;
13
38
  if (!requested) {
@@ -31,21 +56,32 @@ function flagString(flags, key) {
31
56
  throw new Error(`--${key} requires a value`);
32
57
  return value;
33
58
  }
34
- /**
35
- * The CLI's local dev-loop env is named `local` — chosen so `-e local` lines
36
- * up with `seqapi -e local` / `artifact-studio --env local` (see `config.ts`
37
- * `BUILT_IN_ENV_URLS`). There is no `local` in the server's deploy-lifecycle
38
- * enum (`DEPLOY_ENVIRONMENTS` in `atlas/src/server/services/data-pipelines/schema.ts`
39
- * is `dev | staging | production | banksouth`); the server's name for that
40
- * same developer-loop target is `dev` (`targetFactsForEnvironment` treats
41
- * `dev` as the one target exempt from a `lakebase_branch` binding). Map at
42
- * this one boundary — every lifecycle request body funnels through here —
43
- * so the CLI never sends the wire-invalid `local` and every other env name
44
- * passes through unchanged.
45
- */
46
59
  export function deployEnvironmentForEnv(env) {
47
60
  return env.name === 'local' ? 'dev' : env.name;
48
61
  }
62
+ async function resolvePipelineTarget({ args, env, token, }) {
63
+ const requested = flagString(args.flags, 'target');
64
+ const inferredId = requested ?? deployEnvironmentForEnv(env);
65
+ if (!requested) {
66
+ const firstPartyTarget = FIRST_PARTY_TARGETS.find((target) => target.id === inferredId);
67
+ if (firstPartyTarget)
68
+ return firstPartyTarget;
69
+ }
70
+ const response = await getJson({
71
+ baseUrl: env.url,
72
+ token,
73
+ path: '/api/data-pipelines/targets',
74
+ });
75
+ const targets = response.targets;
76
+ const matchingTarget = targets.find((target) => target.id === inferredId);
77
+ if (matchingTarget)
78
+ return matchingTarget;
79
+ if (!requested && targets.length === 1)
80
+ return targets[0];
81
+ const available = targets.map((target) => target.id).join(', ') || 'none';
82
+ throw new Error(`could not select a pipeline target for '${env.name}'; available targets: ${available}. ` +
83
+ 'Pass --target <id> explicitly.');
84
+ }
49
85
  export async function pipelinePlanCommand(args) {
50
86
  const repo = flagString(args.flags, 'repo');
51
87
  const ref = flagString(args.flags, 'ref');
@@ -55,13 +91,21 @@ export async function pipelinePlanCommand(args) {
55
91
  }
56
92
  const { env, token } = await envAndToken(args);
57
93
  const json = args.flags.json === true || args.flags.json === 'true';
94
+ let target;
95
+ try {
96
+ target = await resolvePipelineTarget({ args, env, token });
97
+ }
98
+ catch (error) {
99
+ console.error(`${LOG} plan failed: ${error instanceof Error ? error.message : String(error)}`);
100
+ return 1;
101
+ }
58
102
  let response;
59
103
  try {
60
104
  response = await postJson({
61
105
  baseUrl: env.url,
62
106
  token,
63
107
  path: '/api/data-pipelines/pipelines/plan',
64
- body: { repo, ref, environment: deployEnvironmentForEnv(env) },
108
+ body: { repo, ref, environment: target.id },
65
109
  });
66
110
  }
67
111
  catch (error) {
@@ -86,8 +130,12 @@ export async function pipelineDeployCommand(args) {
86
130
  const deploymentId = flagString(args.flags, 'deployment-id');
87
131
  const approvedBy = flagString(args.flags, 'approved-by');
88
132
  const { env, token } = await envAndToken(args);
89
- if (requiresPinnedSha(env.name) && ref && !isFullSha(ref)) {
90
- console.error(`${LOG} deploy to ${env.name} requires a pinned 40-hex commit SHA (got '${ref}')`);
133
+ let target;
134
+ try {
135
+ target = await resolvePipelineTarget({ args, env, token });
136
+ }
137
+ catch (error) {
138
+ console.error(`${LOG} deploy failed: ${error instanceof Error ? error.message : String(error)}`);
91
139
  return 1;
92
140
  }
93
141
  let enqueue;
@@ -96,19 +144,19 @@ export async function pipelineDeployCommand(args) {
96
144
  // Direct execute of a persisted plan.
97
145
  const stageId = flagString(args.flags, 'stage-id');
98
146
  if (!stageId) {
99
- console.error('usage: seq-studio pipeline deploy --deployment-id <id> --stage-id <id> -e <env> [--approved-by <sub>] [--no-wait]');
147
+ console.error('usage: seq-studio pipeline deploy --deployment-id <id> --stage-id <id> -e <env> [--target <id>] [--approved-by <sub>] [--no-wait]');
100
148
  return 1;
101
149
  }
102
150
  enqueue = await postJson({
103
151
  baseUrl: env.url,
104
152
  token,
105
153
  path: `/api/data-pipelines/stages/${stageId}/deploy`,
106
- body: { deploymentId, ...(approvedBy ? { approvedBy } : {}) },
154
+ body: { deploymentId, environment: target.id, ...(approvedBy ? { approvedBy } : {}) },
107
155
  });
108
156
  }
109
157
  else {
110
158
  if (!repo || !ref) {
111
- console.error('usage: seq-studio pipeline deploy --repo pipelines/<slug> --ref <sha> -e <env> [--approved-by <sub>] [--no-wait]');
159
+ console.error('usage: seq-studio pipeline deploy --repo pipelines/<slug> --ref <sha|branch> -e <env> [--target <id>] [--approved-by <sub>] [--no-wait]');
112
160
  return 1;
113
161
  }
114
162
  // Plan then deploy the first planning deployment.
@@ -116,7 +164,7 @@ export async function pipelineDeployCommand(args) {
116
164
  baseUrl: env.url,
117
165
  token,
118
166
  path: '/api/data-pipelines/pipelines/plan',
119
- body: { repo, ref, environment: deployEnvironmentForEnv(env) },
167
+ body: { repo, ref, environment: target.id },
120
168
  });
121
169
  if (plan.plan.hasDestructive) {
122
170
  console.error(`${LOG} plan has destructive findings — refusing to deploy`);
@@ -140,6 +188,7 @@ export async function pipelineDeployCommand(args) {
140
188
  path: `/api/data-pipelines/stages/${stageId}/deploy`,
141
189
  body: {
142
190
  deploymentId: firstId,
191
+ environment: target.id,
143
192
  ...(approvedBy ? { approvedBy } : {}),
144
193
  },
145
194
  });
@@ -168,8 +217,16 @@ export async function pipelinePromoteCommand(args) {
168
217
  return 1;
169
218
  }
170
219
  const { env, token } = await envAndToken(args);
171
- if (requiresPinnedSha(env.name) && !approvedBy) {
172
- console.error(`${LOG} promote to ${env.name} requires --approved-by <your sub/email> (approvals are self-recorded)`);
220
+ let target;
221
+ try {
222
+ target = await resolvePipelineTarget({ args, env, token });
223
+ }
224
+ catch (error) {
225
+ console.error(`${LOG} promote failed: ${error instanceof Error ? error.message : String(error)}`);
226
+ return 1;
227
+ }
228
+ if (target.requiresApproval && !approvedBy) {
229
+ console.error(`${LOG} promote to ${target.id} requires --approved-by <your sub/email> (approvals are self-recorded)`);
173
230
  return 1;
174
231
  }
175
232
  const stageId = await resolveStageIdBySlug({ baseUrl: env.url, token, slug: stage, repo });
@@ -181,7 +238,7 @@ export async function pipelinePromoteCommand(args) {
181
238
  path: `/api/data-pipelines/stages/${stageId}/promote`,
182
239
  body: {
183
240
  version,
184
- environment: deployEnvironmentForEnv(env),
241
+ environment: target.id,
185
242
  ...(approvedBy ? { approvedBy } : {}),
186
243
  },
187
244
  });
@@ -207,6 +264,14 @@ export async function pipelineRunNowCommand(args) {
207
264
  }
208
265
  const { env, token } = await envAndToken(args);
209
266
  const json = args.flags.json === true || args.flags.json === 'true';
267
+ let target;
268
+ try {
269
+ target = await resolvePipelineTarget({ args, env, token });
270
+ }
271
+ catch (error) {
272
+ console.error(`${LOG} run-now failed: ${error instanceof Error ? error.message : String(error)}`);
273
+ return 1;
274
+ }
210
275
  let stageId;
211
276
  try {
212
277
  stageId = await resolveStageIdBySlug({ baseUrl: env.url, token, slug: stage, repo });
@@ -221,7 +286,7 @@ export async function pipelineRunNowCommand(args) {
221
286
  baseUrl: env.url,
222
287
  token,
223
288
  path: `/api/data-pipelines/stages/${stageId}/run`,
224
- body: { environment: deployEnvironmentForEnv(env) },
289
+ body: { environment: target.id },
225
290
  });
226
291
  }
227
292
  catch (error) {
@@ -235,7 +300,7 @@ export async function pipelineRunNowCommand(args) {
235
300
  console.log(JSON.stringify(response, null, 2));
236
301
  }
237
302
  else {
238
- console.log(`${LOG} run-now ${stage} → ${deployEnvironmentForEnv(env)}`);
303
+ console.log(`${LOG} run-now ${stage} → ${target.id}`);
239
304
  console.log(`${LOG} invocation ${response.invocationId} (${response.status})`);
240
305
  console.log(`${LOG} ${response.databricks_url}`);
241
306
  }
@@ -250,8 +315,16 @@ export async function pipelineRollbackCommand(args) {
250
315
  return 1;
251
316
  }
252
317
  const { env, token } = await envAndToken(args);
253
- if (requiresPinnedSha(env.name) && !approvedBy) {
254
- console.error(`${LOG} rollback in ${env.name} requires --approved-by <your sub/email> (approvals are explicit even for rollbacks)`);
318
+ let target;
319
+ try {
320
+ target = await resolvePipelineTarget({ args, env, token });
321
+ }
322
+ catch (error) {
323
+ console.error(`${LOG} rollback failed: ${error instanceof Error ? error.message : String(error)}`);
324
+ return 1;
325
+ }
326
+ if (target.requiresApproval && !approvedBy) {
327
+ console.error(`${LOG} rollback in ${target.id} requires --approved-by <your sub/email> (approvals are explicit even for rollbacks)`);
255
328
  return 1;
256
329
  }
257
330
  const stageId = await resolveStageIdBySlug({ baseUrl: env.url, token, slug: stage, repo });
@@ -262,7 +335,7 @@ export async function pipelineRollbackCommand(args) {
262
335
  token,
263
336
  path: `/api/data-pipelines/stages/${stageId}/rollback`,
264
337
  body: {
265
- environment: deployEnvironmentForEnv(env),
338
+ environment: target.id,
266
339
  ...(approvedBy ? { approvedBy } : {}),
267
340
  },
268
341
  });
@@ -295,26 +368,165 @@ function printFindingsTable(findings) {
295
368
  async function pollDeployment({ baseUrl, token, deploymentId, }) {
296
369
  const started = Date.now();
297
370
  const timeoutMs = 15 * 60 * 1000;
371
+ let lastLoggedLabel = null;
372
+ let lastLogAt = started;
298
373
  while (Date.now() - started < timeoutMs) {
299
374
  const detail = await getJson({
300
375
  baseUrl,
301
376
  token,
302
377
  path: `/api/data-pipelines/deployments/${deploymentId}`,
303
378
  });
304
- console.log(`${LOG} deployment ${deploymentId}: ${detail.status}`);
379
+ const elapsedMs = Date.now() - started;
305
380
  if (TERMINAL.has(detail.status)) {
381
+ const elapsed = formatElapsed(elapsedMs);
382
+ const progressSuffix = deploymentProgressSuffix(detail);
306
383
  if (detail.status === 'active') {
307
- console.log(`${LOG} deploy succeeded`);
384
+ console.log(`${LOG} deployment ${deploymentId} succeeded (${elapsed})${progressSuffix}`);
308
385
  return 0;
309
386
  }
310
- console.error(`${LOG} deploy ended '${detail.status}': ${detail.statusDetail ?? ''}`);
387
+ const detailText = detail.statusDetail ? `: ${detail.statusDetail}` : '';
388
+ console.error(`${LOG} deployment ${deploymentId} ended ${detail.status} (${elapsed})${detailText}${progressSuffix}`);
311
389
  return 1;
312
390
  }
313
- await new Promise((r) => setTimeout(r, 2000));
391
+ const progressSuffix = deploymentProgressSuffix(detail);
392
+ const statusLabel = deploymentStatusLabel(detail);
393
+ if (statusLabel !== lastLoggedLabel) {
394
+ console.log(`${LOG} deployment ${deploymentId}: ${statusLabel} (${formatElapsed(elapsedMs)})${progressSuffix}`);
395
+ lastLoggedLabel = statusLabel;
396
+ lastLogAt = Date.now();
397
+ }
398
+ else if (Date.now() - lastLogAt >= HEARTBEAT_INTERVAL_MS) {
399
+ console.log(`${LOG} deployment ${deploymentId}: still ${statusLabel} (${formatElapsed(elapsedMs)})${progressSuffix}`);
400
+ lastLogAt = Date.now();
401
+ }
402
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
314
403
  }
315
- console.error(`${LOG} timed out waiting for deployment ${deploymentId}`);
404
+ console.error(`${LOG} timed out waiting for deployment ${deploymentId} (${formatElapsed(Date.now() - started)})`);
316
405
  return 1;
317
406
  }
407
+ export async function pipelineAdoptCommand(args) {
408
+ const stage = flagString(args.flags, 'stage');
409
+ const ref = flagString(args.flags, 'ref');
410
+ const nativeId = flagString(args.flags, 'native-id');
411
+ const approvedBy = flagString(args.flags, 'approved-by');
412
+ if (!stage || !ref || !nativeId || !approvedBy) {
413
+ console.error('usage: seq-studio pipeline adopt --stage <slug> --ref <sha|branch> -e <env> ' +
414
+ '--native-id <id> --approved-by <you> [--resource-key <key>] [--kind job|dlt_pipeline] ' +
415
+ '[--old-source-removal-pr <url>] [--repo pipelines/<slug>] [--json]');
416
+ return 1;
417
+ }
418
+ const { env, token } = await envAndToken(args);
419
+ const json = args.flags.json === true || args.flags.json === 'true';
420
+ let target;
421
+ try {
422
+ target = await resolvePipelineTarget({ args, env, token });
423
+ }
424
+ catch (error) {
425
+ console.error(`${LOG} adopt failed: ${error instanceof Error ? error.message : String(error)}`);
426
+ return 1;
427
+ }
428
+ const repo = flagString(args.flags, 'repo');
429
+ const resourceKey = flagString(args.flags, 'resource-key') ?? stage.replace(/-/g, '_');
430
+ const kind = flagString(args.flags, 'kind') ?? 'job';
431
+ if (kind !== 'job' && kind !== 'dlt_pipeline') {
432
+ console.error(`${LOG} --kind must be job or dlt_pipeline`);
433
+ return 1;
434
+ }
435
+ const oldSourceRemovalPr = flagString(args.flags, 'old-source-removal-pr');
436
+ const stageId = await resolveStageIdBySlug({
437
+ baseUrl: env.url,
438
+ token,
439
+ slug: stage,
440
+ repo,
441
+ });
442
+ try {
443
+ const response = await postJson({
444
+ baseUrl: env.url,
445
+ token,
446
+ path: `/api/data-pipelines/stages/${stageId}/adopt`,
447
+ body: {
448
+ environment: target.id,
449
+ ref,
450
+ bindings: [{ resourceKey, nativeId, kind }],
451
+ approvedBy,
452
+ ...(oldSourceRemovalPr ? { oldSourceRemovalPr } : {}),
453
+ },
454
+ });
455
+ if (json) {
456
+ console.log(JSON.stringify(response, null, 2));
457
+ }
458
+ else {
459
+ console.log(`${LOG} enqueued adopt for ${stage} → ${resourceKey}=${nativeId} ` +
460
+ `(trigger=${response.triggerRunId}). Worker binds on Trigger; ` +
461
+ `confirm with plan or stage_resources once the run completes.`);
462
+ }
463
+ return 0;
464
+ }
465
+ catch (error) {
466
+ if (error instanceof AtlasApiError) {
467
+ console.error(`${LOG} adopt failed: ${error.message}`);
468
+ return 1;
469
+ }
470
+ throw error;
471
+ }
472
+ }
473
+ export async function pipelineUnbindCommand(args) {
474
+ const stage = flagString(args.flags, 'stage');
475
+ const ref = flagString(args.flags, 'ref');
476
+ const approvedBy = flagString(args.flags, 'approved-by');
477
+ if (!stage || !ref || !approvedBy) {
478
+ console.error('usage: seq-studio pipeline unbind --stage <slug> --ref <sha|branch> -e <env> ' +
479
+ '--approved-by <you> [--resource-key <key>] [--repo pipelines/<slug>] [--json]');
480
+ return 1;
481
+ }
482
+ const { env, token } = await envAndToken(args);
483
+ const json = args.flags.json === true || args.flags.json === 'true';
484
+ let target;
485
+ try {
486
+ target = await resolvePipelineTarget({ args, env, token });
487
+ }
488
+ catch (error) {
489
+ console.error(`${LOG} unbind failed: ${error instanceof Error ? error.message : String(error)}`);
490
+ return 1;
491
+ }
492
+ const repo = flagString(args.flags, 'repo');
493
+ const resourceKey = flagString(args.flags, 'resource-key');
494
+ const stageId = await resolveStageIdBySlug({
495
+ baseUrl: env.url,
496
+ token,
497
+ slug: stage,
498
+ repo,
499
+ });
500
+ try {
501
+ const response = await postJson({
502
+ baseUrl: env.url,
503
+ token,
504
+ path: `/api/data-pipelines/stages/${stageId}/unbind`,
505
+ body: {
506
+ environment: target.id,
507
+ ref,
508
+ approvedBy,
509
+ ...(resourceKey ? { resourceKeys: [resourceKey] } : {}),
510
+ },
511
+ });
512
+ if (json) {
513
+ console.log(JSON.stringify(response, null, 2));
514
+ }
515
+ else {
516
+ console.log(`${LOG} enqueued unbind for ${stage}` +
517
+ (resourceKey ? ` (${resourceKey})` : '') +
518
+ ` (trigger=${response.triggerRunId}). Remote object is never deleted.`);
519
+ }
520
+ return 0;
521
+ }
522
+ catch (error) {
523
+ if (error instanceof AtlasApiError) {
524
+ console.error(`${LOG} unbind failed: ${error.message}`);
525
+ return 1;
526
+ }
527
+ throw error;
528
+ }
529
+ }
318
530
  /**
319
531
  * Stage identity is `(repo, slug)` — a bare slug can be ambiguous across
320
532
  * Pipelines. `--repo pipelines/<domain>` scopes the lookup server-side; an
@@ -116,6 +116,7 @@ const SERVING_BODY = `
116
116
  source:
117
117
  stage: my-gold-stage
118
118
  output: gold.my_table
119
+ # catalog: src_example # optional; omit to use the deploy catalog
119
120
  projection:
120
121
  # The served contract (inline columns or schema_ref):
121
122
  columns:
@@ -139,7 +140,8 @@ expose:
139
140
  # table: my_table
140
141
  # mode: referenced
141
142
  `;
142
- const ENTRYPOINT_STUB = `"""Entrypoint stub for the {{name}} stage.
143
+ const ENTRYPOINT_STUB = `# Databricks notebook source
144
+ """Entrypoint stub for the {{name}} stage.
143
145
 
144
146
  The platform invokes this per the stage spec ({{name}}.stage.yml).
145
147
  Decoders and transforms stay pure — bronze materialization, envelope
@@ -44,6 +44,58 @@ export declare function reposCloneCommand(args: ParsedArgs, deps?: {
44
44
  * so askpass never sends ATLAS_GIT_PAT to an attacker-controlled host.
45
45
  */
46
46
  export declare function normalizeCloneUrl(raw: string, env: ResolvedEnv): string;
47
+ type DiscoveredCliCheck = {
48
+ name: string;
49
+ script?: string;
50
+ command?: string[];
51
+ source: 'autodiscover' | 'ci.json';
52
+ };
53
+ /**
54
+ * Pure discovery shared by `repos ci show` / `import`. Mirrors atlas
55
+ * `discoverCiChecks` / `parseCiJson` so CLI preview matches execution.
56
+ */
57
+ export declare function discoverCliChecks({ ciJson, packageScripts, }: {
58
+ ciJson?: unknown;
59
+ packageScripts?: Record<string, string>;
60
+ }): {
61
+ ok: true;
62
+ checks: DiscoveredCliCheck[];
63
+ } | {
64
+ ok: false;
65
+ error: string;
66
+ };
67
+ /**
68
+ * Load `.seq/ci.json` / `package.json` for CI preview.
69
+ *
70
+ * Matches the runner: only a true 404 means "file absent". Invalid JSON and
71
+ * non-404 API errors fail closed (no silent package.json fallback over a bad
72
+ * `.seq/ci.json`).
73
+ */
74
+ export declare function loadRemoteCiSources({ ctx, repoId, ref, }: {
75
+ ctx: CommandContext;
76
+ repoId: string;
77
+ ref: string;
78
+ }): Promise<{
79
+ ok: true;
80
+ ciJson?: unknown;
81
+ packageScripts?: Record<string, string>;
82
+ } | {
83
+ ok: false;
84
+ error: string;
85
+ }>;
86
+ /**
87
+ * Discover CI checks from a remote repo tip (pure preview — no Settings write).
88
+ * Uses the same autodiscovery rules as the platform runner (PLA-378).
89
+ */
90
+ export declare function reposCiShowCommand(args: ParsedArgs): Promise<number>;
91
+ /** Add a check-run name to Settings `requiredChecks` (merge-blocking). */
92
+ export declare function reposCiRequireCommand(args: ParsedArgs): Promise<number>;
93
+ /**
94
+ * Set requiredChecks to every check discovered on the tip (opt-in bulk require).
95
+ */
96
+ export declare function reposCiImportCommand(args: ParsedArgs): Promise<number>;
97
+ export declare function reposCiCommand(args: ParsedArgs): Promise<number>;
47
98
  export declare function reposDeleteCommand(args: ParsedArgs): Promise<number>;
48
- export declare const REPOS_USAGE = "usage:\n seq-studio repos list -e <env> [--namespace <slug>] [--mine] repos visible on the environment\n seq-studio repos namespaces [create <slug>] -e <env> list or create namespaces\n seq-studio repos show <ns>/<name> -e <env> repo detail (branches, clone URL)\n seq-studio repos create <ns>/<name> -e <env> [--default-branch b] create an empty repo\n seq-studio repos clone <ns>/<name> -e <env> [--ref r] [--out dir] [--force]\n seq-studio repos clone --url <https://\u2026/repos/<id>/git> -e <env> [--ref r] [--out dir]\n seq-studio repos clone --id <uuid> -e <env> [--ref r] [--out dir]\n smart-HTTP when ATLAS_GIT_PAT is set;\n otherwise JSON materialize (<ns>/<name>)\n seq-studio repos pull <ns>/<name> -e <env> [--ref r] [--out dir] [--force]\n materialize the tree at a ref (JSON API)\n seq-studio repos delete <ns>/<name> -e <env> [--yes] delete a repo (confirm prompt)\n\n Flags: -e/--env <env> (required; see: seq-studio envs list)\n\n clone prefers real git clone (PAT via askpass \u2014 never written into the remote\n URL). Without ATLAS_GIT_PAT, <ns>/<name> falls back to the JSON API and prints\n how to get a PAT (seq-studio or Atlas UI /settings/tokens).\n PAT without Auth0 login: use --url from Repositories \u2192 Clone (or --id <uuid>).\n --ref accepts a branch, tag, or commit SHA (SHA \u2192 clone then checkout).\n\n Authenticate JSON API calls with: seq-studio login\n Authenticate git clone/push with: ATLAS_GIT_PAT (from Atlas Settings \u2192 Tokens)\n";
99
+ export declare const REPOS_USAGE = "usage:\n seq-studio repos list -e <env> [--namespace <slug>] [--mine] repos visible on the environment\n seq-studio repos namespaces [create <slug>] -e <env> list or create namespaces\n seq-studio repos show <ns>/<name> -e <env> repo detail (branches, clone URL)\n seq-studio repos create <ns>/<name> -e <env> [--default-branch b] create an empty repo\n seq-studio repos clone <ns>/<name> -e <env> [--ref r] [--out dir] [--force]\n seq-studio repos clone --url <https://\u2026/repos/<id>/git> -e <env> [--ref r] [--out dir]\n seq-studio repos clone --id <uuid> -e <env> [--ref r] [--out dir]\n smart-HTTP when ATLAS_GIT_PAT is set;\n otherwise JSON materialize (<ns>/<name>)\n seq-studio repos pull <ns>/<name> -e <env> [--ref r] [--out dir] [--force]\n materialize the tree at a ref (JSON API)\n seq-studio repos delete <ns>/<name> -e <env> [--yes] delete a repo (confirm prompt)\n seq-studio repos ci show <ns>/<name> -e <env> [--ref r] preview discovered CI checks\n seq-studio repos ci require <ns>/<name> --check <name> -e <env> reserved (refuses write until sandboxed runner)\n seq-studio repos ci import <ns>/<name> -e <env> [--ref r] reserved (refuses write until sandboxed runner)\n\n Flags: -e/--env <env> (required; see: seq-studio envs list)\n\n clone prefers real git clone (PAT via askpass \u2014 never written into the remote\n URL). Without ATLAS_GIT_PAT, <ns>/<name> falls back to the JSON API and prints\n how to get a PAT (seq-studio or Atlas UI /settings/tokens).\n PAT without Auth0 login: use --url from Repositories \u2192 Clone (or --id <uuid>).\n --ref accepts a branch, tag, or commit SHA (SHA \u2192 clone then checkout).\n\n Authenticate JSON API calls with: seq-studio login\n Authenticate git clone/push with: ATLAS_GIT_PAT (from Atlas Settings \u2192 Tokens)\n";
49
100
  export declare function runReposCommand(sub: string | undefined, args: ParsedArgs): Promise<number>;
101
+ export {};