@sequenceholdings/artifact-studio 0.1.9 → 0.1.11
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/dist/auth.js +1 -1
- package/dist/cli.js +11 -9
- package/dist/config.d.ts +7 -0
- package/dist/config.js +45 -6
- package/dist/git-clone.js +10 -6
- package/dist/prepare-build.d.ts +9 -0
- package/dist/prepare-build.js +32 -7
- package/dist/source-resolver.js +2 -1
- package/dist/templates/react-vite/artifact.bundle.yml +4 -2
- package/package.json +3 -3
- package/templates/react-vite/artifact.bundle.yml +4 -2
package/dist/auth.js
CHANGED
|
@@ -87,7 +87,7 @@ export async function getAccessToken() {
|
|
|
87
87
|
}),
|
|
88
88
|
});
|
|
89
89
|
if (!response.ok) {
|
|
90
|
-
throw new Error('Stored refresh token is no longer valid. Run
|
|
90
|
+
throw new Error('Stored refresh token is no longer valid. Run seq-studio login again.');
|
|
91
91
|
}
|
|
92
92
|
const refreshed = await response.json();
|
|
93
93
|
await writeTokenConfig({
|
package/dist/cli.js
CHANGED
|
@@ -3,7 +3,7 @@ import { existsSync } from 'node:fs';
|
|
|
3
3
|
import { dirname, join, relative, resolve } from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { getJson, getJsonOr404, postJson } from './api.js';
|
|
6
|
-
import { readLocalConfig, resolveEnvironment, writeLocalConfig, writeTokenConfig, } from './config.js';
|
|
6
|
+
import { ENV_URLS, readLocalConfig, resolveEnvironment, writeLocalConfig, writeTokenConfig, } from './config.js';
|
|
7
7
|
import { buildArtifactStudioProject, writeBuildArtifact } from './build.js';
|
|
8
8
|
import { buildArtifactStudioProjectIsolated } from './build-subprocess.js';
|
|
9
9
|
import { prepareArtifactBuildRoot } from './prepare-build.js';
|
|
@@ -45,10 +45,10 @@ const USAGE = `usage:
|
|
|
45
45
|
--repo clones over smart-HTTP and REQUIRES a git PAT in ATLAS_GIT_PAT
|
|
46
46
|
(repo:read scope) — create one with \`seq-studio auth pat create --scopes
|
|
47
47
|
repo:read\` or in Atlas → Settings → Tokens. It's the same token you clone the
|
|
48
|
-
repo with. (--env +
|
|
48
|
+
repo with. (--env + seq-studio login are still needed to resolve the repo and
|
|
49
49
|
upload the deployment.)
|
|
50
50
|
|
|
51
|
-
Authenticate with:
|
|
51
|
+
Authenticate with: seq-studio login`;
|
|
52
52
|
export async function runCli(argv = process.argv.slice(2)) {
|
|
53
53
|
const parsed = parseArgs(argv);
|
|
54
54
|
if ('error' in parsed) {
|
|
@@ -116,7 +116,7 @@ async function initCommand(args) {
|
|
|
116
116
|
await replaceInFile(join(dir, 'artifact.bundle.yml'), /\{\{title\}\}/g, titleize(slug));
|
|
117
117
|
await replaceInFile(join(dir, 'package.json'), /\{\{slug\}\}/g, slug);
|
|
118
118
|
console.log(`[seq-studio] initialized ${dir}`);
|
|
119
|
-
console.log('Next:
|
|
119
|
+
console.log('Next: seq-studio login && seq-studio artifact dev --env <env> (see: seq-studio envs list)');
|
|
120
120
|
return 0;
|
|
121
121
|
}
|
|
122
122
|
async function loginCommand(args) {
|
|
@@ -204,7 +204,9 @@ async function linkCommand(args) {
|
|
|
204
204
|
async function envCommand(args) {
|
|
205
205
|
const [subcommand, value] = args.positional;
|
|
206
206
|
if (subcommand === 'list') {
|
|
207
|
-
|
|
207
|
+
for (const name of Object.keys(ENV_URLS))
|
|
208
|
+
console.log(name);
|
|
209
|
+
console.log('(deployed environments are discovered per-identity — run: seq-studio envs list)');
|
|
208
210
|
return 0;
|
|
209
211
|
}
|
|
210
212
|
if (subcommand === 'use') {
|
|
@@ -229,7 +231,7 @@ async function statusCommand(args) {
|
|
|
229
231
|
if (env.name === 'local') {
|
|
230
232
|
await fetch(`${env.url}/api/health`)
|
|
231
233
|
.then((response) => console.log(`local Atlas: ${response.ok ? 'ready' : `HTTP ${response.status}`}`))
|
|
232
|
-
.catch(() => console.log('local Atlas: not reachable (start Atlas on port 5001 or pass --env
|
|
234
|
+
.catch(() => console.log('local Atlas: not reachable (start Atlas on port 5001 or pass --env <env>)'));
|
|
233
235
|
}
|
|
234
236
|
return 0;
|
|
235
237
|
}
|
|
@@ -382,7 +384,7 @@ async function planCommand(args) {
|
|
|
382
384
|
console.log(` source: ${result.sourceHash}`);
|
|
383
385
|
console.log(` bundle: ${result.bundleHash}`);
|
|
384
386
|
if (!token) {
|
|
385
|
-
console.log(' remote: skipped (run
|
|
387
|
+
console.log(' remote: skipped (run seq-studio login)');
|
|
386
388
|
return 0;
|
|
387
389
|
}
|
|
388
390
|
const remote = await fetchRemoteProject({ env: env.url, token, slug: result.manifest.artifact.project_id });
|
|
@@ -709,7 +711,7 @@ async function ensureRemoteProject({ env, token, result, linkConfigDir, allowCre
|
|
|
709
711
|
if (!allowCreate) {
|
|
710
712
|
throw new Error(`project "${result.manifest.artifact.project_id}" does not exist on ${env} and --no-create is set. ` +
|
|
711
713
|
`First-time provisioning is a human step: run \`seq-studio artifact deploy . -e <env>\` once with your ` +
|
|
712
|
-
`own login (
|
|
714
|
+
`own login (\`seq-studio login\`) to create the project, then the CI sweep keeps it updated.`);
|
|
713
715
|
}
|
|
714
716
|
const created = await postJson({
|
|
715
717
|
baseUrl: env,
|
|
@@ -787,7 +789,7 @@ export function setTokenProvider(provider) {
|
|
|
787
789
|
async function getRequiredToken(args) {
|
|
788
790
|
const token = await getOptionalToken(args);
|
|
789
791
|
if (!token)
|
|
790
|
-
throw new Error('Missing token. Run
|
|
792
|
+
throw new Error('Missing token. Run seq-studio login or pass --token.');
|
|
791
793
|
return token;
|
|
792
794
|
}
|
|
793
795
|
async function getOptionalToken(args) {
|
package/dist/config.d.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Only `local` ships in the published package. Deployed-environment URLs
|
|
3
|
+
* are resolved by the embedding CLI (seq-studio), which discovers the
|
|
4
|
+
* environments visible to the caller's identity and injects the resolved
|
|
5
|
+
* URL via ARTIFACT_STUDIO_BASE_URL (see resolveEnvironment below).
|
|
6
|
+
*/
|
|
1
7
|
export declare const ENV_URLS: Record<string, string>;
|
|
2
8
|
export interface LocalProjectConfig {
|
|
3
9
|
projectId?: string;
|
|
@@ -18,6 +24,7 @@ export declare function readLocalConfig(cwd?: string): Promise<LocalProjectConfi
|
|
|
18
24
|
export declare function writeLocalConfig(config: LocalProjectConfig, cwd?: string): Promise<void>;
|
|
19
25
|
export declare function readTokenConfig(): Promise<TokenConfig>;
|
|
20
26
|
export declare function writeTokenConfig(config: TokenConfig): Promise<void>;
|
|
27
|
+
export declare function knownEnvUrls(): Record<string, string>;
|
|
21
28
|
export declare function resolveEnvironment(name: string | undefined, fallback?: string): {
|
|
22
29
|
name: string;
|
|
23
30
|
url: string;
|
package/dist/config.js
CHANGED
|
@@ -2,11 +2,14 @@ import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
3
|
import { dirname, join, resolve } from 'node:path';
|
|
4
4
|
import { homedir } from 'node:os';
|
|
5
|
+
/**
|
|
6
|
+
* Only `local` ships in the published package. Deployed-environment URLs
|
|
7
|
+
* are resolved by the embedding CLI (seq-studio), which discovers the
|
|
8
|
+
* environments visible to the caller's identity and injects the resolved
|
|
9
|
+
* URL via ARTIFACT_STUDIO_BASE_URL (see resolveEnvironment below).
|
|
10
|
+
*/
|
|
5
11
|
export const ENV_URLS = {
|
|
6
12
|
local: 'http://localhost:5001',
|
|
7
|
-
staging: 'https://staging.atlas.seqholdings.com',
|
|
8
|
-
production: 'https://atlas.seqholdings.com',
|
|
9
|
-
banksouth: 'https://banksouth.seqholdings.com',
|
|
10
13
|
};
|
|
11
14
|
export function globalConfigDir() {
|
|
12
15
|
return join(homedir(), '.config', 'sequence-artifact-studio');
|
|
@@ -42,6 +45,35 @@ export async function writeTokenConfig(config) {
|
|
|
42
45
|
await mkdir(dirname(path), { recursive: true });
|
|
43
46
|
await writeFile(path, JSON.stringify(config, null, 2) + '\n', { encoding: 'utf8', mode: 0o600 });
|
|
44
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Environment map injected by the embedding CLI (seq-studio sets
|
|
50
|
+
* ARTIFACT_STUDIO_ENV_URLS to the JSON name→url map of environments visible
|
|
51
|
+
* to the caller's identity). Lets commands that resolve an env *name* without
|
|
52
|
+
* an explicit --env flag — the stored `defaultEnv`, `artifact env set` —
|
|
53
|
+
* reach deployed environments, not just the built-in `local`.
|
|
54
|
+
*/
|
|
55
|
+
function embedderEnvUrls() {
|
|
56
|
+
const raw = process.env['ARTIFACT_STUDIO_ENV_URLS']?.trim();
|
|
57
|
+
if (!raw)
|
|
58
|
+
return {};
|
|
59
|
+
try {
|
|
60
|
+
const parsed = JSON.parse(raw);
|
|
61
|
+
if (typeof parsed !== 'object' || parsed === null)
|
|
62
|
+
return {};
|
|
63
|
+
const out = {};
|
|
64
|
+
for (const [name, url] of Object.entries(parsed)) {
|
|
65
|
+
if (typeof url === 'string' && url)
|
|
66
|
+
out[name] = url;
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return {};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
export function knownEnvUrls() {
|
|
75
|
+
return { ...ENV_URLS, ...embedderEnvUrls() };
|
|
76
|
+
}
|
|
45
77
|
export function resolveEnvironment(name, fallback) {
|
|
46
78
|
const envName = name ?? fallback;
|
|
47
79
|
// Allow `seq-studio artifact` (and any other embedder that wants to
|
|
@@ -52,11 +84,18 @@ export function resolveEnvironment(name, fallback) {
|
|
|
52
84
|
const overrideUrl = process.env['ARTIFACT_STUDIO_BASE_URL']?.trim() || undefined;
|
|
53
85
|
if (overrideUrl) {
|
|
54
86
|
if (!envName)
|
|
55
|
-
throw new Error(
|
|
87
|
+
throw new Error(unknownEnvMessage(envName));
|
|
56
88
|
return { name: envName, url: overrideUrl };
|
|
57
89
|
}
|
|
58
|
-
const
|
|
90
|
+
const envUrls = knownEnvUrls();
|
|
91
|
+
const url = envName ? envUrls[envName] : undefined;
|
|
59
92
|
if (!envName || !url)
|
|
60
|
-
throw new Error(
|
|
93
|
+
throw new Error(unknownEnvMessage(envName));
|
|
61
94
|
return { name: envName, url };
|
|
62
95
|
}
|
|
96
|
+
function unknownEnvMessage(envName) {
|
|
97
|
+
const known = Object.keys(knownEnvUrls()).join(', ');
|
|
98
|
+
return (`--env ${envName ? `"${envName}" is not available here` : 'is required'} ` +
|
|
99
|
+
`(known: ${known}). Deployed environments are resolved through ` +
|
|
100
|
+
`seq-studio (run: seq-studio envs list), or set ARTIFACT_STUDIO_BASE_URL.`);
|
|
101
|
+
}
|
package/dist/git-clone.js
CHANGED
|
@@ -42,14 +42,18 @@ export function redactCloneUrl(url) {
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
/**
|
|
45
|
-
* Abort
|
|
46
|
-
*
|
|
47
|
-
*
|
|
45
|
+
* Abort a smart-HTTP transfer that drops below ~1 KB/s for a sustained window,
|
|
46
|
+
* so a wedged fetch fails cleanly instead of hanging a deploy/CI. The window
|
|
47
|
+
* must clear the git-service's *server-side pack generation* pause: upload-pack
|
|
48
|
+
* enumerates + compresses objects before streaming a single byte, and for a
|
|
49
|
+
* real repo that silent gap is tens of seconds (observed ~55s for a 145-file
|
|
50
|
+
* artifact). A 30s threshold false-killed those legitimate clones, so allow a
|
|
51
|
+
* generous 180s of silence — the hard timeout below is the real backstop.
|
|
48
52
|
*/
|
|
49
53
|
const GIT_LOW_SPEED_LIMIT_BYTES = 1000;
|
|
50
|
-
const GIT_LOW_SPEED_TIME_SEC =
|
|
51
|
-
/** Backstop hard deadline:
|
|
52
|
-
const GIT_CLONE_TIMEOUT_MS =
|
|
54
|
+
const GIT_LOW_SPEED_TIME_SEC = 180;
|
|
55
|
+
/** Backstop hard deadline: SIGKILL the child even if it trickles above the low-speed floor. */
|
|
56
|
+
const GIT_CLONE_TIMEOUT_MS = 6 * 60_000;
|
|
53
57
|
/** First non-flag arg (the git subcommand), skipping `-c <value>` config pairs. */
|
|
54
58
|
function gitVerb(args) {
|
|
55
59
|
for (let i = 0; i < args.length; i++) {
|
package/dist/prepare-build.d.ts
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pinned pnpm for remote artifact installs, run via `npx`. Kept in lockstep
|
|
3
|
+
* with the repo's root `packageManager`. Do not float to latest: pnpm 11+ adds
|
|
4
|
+
* an UNCONDITIONAL tarball-URL verification policy (no opt-out) that rejects
|
|
5
|
+
* Chainguard's upstream-proxied tarballs at install time
|
|
6
|
+
* (ERR_PNPM_TARBALL_URL_MISMATCH). We run it via npx — which ships with npm on
|
|
7
|
+
* every Node release — rather than corepack, which Node 25+ no longer bundles.
|
|
8
|
+
*/
|
|
9
|
+
export declare const ARTIFACT_BUILD_PNPM = "pnpm@10.28.2";
|
|
1
10
|
/** Chainguard registry routing for ephemeral remote builds. */
|
|
2
11
|
export declare const ARTIFACT_BUILD_NPMRC = "; Ephemeral install config for seq-studio artifact builds.\nregistry=https://libraries.cgr.dev/javascript/\n//libraries.cgr.dev/javascript/:always-auth=true\n//libraries.cgr.dev/javascript-upstream/:always-auth=true\nignore-scripts=true\nmanage-package-manager-versions=false\n";
|
|
3
12
|
/** Pull only Chainguard registry auth lines from the developer ~/.npmrc. */
|
package/dist/prepare-build.js
CHANGED
|
@@ -5,6 +5,15 @@ import { homedir, tmpdir } from 'node:os';
|
|
|
5
5
|
import { join } from 'node:path';
|
|
6
6
|
import { stripInstallControlFiles } from './sanitize-remote-tree.js';
|
|
7
7
|
import { assertTrustedArtifactInstallTree } from './trusted-install.js';
|
|
8
|
+
/**
|
|
9
|
+
* Pinned pnpm for remote artifact installs, run via `npx`. Kept in lockstep
|
|
10
|
+
* with the repo's root `packageManager`. Do not float to latest: pnpm 11+ adds
|
|
11
|
+
* an UNCONDITIONAL tarball-URL verification policy (no opt-out) that rejects
|
|
12
|
+
* Chainguard's upstream-proxied tarballs at install time
|
|
13
|
+
* (ERR_PNPM_TARBALL_URL_MISMATCH). We run it via npx — which ships with npm on
|
|
14
|
+
* every Node release — rather than corepack, which Node 25+ no longer bundles.
|
|
15
|
+
*/
|
|
16
|
+
export const ARTIFACT_BUILD_PNPM = 'pnpm@10.28.2';
|
|
8
17
|
/** Chainguard registry routing for ephemeral remote builds. */
|
|
9
18
|
export const ARTIFACT_BUILD_NPMRC = `; Ephemeral install config for seq-studio artifact builds.
|
|
10
19
|
registry=https://libraries.cgr.dev/javascript/
|
|
@@ -65,28 +74,44 @@ export async function prepareArtifactBuildRoot(dir, opts = {}) {
|
|
|
65
74
|
console.warn(`[seq-studio] stripped install-control files from remote tree (not committed): ${stripped.join(', ')}`);
|
|
66
75
|
}
|
|
67
76
|
assertTrustedArtifactInstallTree(dir);
|
|
77
|
+
// Defense-in-depth: never trust a node_modules committed into the checkout.
|
|
78
|
+
// The trust walk skips node_modules, so a crafted tree could smuggle a
|
|
79
|
+
// malicious `node_modules/pnpm` bin. We both (a) remove any pre-existing
|
|
80
|
+
// node_modules and (b) run npx from a trusted cwd below so npx can't resolve
|
|
81
|
+
// the pinned CLI out of the untrusted tree.
|
|
82
|
+
await rm(join(dir, 'node_modules'), { recursive: true, force: true });
|
|
68
83
|
const configDir = await mkdtemp(join(tmpdir(), 'artifact-pnpm-'));
|
|
69
84
|
const controlledNpmrc = join(configDir, '.npmrc');
|
|
70
85
|
const args = ['install', '--frozen-lockfile', '--ignore-scripts', '--prod'];
|
|
71
86
|
const userNpmrcPath = process.env.npm_config_userconfig ?? join(homedir(), '.npmrc');
|
|
72
|
-
console.log(
|
|
87
|
+
console.log(`[seq-studio] installing dependencies (frozen lockfile, prod only) via ${ARTIFACT_BUILD_PNPM}…`);
|
|
73
88
|
try {
|
|
74
89
|
await writeFile(controlledNpmrc, buildControlledArtifactNpmrc(userNpmrcPath), { mode: 0o600 });
|
|
75
|
-
|
|
76
|
-
|
|
90
|
+
// Run a PINNED pnpm via `npx`, not the operator's ambient `pnpm`. The
|
|
91
|
+
// install must be reproducible across laptops and CI: in a bare temp clone
|
|
92
|
+
// dir the ambient pnpm resolves to latest (11.x), whose unconditional
|
|
93
|
+
// tarball-URL policy rejects Chainguard's upstream-proxied tarballs with
|
|
94
|
+
// ERR_PNPM_TARBALL_URL_MISMATCH. `npx` ships with npm on every Node release
|
|
95
|
+
// (including Node 25+, which dropped corepack), fetches the pinned pnpm, and
|
|
96
|
+
// caches it. `--yes` skips the install-confirmation prompt.
|
|
97
|
+
//
|
|
98
|
+
// SECURITY: run npx from the trusted `configDir` (not `dir`) so it can't
|
|
99
|
+
// resolve/execute a pnpm bin planted in the checkout's node_modules; pnpm
|
|
100
|
+
// still operates on the checkout via `--dir`.
|
|
101
|
+
execFileSync('npx', ['--yes', ARTIFACT_BUILD_PNPM, '--dir', dir, ...args], {
|
|
102
|
+
cwd: configDir,
|
|
77
103
|
stdio: 'inherit',
|
|
78
104
|
env: {
|
|
79
105
|
...process.env,
|
|
80
106
|
npm_config_userconfig: controlledNpmrc,
|
|
81
|
-
COREPACK_ENABLE_AUTO_PIN: '0',
|
|
82
|
-
COREPACK_ENABLE_DOWNLOAD_PROMPT: '0',
|
|
83
107
|
},
|
|
84
108
|
});
|
|
85
109
|
}
|
|
86
110
|
catch (error) {
|
|
87
|
-
const hint = 'Lockfile out of sync
|
|
111
|
+
const hint = 'Lockfile out of sync, registry auth missing, or npx unavailable — ' +
|
|
112
|
+
'run `pnpm install` locally and commit pnpm-lock.yaml, and ensure Node ships npm/npx.';
|
|
88
113
|
const detail = error instanceof Error ? error.message : String(error);
|
|
89
|
-
throw new Error(`
|
|
114
|
+
throw new Error(`artifact dependency install failed (${ARTIFACT_BUILD_PNPM}). ${hint} (${detail})`);
|
|
90
115
|
}
|
|
91
116
|
finally {
|
|
92
117
|
await rm(configDir, { recursive: true, force: true });
|
package/dist/source-resolver.js
CHANGED
|
@@ -82,7 +82,8 @@ export async function resolveArtifactSource(spec, opts = {}) {
|
|
|
82
82
|
try {
|
|
83
83
|
if (spec.kind === 'git-service') {
|
|
84
84
|
if (!opts.baseUrl || !opts.token) {
|
|
85
|
-
throw new Error('Deploying from --repo needs a target environment and auth.
|
|
85
|
+
throw new Error('Deploying from --repo needs a target environment and auth. ' +
|
|
86
|
+
'Pass --env and run `seq-studio login`.');
|
|
86
87
|
}
|
|
87
88
|
// resolveRepo + resolveCommitSha use the Auth0/M2M token: the clone URL
|
|
88
89
|
// is id-addressed (no by-path endpoint), and we pin the build to the
|
|
@@ -24,7 +24,9 @@ capabilities:
|
|
|
24
24
|
functions:
|
|
25
25
|
invoke: []
|
|
26
26
|
|
|
27
|
+
# Point each target at an Atlas deployment you can access.
|
|
28
|
+
# Run `seq-studio envs list` to see the environments visible to your identity.
|
|
27
29
|
targets:
|
|
28
|
-
|
|
29
|
-
url:
|
|
30
|
+
local:
|
|
31
|
+
url: http://localhost:5001
|
|
30
32
|
visibility: private
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"publishConfig": {
|
|
10
10
|
"access": "public"
|
|
11
11
|
},
|
|
12
|
-
"version": "0.1.
|
|
12
|
+
"version": "0.1.11",
|
|
13
13
|
"description": "SDK types and CLI library for building Artifact Studio apps. Driven via `seq-studio artifact <sub>` (@sequenceholdings/studio-cli), which runs the `./cli` runCli export.",
|
|
14
14
|
"type": "module",
|
|
15
15
|
"exports": {
|
|
@@ -51,8 +51,8 @@
|
|
|
51
51
|
"tw-animate-css": "^1.4.0",
|
|
52
52
|
"vite": "8.0.16",
|
|
53
53
|
"zod": "^4.1.13",
|
|
54
|
-
"@sequenceholdings/
|
|
55
|
-
"@sequenceholdings/
|
|
54
|
+
"@sequenceholdings/lattice-form-renderer": "0.1.0",
|
|
55
|
+
"@sequenceholdings/atlas-ui": "0.1.3"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@types/js-yaml": "^4.0.9",
|
|
@@ -24,7 +24,9 @@ capabilities:
|
|
|
24
24
|
functions:
|
|
25
25
|
invoke: []
|
|
26
26
|
|
|
27
|
+
# Point each target at an Atlas deployment you can access.
|
|
28
|
+
# Run `seq-studio envs list` to see the environments visible to your identity.
|
|
27
29
|
targets:
|
|
28
|
-
|
|
29
|
-
url:
|
|
30
|
+
local:
|
|
31
|
+
url: http://localhost:5001
|
|
30
32
|
visibility: private
|