@whittlelabs/sifter 0.29.0 → 0.30.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.
- package/README.md +25 -5
- package/bin.js +210 -13
- package/bin.js.map +4 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,15 +21,35 @@ Visit Sift's **Settings → Pair a Sifter**. Sift will display a pairing code an
|
|
|
21
21
|
whittle-sifter init --pairing-code=ABCD-1234
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
The Sifter defaults to production
|
|
24
|
+
The Sifter defaults to production. To pair against another Whittle Labs environment, name it with `--profile`: the name selects the backend as well as the directory the pairing lands in.
|
|
25
25
|
|
|
26
26
|
```bash
|
|
27
|
-
whittle-sifter init
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
whittle-sifter init --profile stage # keep.stage / jobs.stage
|
|
28
|
+
whittle-sifter init --profile test # keep.test / jobs.test
|
|
29
|
+
whittle-sifter init --profile studio # a dev stack on the Studio
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
The known names are `prod`, `stage`, `test` and `studio`. For any other backend, keep the profile as a label and pass the URLs yourself; a flag always beats the environment it would otherwise have resolved:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
whittle-sifter init --profile my-box \
|
|
36
|
+
--keep-url=http://box.local:3007 \
|
|
37
|
+
--jobs-url=http://box.local:3005
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Whenever a profile or a URL flag is in play, `init` prints what it resolved before it pairs, so a host that has moved is something you see rather than something you debug:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
Pairing environment
|
|
44
|
+
Profile: stage
|
|
45
|
+
Environment: stage (named by this build)
|
|
46
|
+
Keep: https://keep.stage.whittlelabs.com from the "stage" environment
|
|
47
|
+
Jobs: https://jobs.stage.whittlelabs.com from the "stage" environment
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Once the backend answers, one more line names the page it sent you to, which is its decision and not the CLI's. Add `--json` to get those two facts as JSON objects, one per line.
|
|
51
|
+
|
|
52
|
+
Pairing writes credentials to `~/.sifter/<profile>/config.json` (mode 0600), or `~/.sifter/config.json` for the default profile.
|
|
33
53
|
|
|
34
54
|
## Run
|
|
35
55
|
|
package/bin.js
CHANGED
|
@@ -11604,6 +11604,120 @@ var require_version_check = __commonJS({
|
|
|
11604
11604
|
}
|
|
11605
11605
|
});
|
|
11606
11606
|
|
|
11607
|
+
// ../../packages/shuttle/dist/branding/environment.js
|
|
11608
|
+
var require_environment = __commonJS({
|
|
11609
|
+
"../../packages/shuttle/dist/branding/environment.js"(exports2) {
|
|
11610
|
+
"use strict";
|
|
11611
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
11612
|
+
exports2.resolveEnvironment = resolveEnvironment;
|
|
11613
|
+
exports2.applyEnvironment = applyEnvironment;
|
|
11614
|
+
exports2.isWorthReporting = isWorthReporting;
|
|
11615
|
+
exports2.renderEnvironmentReport = renderEnvironmentReport;
|
|
11616
|
+
exports2.renderVerificationLine = renderVerificationLine;
|
|
11617
|
+
exports2.environmentReportJson = environmentReportJson;
|
|
11618
|
+
exports2.verificationJson = verificationJson;
|
|
11619
|
+
exports2.hostOf = hostOf;
|
|
11620
|
+
function resolveEnvironment(brand, profile, overrides = {}) {
|
|
11621
|
+
const table = brand.environments ?? {};
|
|
11622
|
+
const known = Object.keys(table).sort();
|
|
11623
|
+
const matched = Object.prototype.hasOwnProperty.call(table, profile) ? profile : null;
|
|
11624
|
+
const entry = matched === null ? void 0 : table[matched];
|
|
11625
|
+
return {
|
|
11626
|
+
profile,
|
|
11627
|
+
environment: matched,
|
|
11628
|
+
known,
|
|
11629
|
+
keep: pick(overrides.keepUrl, entry?.keepApiUrl, brand.keepRegistration.keepApiUrl),
|
|
11630
|
+
jobs: pick(overrides.jobsUrl, entry?.jobsApiUrl, brand.keepRegistration.jobsApiUrl)
|
|
11631
|
+
};
|
|
11632
|
+
}
|
|
11633
|
+
function pick(flag, fromEnvironment, brandDefault) {
|
|
11634
|
+
if (flag)
|
|
11635
|
+
return { url: flag, source: "flag" };
|
|
11636
|
+
if (fromEnvironment)
|
|
11637
|
+
return { url: fromEnvironment, source: "environment" };
|
|
11638
|
+
return { url: brandDefault, source: "brand-default" };
|
|
11639
|
+
}
|
|
11640
|
+
function applyEnvironment(brand, resolved) {
|
|
11641
|
+
const reg = brand.keepRegistration;
|
|
11642
|
+
if (resolved.keep.url === reg.keepApiUrl && resolved.jobs.url === reg.jobsApiUrl) {
|
|
11643
|
+
return brand;
|
|
11644
|
+
}
|
|
11645
|
+
return {
|
|
11646
|
+
...brand,
|
|
11647
|
+
keepRegistration: {
|
|
11648
|
+
...reg,
|
|
11649
|
+
keepApiUrl: resolved.keep.url,
|
|
11650
|
+
jobsApiUrl: resolved.jobs.url
|
|
11651
|
+
}
|
|
11652
|
+
};
|
|
11653
|
+
}
|
|
11654
|
+
function isWorthReporting(resolved) {
|
|
11655
|
+
return resolved.environment !== null || resolved.keep.source === "flag" || resolved.jobs.source === "flag";
|
|
11656
|
+
}
|
|
11657
|
+
function renderEnvironmentReport(resolved) {
|
|
11658
|
+
return [
|
|
11659
|
+
"",
|
|
11660
|
+
"Pairing environment",
|
|
11661
|
+
field("Profile", resolved.profile),
|
|
11662
|
+
field("Environment", describeEnvironment(resolved)),
|
|
11663
|
+
field("Keep", `${resolved.keep.url} ${describeSource(resolved, resolved.keep)}`),
|
|
11664
|
+
field("Jobs", `${resolved.jobs.url} ${describeSource(resolved, resolved.jobs)}`),
|
|
11665
|
+
""
|
|
11666
|
+
];
|
|
11667
|
+
}
|
|
11668
|
+
function renderVerificationLine(verificationUri) {
|
|
11669
|
+
return field("Verify at", `${hostOf(verificationUri)} as Keep reported it`);
|
|
11670
|
+
}
|
|
11671
|
+
function environmentReportJson(resolved) {
|
|
11672
|
+
return JSON.stringify({
|
|
11673
|
+
kind: "pairing-environment",
|
|
11674
|
+
profile: resolved.profile,
|
|
11675
|
+
environment: resolved.environment,
|
|
11676
|
+
known: resolved.known,
|
|
11677
|
+
keep: resolved.keep,
|
|
11678
|
+
jobs: resolved.jobs
|
|
11679
|
+
});
|
|
11680
|
+
}
|
|
11681
|
+
function verificationJson(verificationUri) {
|
|
11682
|
+
return JSON.stringify({
|
|
11683
|
+
kind: "pairing-verification",
|
|
11684
|
+
verificationUri,
|
|
11685
|
+
host: hostOf(verificationUri),
|
|
11686
|
+
source: "keep"
|
|
11687
|
+
});
|
|
11688
|
+
}
|
|
11689
|
+
function field(label, value) {
|
|
11690
|
+
return ` ${`${label}:`.padEnd(13)}${value}`;
|
|
11691
|
+
}
|
|
11692
|
+
function describeEnvironment(resolved) {
|
|
11693
|
+
if (resolved.environment !== null) {
|
|
11694
|
+
return `${resolved.environment} (named by this build)`;
|
|
11695
|
+
}
|
|
11696
|
+
if (resolved.known.length === 0) {
|
|
11697
|
+
return "none (this build declares no environments)";
|
|
11698
|
+
}
|
|
11699
|
+
return `none (this build knows ${resolved.known.join(", ")})`;
|
|
11700
|
+
}
|
|
11701
|
+
function describeSource(resolved, url) {
|
|
11702
|
+
switch (url.source) {
|
|
11703
|
+
case "flag":
|
|
11704
|
+
return "from the flag you passed";
|
|
11705
|
+
case "environment":
|
|
11706
|
+
return `from the "${resolved.environment ?? ""}" environment`;
|
|
11707
|
+
default:
|
|
11708
|
+
return "from the brand default";
|
|
11709
|
+
}
|
|
11710
|
+
}
|
|
11711
|
+
function hostOf(url) {
|
|
11712
|
+
try {
|
|
11713
|
+
return new URL(url).host;
|
|
11714
|
+
} catch {
|
|
11715
|
+
return url;
|
|
11716
|
+
}
|
|
11717
|
+
}
|
|
11718
|
+
}
|
|
11719
|
+
});
|
|
11720
|
+
|
|
11607
11721
|
// ../../packages/shuttle/dist/preflight/checks.js
|
|
11608
11722
|
var require_checks = __commonJS({
|
|
11609
11723
|
"../../packages/shuttle/dist/preflight/checks.js"(exports2) {
|
|
@@ -11619,6 +11733,7 @@ var require_checks = __commonJS({
|
|
|
11619
11733
|
var loom_1 = require_dist();
|
|
11620
11734
|
var version_check_1 = require_version_check();
|
|
11621
11735
|
var apply_1 = require_apply();
|
|
11736
|
+
var environment_1 = require_environment();
|
|
11622
11737
|
var NODE_FLOOR = "20.0.0";
|
|
11623
11738
|
var DISK_FLOOR_BYTES = 1024 * 1024 * 1024;
|
|
11624
11739
|
var WORKSPACE_EXECUTOR = "claude-code";
|
|
@@ -11745,7 +11860,7 @@ var require_checks = __commonJS({
|
|
|
11745
11860
|
if (strays.length > 0) {
|
|
11746
11861
|
return fail("pairing-environment", `the config claims from ${strays.join(", ")}, which this pairing is not rostered into`, `run \`${subject.brand.product.cliBinary} init\` to rewrite the config from this pairing`);
|
|
11747
11862
|
}
|
|
11748
|
-
return pass("pairing-environment", `
|
|
11863
|
+
return pass("pairing-environment", `paired with Keep at ${(0, environment_1.hostOf)(pairing.keepApiUrl)}, claiming from Jobs at ${(0, environment_1.hostOf)(pairing.jobsApiUrl)}`);
|
|
11749
11864
|
});
|
|
11750
11865
|
}
|
|
11751
11866
|
};
|
|
@@ -22369,6 +22484,7 @@ var require_device_code = __commonJS({
|
|
|
22369
22484
|
throw err;
|
|
22370
22485
|
throw new Error(webFirstRemedy(opts.brand, err));
|
|
22371
22486
|
}
|
|
22487
|
+
opts.onVerificationUri?.(device.verificationUri);
|
|
22372
22488
|
const invitation = (0, terminal_1.renderPairingInvitation)({
|
|
22373
22489
|
header: opts.brand.strings?.pairingHeader ?? `Pair your ${opts.brand.product.title}`,
|
|
22374
22490
|
verificationUri: device.verificationUri,
|
|
@@ -22712,24 +22828,31 @@ var require_init = __commonJS({
|
|
|
22712
22828
|
var fsp = __importStar(require("fs/promises"));
|
|
22713
22829
|
var commander_1 = require_commander();
|
|
22714
22830
|
var apply_1 = require_apply();
|
|
22831
|
+
var environment_1 = require_environment();
|
|
22715
22832
|
var device_code_1 = require_device_code();
|
|
22716
22833
|
var store_1 = require_store();
|
|
22717
22834
|
var spend_store_1 = require_spend_store();
|
|
22718
22835
|
var typed_exit_1 = require_typed_exit();
|
|
22719
22836
|
var preflight_1 = require_preflight();
|
|
22720
22837
|
function buildInitCommand(brand) {
|
|
22721
|
-
return new commander_1.Command("init").description(`Pair this ${brand.product.title} with Keep and seed local state`).option("--pairing-code <code>", "Pairing code from the product UI").option("--keep-url <url>", "Override the Keep API URL
|
|
22838
|
+
return new commander_1.Command("init").description(`Pair this ${brand.product.title} with Keep and seed local state`).option("--pairing-code <code>", "Pairing code from the product UI").option("--keep-url <url>", "Override the Keep API URL, beating both the profile's environment and the brand default").option("--jobs-url <url>", "Override the Jobs API URL, beating both the profile's environment and the brand default").option("--json", "Print the resolved pairing environment as JSON, one object per line; the rest of the output is unchanged").option("--executor <type>", `Executor to write into ${brand.product.id}.yaml (e.g. claude-code). Must be in the brand's executor allowlist.`).option("--poll-interval <ms>", "Run-loop poll interval in milliseconds").option("--daily-cap <tokens>", "Daily token cap; overrides the brand default").option("--monthly-cap <tokens>", "Monthly token cap; overrides the brand default").option("--tmp-dir <path>", "Base directory for each job's throwaway checkout (defaults to the OS temp dir; set only when that is unsuitable)").option("-p, --profile <name>", profileFlagDescription(brand)).action(async (options) => {
|
|
22722
22839
|
try {
|
|
22723
22840
|
const profile = (0, apply_1.resolveProfile)(brand, options.profile);
|
|
22724
22841
|
const setupOptions = parseSetupOptions(brand, options);
|
|
22725
|
-
const
|
|
22726
|
-
|
|
22727
|
-
|
|
22728
|
-
|
|
22729
|
-
|
|
22730
|
-
|
|
22731
|
-
|
|
22732
|
-
|
|
22842
|
+
const asJson = options.json === true;
|
|
22843
|
+
const environment = (0, environment_1.resolveEnvironment)(brand, profile, {
|
|
22844
|
+
keepUrl: options.keepUrl,
|
|
22845
|
+
jobsUrl: options.jobsUrl
|
|
22846
|
+
});
|
|
22847
|
+
const effectiveBrand = (0, environment_1.applyEnvironment)(brand, environment);
|
|
22848
|
+
const report = (0, environment_1.isWorthReporting)(environment);
|
|
22849
|
+
if (report) {
|
|
22850
|
+
if (asJson)
|
|
22851
|
+
console.log((0, environment_1.environmentReportJson)(environment));
|
|
22852
|
+
else
|
|
22853
|
+
for (const line of (0, environment_1.renderEnvironmentReport)(environment))
|
|
22854
|
+
console.log(line);
|
|
22855
|
+
}
|
|
22733
22856
|
const paths = (0, apply_1.resolvePaths)(effectiveBrand, profile);
|
|
22734
22857
|
await fsp.mkdir(paths.configDir, { recursive: true });
|
|
22735
22858
|
for (const step of effectiveBrand.setupSteps ?? []) {
|
|
@@ -22759,7 +22882,14 @@ var require_init = __commonJS({
|
|
|
22759
22882
|
}
|
|
22760
22883
|
const pairing = await (0, device_code_1.pair)({
|
|
22761
22884
|
brand: effectiveBrand,
|
|
22762
|
-
pairingCode: options.pairingCode
|
|
22885
|
+
pairingCode: options.pairingCode,
|
|
22886
|
+
// Keep's answer is the half no local table can predict: it
|
|
22887
|
+
// builds `verification_uri` from its OWN configuration, so a
|
|
22888
|
+
// tier pointed at a page nobody serves looks exactly like a
|
|
22889
|
+
// healthy one until an operator opens the link.
|
|
22890
|
+
...report ? {
|
|
22891
|
+
onVerificationUri: (uri) => console.log(asJson ? (0, environment_1.verificationJson)(uri) : (0, environment_1.renderVerificationLine)(uri))
|
|
22892
|
+
} : {}
|
|
22763
22893
|
});
|
|
22764
22894
|
const store = new store_1.PairingConfigStore(paths.configFile);
|
|
22765
22895
|
await store.write(pairing);
|
|
@@ -22799,6 +22929,13 @@ var require_init = __commonJS({
|
|
|
22799
22929
|
}
|
|
22800
22930
|
});
|
|
22801
22931
|
}
|
|
22932
|
+
function profileFlagDescription(brand) {
|
|
22933
|
+
const known = Object.keys(brand.environments ?? {}).sort();
|
|
22934
|
+
const base = 'Config profile to pair into (isolates this pairing under its own dir; default "default")';
|
|
22935
|
+
if (known.length === 0)
|
|
22936
|
+
return base;
|
|
22937
|
+
return `${base}. These names also select the backend: ${known.join(", ")}`;
|
|
22938
|
+
}
|
|
22802
22939
|
function parseSetupOptions(brand, raw) {
|
|
22803
22940
|
const out = {};
|
|
22804
22941
|
if (raw.executor !== void 0) {
|
|
@@ -25096,7 +25233,8 @@ var require_dist5 = __commonJS({
|
|
|
25096
25233
|
"../../packages/shuttle/dist/index.js"(exports2) {
|
|
25097
25234
|
"use strict";
|
|
25098
25235
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
25099
|
-
exports2.
|
|
25236
|
+
exports2.listPairings = exports2.PairingConfigStore = exports2.pair = exports2.DEFAULT_REFUSAL_MESSAGE = exports2.DEFAULT_MONTHLY_PERIOD = exports2.DEFAULT_DAILY_PERIOD = exports2.SUBSTRATE_BRAND = exports2.verificationJson = exports2.resolveEnvironment = exports2.renderVerificationLine = exports2.renderEnvironmentReport = exports2.isWorthReporting = exports2.hostOf = exports2.environmentReportJson = exports2.applyEnvironment = exports2.renderTemplate = exports2.expandHome = exports2.resolvePaths = exports2.SpendStore = exports2.AuditLogStore = exports2.createSpendTrackerObserver = exports2.createAuditLogObserver = exports2.createLoggerObserver = exports2.SpendCapExceeded = exports2.ObserverChain = exports2.createCustomScriptExecutor = exports2.createWebhookExecutor = exports2.createHttpApiExecutor = exports2.createClaudeCodeExecutor = exports2.ExecutorRegistry = exports2.AgentCredentialRevokedError = exports2.runPreflight = exports2.renderLines = exports2.renderJson = exports2.printReport = exports2.hasFailure = exports2.createProbes = exports2.checksFor = exports2.PREFLIGHT_FLOORS = exports2.PREFLIGHT_EXIT_CODE = exports2.CHECKS = exports2.readResponseLatestVersion = exports2.checkResponseMinVersion = exports2.compareSemver = exports2.UpgradeRequiredError = exports2.LATEST_VERSION_HEADER = exports2.MIN_VERSION_HEADER = exports2.loadConfig = exports2.Shuttle = exports2.createCli = void 0;
|
|
25237
|
+
exports2.revokePairing = void 0;
|
|
25100
25238
|
var cli_1 = require_cli();
|
|
25101
25239
|
Object.defineProperty(exports2, "createCli", { enumerable: true, get: function() {
|
|
25102
25240
|
return cli_1.createCli;
|
|
@@ -25220,6 +25358,31 @@ var require_dist5 = __commonJS({
|
|
|
25220
25358
|
Object.defineProperty(exports2, "renderTemplate", { enumerable: true, get: function() {
|
|
25221
25359
|
return apply_1.renderTemplate;
|
|
25222
25360
|
} });
|
|
25361
|
+
var environment_1 = require_environment();
|
|
25362
|
+
Object.defineProperty(exports2, "applyEnvironment", { enumerable: true, get: function() {
|
|
25363
|
+
return environment_1.applyEnvironment;
|
|
25364
|
+
} });
|
|
25365
|
+
Object.defineProperty(exports2, "environmentReportJson", { enumerable: true, get: function() {
|
|
25366
|
+
return environment_1.environmentReportJson;
|
|
25367
|
+
} });
|
|
25368
|
+
Object.defineProperty(exports2, "hostOf", { enumerable: true, get: function() {
|
|
25369
|
+
return environment_1.hostOf;
|
|
25370
|
+
} });
|
|
25371
|
+
Object.defineProperty(exports2, "isWorthReporting", { enumerable: true, get: function() {
|
|
25372
|
+
return environment_1.isWorthReporting;
|
|
25373
|
+
} });
|
|
25374
|
+
Object.defineProperty(exports2, "renderEnvironmentReport", { enumerable: true, get: function() {
|
|
25375
|
+
return environment_1.renderEnvironmentReport;
|
|
25376
|
+
} });
|
|
25377
|
+
Object.defineProperty(exports2, "renderVerificationLine", { enumerable: true, get: function() {
|
|
25378
|
+
return environment_1.renderVerificationLine;
|
|
25379
|
+
} });
|
|
25380
|
+
Object.defineProperty(exports2, "resolveEnvironment", { enumerable: true, get: function() {
|
|
25381
|
+
return environment_1.resolveEnvironment;
|
|
25382
|
+
} });
|
|
25383
|
+
Object.defineProperty(exports2, "verificationJson", { enumerable: true, get: function() {
|
|
25384
|
+
return environment_1.verificationJson;
|
|
25385
|
+
} });
|
|
25223
25386
|
var defaults_1 = require_defaults2();
|
|
25224
25387
|
Object.defineProperty(exports2, "SUBSTRATE_BRAND", { enumerable: true, get: function() {
|
|
25225
25388
|
return defaults_1.SUBSTRATE_BRAND;
|
|
@@ -25259,7 +25422,7 @@ var import_path = require("path");
|
|
|
25259
25422
|
var import_promises = require("fs/promises");
|
|
25260
25423
|
var import_yaml = __toESM(require_dist4());
|
|
25261
25424
|
var import_shuttle = __toESM(require_dist5());
|
|
25262
|
-
var buildVersion = true ? "0.
|
|
25425
|
+
var buildVersion = true ? "0.30.0" : pkg.version;
|
|
25263
25426
|
var sifterBrand = {
|
|
25264
25427
|
product: {
|
|
25265
25428
|
id: "sifter",
|
|
@@ -25307,6 +25470,40 @@ var sifterBrand = {
|
|
|
25307
25470
|
]
|
|
25308
25471
|
}
|
|
25309
25472
|
},
|
|
25473
|
+
// The environments Whittle Labs operates, keyed by the profile name that
|
|
25474
|
+
// selects one. `init --profile stage` resolves both URLs from here, so a
|
|
25475
|
+
// non-prod pairing is one flag instead of three and there is no host to
|
|
25476
|
+
// mistype. `--keep-url` / `--jobs-url` still win, which is what keeps a
|
|
25477
|
+
// backend Whittle Labs does not run reachable.
|
|
25478
|
+
//
|
|
25479
|
+
// `prod` is stated even though it duplicates the defaults below: a person
|
|
25480
|
+
// reading `--help` should see the whole set, and a profile literally named
|
|
25481
|
+
// `prod` should resolve rather than fall through to a default that only
|
|
25482
|
+
// happens to agree. The DEFAULT profile ("default") is deliberately not a
|
|
25483
|
+
// key — it is not an environment name, and it keeps its historic meaning
|
|
25484
|
+
// of "the brand's own defaults", which are production's.
|
|
25485
|
+
//
|
|
25486
|
+
// `studio` is the local dev fleet on this machine, reachable over Tailscale
|
|
25487
|
+
// at the `studio.whittlelabs.com` hostname rather than `localhost`, because
|
|
25488
|
+
// a pairing made from a phone or a second machine has to resolve too.
|
|
25489
|
+
environments: {
|
|
25490
|
+
prod: {
|
|
25491
|
+
keepApiUrl: "https://keep.whittlelabs.com",
|
|
25492
|
+
jobsApiUrl: "https://jobs.whittlelabs.com"
|
|
25493
|
+
},
|
|
25494
|
+
stage: {
|
|
25495
|
+
keepApiUrl: "https://keep.stage.whittlelabs.com",
|
|
25496
|
+
jobsApiUrl: "https://jobs.stage.whittlelabs.com"
|
|
25497
|
+
},
|
|
25498
|
+
test: {
|
|
25499
|
+
keepApiUrl: "https://keep.test.whittlelabs.com",
|
|
25500
|
+
jobsApiUrl: "https://jobs.test.whittlelabs.com"
|
|
25501
|
+
},
|
|
25502
|
+
studio: {
|
|
25503
|
+
keepApiUrl: "http://studio.whittlelabs.com:3007",
|
|
25504
|
+
jobsApiUrl: "http://studio.whittlelabs.com:3005"
|
|
25505
|
+
}
|
|
25506
|
+
},
|
|
25310
25507
|
keepRegistration: {
|
|
25311
25508
|
keepApiUrl: process.env.KEEP_API_URL ?? "https://keep.whittlelabs.com",
|
|
25312
25509
|
jobsApiUrl: process.env.JOBS_API_URL ?? "https://jobs.whittlelabs.com",
|