@whittlelabs/sifter 0.29.0 → 0.30.1
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 +348 -29
- 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
|
@@ -10008,6 +10008,8 @@ var require_claude_code = __commonJS({
|
|
|
10008
10008
|
exports2.createClaudeCodeExecutor = createClaudeCodeExecutor;
|
|
10009
10009
|
exports2.parseStreamJson = parseStreamJson;
|
|
10010
10010
|
exports2.relativeWorkspaceReads = relativeWorkspaceReads;
|
|
10011
|
+
exports2.globToRegExp = globToRegExp;
|
|
10012
|
+
exports2.creditOverlayReads = creditOverlayReads;
|
|
10011
10013
|
exports2.extractJsonObject = extractJsonObject;
|
|
10012
10014
|
var child_process_1 = require("child_process");
|
|
10013
10015
|
var fs_1 = require("fs");
|
|
@@ -10092,12 +10094,11 @@ var require_claude_code = __commonJS({
|
|
|
10092
10094
|
promptOnlyScratch = await fs_1.promises.mkdtemp(path_1.default.join(tmpBase, "shuttle-cc-run-"));
|
|
10093
10095
|
}
|
|
10094
10096
|
const runCwd = workspace?.cwd ?? promptOnlyScratch;
|
|
10095
|
-
const { response, readPaths,
|
|
10097
|
+
const { response, readPaths, toolInputs } = await this.runClaude(prompt, spec, runCwd, childEnv, signal, hintedModel, reporter, void 0, dispatch.job.id);
|
|
10096
10098
|
if (workspace) {
|
|
10097
10099
|
const reads = [...readPaths];
|
|
10098
|
-
for (const rel of workspace.overlay?.writtenPaths ?? []) {
|
|
10099
|
-
|
|
10100
|
-
reads.push(path_1.default.join(runCwd, rel));
|
|
10100
|
+
for (const rel of creditOverlayReads(toolInputs, workspace.overlay?.writtenPaths ?? [])) {
|
|
10101
|
+
reads.push(path_1.default.join(runCwd, rel));
|
|
10101
10102
|
}
|
|
10102
10103
|
response.outputs.push({
|
|
10103
10104
|
label: "workspace_reads",
|
|
@@ -10155,14 +10156,14 @@ var require_claude_code = __commonJS({
|
|
|
10155
10156
|
outputLabel: handle.outputLabel,
|
|
10156
10157
|
outputSchema: handle.outputSchema
|
|
10157
10158
|
};
|
|
10158
|
-
const { response } = await this.runClaude(followUp, spec, scratch, handle.childEnv, signal, handle.model, null, handle.sessionId);
|
|
10159
|
+
const { response } = await this.runClaude(followUp, spec, scratch, handle.childEnv, signal, handle.model, null, handle.sessionId, `${handle.sessionId}-repair`);
|
|
10159
10160
|
return response;
|
|
10160
10161
|
} finally {
|
|
10161
10162
|
await fs_1.promises.rm(scratch, { recursive: true, force: true }).catch(() => {
|
|
10162
10163
|
});
|
|
10163
10164
|
}
|
|
10164
10165
|
}
|
|
10165
|
-
runClaude(prompt, spec, cwd, childEnv, signal, modelOverride, reporter, resumeSessionId) {
|
|
10166
|
+
runClaude(prompt, spec, cwd, childEnv, signal, modelOverride, reporter, resumeSessionId, transcriptName) {
|
|
10166
10167
|
const outputLabel = spec.outputLabel;
|
|
10167
10168
|
const model = modelOverride ?? this.config.model;
|
|
10168
10169
|
return new Promise((resolve, reject) => {
|
|
@@ -10251,6 +10252,9 @@ var require_claude_code = __commonJS({
|
|
|
10251
10252
|
tap.end();
|
|
10252
10253
|
}
|
|
10253
10254
|
const stdout = Buffer.concat(stdoutChunks).toString("utf-8");
|
|
10255
|
+
if (this.config.keepTranscripts && transcriptName) {
|
|
10256
|
+
void keepTranscript((0, apply_1.expandHome)(this.config.keepTranscripts), transcriptName, stdout);
|
|
10257
|
+
}
|
|
10254
10258
|
if (code === 0) {
|
|
10255
10259
|
try {
|
|
10256
10260
|
const stream = parseStreamJson(stdout);
|
|
@@ -10286,7 +10290,7 @@ var require_claude_code = __commonJS({
|
|
|
10286
10290
|
} : {}
|
|
10287
10291
|
},
|
|
10288
10292
|
readPaths: stream.readPaths,
|
|
10289
|
-
|
|
10293
|
+
toolInputs: stream.toolInputs
|
|
10290
10294
|
}));
|
|
10291
10295
|
} catch (err) {
|
|
10292
10296
|
settle(() => reject(err instanceof Error ? err : new Error(String(err))));
|
|
@@ -10334,6 +10338,12 @@ var require_claude_code = __commonJS({
|
|
|
10334
10338
|
}
|
|
10335
10339
|
validated.timeout = config.timeout;
|
|
10336
10340
|
}
|
|
10341
|
+
if (config.keepTranscripts !== void 0) {
|
|
10342
|
+
if (typeof config.keepTranscripts !== "string" || config.keepTranscripts.length === 0) {
|
|
10343
|
+
throw new Error('"keepTranscripts" must be a non-empty directory path');
|
|
10344
|
+
}
|
|
10345
|
+
validated.keepTranscripts = config.keepTranscripts;
|
|
10346
|
+
}
|
|
10337
10347
|
if (config.overlayAllowedHosts !== void 0) {
|
|
10338
10348
|
if (!Array.isArray(config.overlayAllowedHosts) || !config.overlayAllowedHosts.every((h) => typeof h === "string")) {
|
|
10339
10349
|
throw new Error('"overlayAllowedHosts" must be an array of strings');
|
|
@@ -10358,7 +10368,7 @@ var require_claude_code = __commonJS({
|
|
|
10358
10368
|
let resultMeta = null;
|
|
10359
10369
|
let structuredOutput = null;
|
|
10360
10370
|
let sessionId = null;
|
|
10361
|
-
|
|
10371
|
+
const toolInputs = [];
|
|
10362
10372
|
for (const line of stdout.split("\n")) {
|
|
10363
10373
|
const trimmed = line.trim();
|
|
10364
10374
|
if (!trimmed.startsWith("{"))
|
|
@@ -10384,7 +10394,7 @@ var require_claude_code = __commonJS({
|
|
|
10384
10394
|
readPaths.push(fp);
|
|
10385
10395
|
}
|
|
10386
10396
|
if (b.input && typeof b.input === "object")
|
|
10387
|
-
|
|
10397
|
+
toolInputs.push(JSON.stringify(b.input));
|
|
10388
10398
|
}
|
|
10389
10399
|
} else if (event.type === "result") {
|
|
10390
10400
|
if (typeof event.result === "string" && event.result.trim().length > 0) {
|
|
@@ -10412,24 +10422,131 @@ var require_claude_code = __commonJS({
|
|
|
10412
10422
|
};
|
|
10413
10423
|
}
|
|
10414
10424
|
}
|
|
10415
|
-
return { finalText, readPaths, usage, resultMeta, structuredOutput, sessionId,
|
|
10425
|
+
return { finalText, readPaths, usage, resultMeta, structuredOutput, sessionId, toolInputs };
|
|
10416
10426
|
}
|
|
10417
10427
|
var MAX_REPORTED_READS = 2e3;
|
|
10418
10428
|
function relativeWorkspaceReads(readPaths, cwd) {
|
|
10419
10429
|
const root = path_1.default.resolve(cwd);
|
|
10430
|
+
const roots = [root];
|
|
10431
|
+
try {
|
|
10432
|
+
const real = (0, fs_1.realpathSync)(root);
|
|
10433
|
+
if (real !== root)
|
|
10434
|
+
roots.push(real);
|
|
10435
|
+
} catch {
|
|
10436
|
+
}
|
|
10420
10437
|
const out = /* @__PURE__ */ new Set();
|
|
10421
10438
|
for (const p of readPaths) {
|
|
10422
10439
|
const resolved = path_1.default.resolve(root, p);
|
|
10423
|
-
|
|
10424
|
-
|
|
10425
|
-
|
|
10426
|
-
|
|
10427
|
-
|
|
10440
|
+
for (const r of roots) {
|
|
10441
|
+
if (resolved === r)
|
|
10442
|
+
break;
|
|
10443
|
+
if (!resolved.startsWith(r + path_1.default.sep))
|
|
10444
|
+
continue;
|
|
10445
|
+
out.add(resolved.slice(r.length + 1));
|
|
10446
|
+
break;
|
|
10447
|
+
}
|
|
10428
10448
|
if (out.size >= MAX_REPORTED_READS)
|
|
10429
10449
|
break;
|
|
10430
10450
|
}
|
|
10431
10451
|
return [...out].sort();
|
|
10432
10452
|
}
|
|
10453
|
+
var GLOB_CHARS = /[*?[{]/;
|
|
10454
|
+
function escapeRegExp(s) {
|
|
10455
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
10456
|
+
}
|
|
10457
|
+
function globToRegExp(glob) {
|
|
10458
|
+
let re = "^";
|
|
10459
|
+
for (let i = 0; i < glob.length; i++) {
|
|
10460
|
+
const ch = glob[i];
|
|
10461
|
+
if (ch === "*") {
|
|
10462
|
+
if (glob[i + 1] === "*") {
|
|
10463
|
+
if (glob[i + 2] === "/") {
|
|
10464
|
+
re += "(?:.*/)?";
|
|
10465
|
+
i += 2;
|
|
10466
|
+
} else {
|
|
10467
|
+
re += ".*";
|
|
10468
|
+
i += 1;
|
|
10469
|
+
}
|
|
10470
|
+
} else {
|
|
10471
|
+
re += "[^/]*";
|
|
10472
|
+
}
|
|
10473
|
+
} else if (ch === "?") {
|
|
10474
|
+
re += "[^/]";
|
|
10475
|
+
} else if (ch === "[") {
|
|
10476
|
+
const end = glob.indexOf("]", i + 1);
|
|
10477
|
+
if (end === -1)
|
|
10478
|
+
return null;
|
|
10479
|
+
let cls = glob.slice(i + 1, end);
|
|
10480
|
+
if (cls.startsWith("!"))
|
|
10481
|
+
cls = `^${cls.slice(1)}`;
|
|
10482
|
+
re += `[${cls.replace(/\\/g, "\\\\")}]`;
|
|
10483
|
+
i = end;
|
|
10484
|
+
} else if (ch === "{") {
|
|
10485
|
+
const end = glob.indexOf("}", i + 1);
|
|
10486
|
+
if (end === -1)
|
|
10487
|
+
return null;
|
|
10488
|
+
re += `(?:${glob.slice(i + 1, end).split(",").map(escapeRegExp).join("|")})`;
|
|
10489
|
+
i = end;
|
|
10490
|
+
} else {
|
|
10491
|
+
re += escapeRegExp(ch);
|
|
10492
|
+
}
|
|
10493
|
+
}
|
|
10494
|
+
return new RegExp(`${re}$`);
|
|
10495
|
+
}
|
|
10496
|
+
function creditOverlayReads(toolInputs, writtenPaths) {
|
|
10497
|
+
if (writtenPaths.length === 0 || toolInputs.length === 0)
|
|
10498
|
+
return [];
|
|
10499
|
+
const dirs = /* @__PURE__ */ new Set();
|
|
10500
|
+
for (const rel of writtenPaths) {
|
|
10501
|
+
const parts = rel.split("/");
|
|
10502
|
+
for (let i = 1; i < parts.length; i++)
|
|
10503
|
+
dirs.add(`${parts.slice(0, i).join("/")}/`);
|
|
10504
|
+
}
|
|
10505
|
+
const dirAlt = [...dirs].map(escapeRegExp).join("|");
|
|
10506
|
+
const wordRe = dirs.size > 0 ? new RegExp(`(?:${dirAlt})[^\\s"'\`;|&<>(),\\\\]*`, "g") : null;
|
|
10507
|
+
const findRe = dirs.size > 0 ? new RegExp(`\\bfind\\s+(?:\\./)?(${[...dirs].map((d) => escapeRegExp(d.slice(0, -1))).join("|")})/?(?=\\s|$)`, "g") : null;
|
|
10508
|
+
const readerRe = /(?:^|[\s"])(?:-exec|-execdir|xargs)\b[^;|&]*\b(?:cat|head|tail|sed|awk|less|more|bat)\b/;
|
|
10509
|
+
const credited = /* @__PURE__ */ new Set();
|
|
10510
|
+
for (const input of toolInputs) {
|
|
10511
|
+
for (const rel of writtenPaths) {
|
|
10512
|
+
if (input.includes(rel))
|
|
10513
|
+
credited.add(rel);
|
|
10514
|
+
}
|
|
10515
|
+
if (!wordRe)
|
|
10516
|
+
continue;
|
|
10517
|
+
for (const match of input.matchAll(wordRe)) {
|
|
10518
|
+
const word = match[0];
|
|
10519
|
+
if (!GLOB_CHARS.test(word))
|
|
10520
|
+
continue;
|
|
10521
|
+
const re = globToRegExp(word);
|
|
10522
|
+
if (!re)
|
|
10523
|
+
continue;
|
|
10524
|
+
for (const rel of writtenPaths) {
|
|
10525
|
+
if (re.test(rel))
|
|
10526
|
+
credited.add(rel);
|
|
10527
|
+
}
|
|
10528
|
+
}
|
|
10529
|
+
if (findRe && readerRe.test(input)) {
|
|
10530
|
+
for (const match of input.matchAll(findRe)) {
|
|
10531
|
+
const dir = `${match[1]}/`;
|
|
10532
|
+
for (const rel of writtenPaths) {
|
|
10533
|
+
if (rel.startsWith(dir))
|
|
10534
|
+
credited.add(rel);
|
|
10535
|
+
}
|
|
10536
|
+
}
|
|
10537
|
+
}
|
|
10538
|
+
}
|
|
10539
|
+
return [...credited].sort();
|
|
10540
|
+
}
|
|
10541
|
+
async function keepTranscript(dir, name, stdout) {
|
|
10542
|
+
try {
|
|
10543
|
+
await fs_1.promises.mkdir(dir, { recursive: true });
|
|
10544
|
+
const safe = name.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
10545
|
+
await fs_1.promises.writeFile(path_1.default.join(dir, `${safe}.jsonl`), stdout);
|
|
10546
|
+
} catch (err) {
|
|
10547
|
+
console.warn(`[shuttle] could not keep the transcript for ${name} under ${dir}: ${err instanceof Error ? err.message : String(err)}`);
|
|
10548
|
+
}
|
|
10549
|
+
}
|
|
10433
10550
|
function extractJsonObject(s) {
|
|
10434
10551
|
const exact = tryParseObject(s);
|
|
10435
10552
|
if (exact)
|
|
@@ -11604,6 +11721,120 @@ var require_version_check = __commonJS({
|
|
|
11604
11721
|
}
|
|
11605
11722
|
});
|
|
11606
11723
|
|
|
11724
|
+
// ../../packages/shuttle/dist/branding/environment.js
|
|
11725
|
+
var require_environment = __commonJS({
|
|
11726
|
+
"../../packages/shuttle/dist/branding/environment.js"(exports2) {
|
|
11727
|
+
"use strict";
|
|
11728
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
11729
|
+
exports2.resolveEnvironment = resolveEnvironment;
|
|
11730
|
+
exports2.applyEnvironment = applyEnvironment;
|
|
11731
|
+
exports2.isWorthReporting = isWorthReporting;
|
|
11732
|
+
exports2.renderEnvironmentReport = renderEnvironmentReport;
|
|
11733
|
+
exports2.renderVerificationLine = renderVerificationLine;
|
|
11734
|
+
exports2.environmentReportJson = environmentReportJson;
|
|
11735
|
+
exports2.verificationJson = verificationJson;
|
|
11736
|
+
exports2.hostOf = hostOf;
|
|
11737
|
+
function resolveEnvironment(brand, profile, overrides = {}) {
|
|
11738
|
+
const table = brand.environments ?? {};
|
|
11739
|
+
const known = Object.keys(table).sort();
|
|
11740
|
+
const matched = Object.prototype.hasOwnProperty.call(table, profile) ? profile : null;
|
|
11741
|
+
const entry = matched === null ? void 0 : table[matched];
|
|
11742
|
+
return {
|
|
11743
|
+
profile,
|
|
11744
|
+
environment: matched,
|
|
11745
|
+
known,
|
|
11746
|
+
keep: pick(overrides.keepUrl, entry?.keepApiUrl, brand.keepRegistration.keepApiUrl),
|
|
11747
|
+
jobs: pick(overrides.jobsUrl, entry?.jobsApiUrl, brand.keepRegistration.jobsApiUrl)
|
|
11748
|
+
};
|
|
11749
|
+
}
|
|
11750
|
+
function pick(flag, fromEnvironment, brandDefault) {
|
|
11751
|
+
if (flag)
|
|
11752
|
+
return { url: flag, source: "flag" };
|
|
11753
|
+
if (fromEnvironment)
|
|
11754
|
+
return { url: fromEnvironment, source: "environment" };
|
|
11755
|
+
return { url: brandDefault, source: "brand-default" };
|
|
11756
|
+
}
|
|
11757
|
+
function applyEnvironment(brand, resolved) {
|
|
11758
|
+
const reg = brand.keepRegistration;
|
|
11759
|
+
if (resolved.keep.url === reg.keepApiUrl && resolved.jobs.url === reg.jobsApiUrl) {
|
|
11760
|
+
return brand;
|
|
11761
|
+
}
|
|
11762
|
+
return {
|
|
11763
|
+
...brand,
|
|
11764
|
+
keepRegistration: {
|
|
11765
|
+
...reg,
|
|
11766
|
+
keepApiUrl: resolved.keep.url,
|
|
11767
|
+
jobsApiUrl: resolved.jobs.url
|
|
11768
|
+
}
|
|
11769
|
+
};
|
|
11770
|
+
}
|
|
11771
|
+
function isWorthReporting(resolved) {
|
|
11772
|
+
return resolved.environment !== null || resolved.keep.source === "flag" || resolved.jobs.source === "flag";
|
|
11773
|
+
}
|
|
11774
|
+
function renderEnvironmentReport(resolved) {
|
|
11775
|
+
return [
|
|
11776
|
+
"",
|
|
11777
|
+
"Pairing environment",
|
|
11778
|
+
field("Profile", resolved.profile),
|
|
11779
|
+
field("Environment", describeEnvironment(resolved)),
|
|
11780
|
+
field("Keep", `${resolved.keep.url} ${describeSource(resolved, resolved.keep)}`),
|
|
11781
|
+
field("Jobs", `${resolved.jobs.url} ${describeSource(resolved, resolved.jobs)}`),
|
|
11782
|
+
""
|
|
11783
|
+
];
|
|
11784
|
+
}
|
|
11785
|
+
function renderVerificationLine(verificationUri) {
|
|
11786
|
+
return field("Verify at", `${hostOf(verificationUri)} as Keep reported it`);
|
|
11787
|
+
}
|
|
11788
|
+
function environmentReportJson(resolved) {
|
|
11789
|
+
return JSON.stringify({
|
|
11790
|
+
kind: "pairing-environment",
|
|
11791
|
+
profile: resolved.profile,
|
|
11792
|
+
environment: resolved.environment,
|
|
11793
|
+
known: resolved.known,
|
|
11794
|
+
keep: resolved.keep,
|
|
11795
|
+
jobs: resolved.jobs
|
|
11796
|
+
});
|
|
11797
|
+
}
|
|
11798
|
+
function verificationJson(verificationUri) {
|
|
11799
|
+
return JSON.stringify({
|
|
11800
|
+
kind: "pairing-verification",
|
|
11801
|
+
verificationUri,
|
|
11802
|
+
host: hostOf(verificationUri),
|
|
11803
|
+
source: "keep"
|
|
11804
|
+
});
|
|
11805
|
+
}
|
|
11806
|
+
function field(label, value) {
|
|
11807
|
+
return ` ${`${label}:`.padEnd(13)}${value}`;
|
|
11808
|
+
}
|
|
11809
|
+
function describeEnvironment(resolved) {
|
|
11810
|
+
if (resolved.environment !== null) {
|
|
11811
|
+
return `${resolved.environment} (named by this build)`;
|
|
11812
|
+
}
|
|
11813
|
+
if (resolved.known.length === 0) {
|
|
11814
|
+
return "none (this build declares no environments)";
|
|
11815
|
+
}
|
|
11816
|
+
return `none (this build knows ${resolved.known.join(", ")})`;
|
|
11817
|
+
}
|
|
11818
|
+
function describeSource(resolved, url) {
|
|
11819
|
+
switch (url.source) {
|
|
11820
|
+
case "flag":
|
|
11821
|
+
return "from the flag you passed";
|
|
11822
|
+
case "environment":
|
|
11823
|
+
return `from the "${resolved.environment ?? ""}" environment`;
|
|
11824
|
+
default:
|
|
11825
|
+
return "from the brand default";
|
|
11826
|
+
}
|
|
11827
|
+
}
|
|
11828
|
+
function hostOf(url) {
|
|
11829
|
+
try {
|
|
11830
|
+
return new URL(url).host;
|
|
11831
|
+
} catch {
|
|
11832
|
+
return url;
|
|
11833
|
+
}
|
|
11834
|
+
}
|
|
11835
|
+
}
|
|
11836
|
+
});
|
|
11837
|
+
|
|
11607
11838
|
// ../../packages/shuttle/dist/preflight/checks.js
|
|
11608
11839
|
var require_checks = __commonJS({
|
|
11609
11840
|
"../../packages/shuttle/dist/preflight/checks.js"(exports2) {
|
|
@@ -11619,6 +11850,7 @@ var require_checks = __commonJS({
|
|
|
11619
11850
|
var loom_1 = require_dist();
|
|
11620
11851
|
var version_check_1 = require_version_check();
|
|
11621
11852
|
var apply_1 = require_apply();
|
|
11853
|
+
var environment_1 = require_environment();
|
|
11622
11854
|
var NODE_FLOOR = "20.0.0";
|
|
11623
11855
|
var DISK_FLOOR_BYTES = 1024 * 1024 * 1024;
|
|
11624
11856
|
var WORKSPACE_EXECUTOR = "claude-code";
|
|
@@ -11745,7 +11977,7 @@ var require_checks = __commonJS({
|
|
|
11745
11977
|
if (strays.length > 0) {
|
|
11746
11978
|
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
11979
|
}
|
|
11748
|
-
return pass("pairing-environment", `
|
|
11980
|
+
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
11981
|
});
|
|
11750
11982
|
}
|
|
11751
11983
|
};
|
|
@@ -22369,6 +22601,7 @@ var require_device_code = __commonJS({
|
|
|
22369
22601
|
throw err;
|
|
22370
22602
|
throw new Error(webFirstRemedy(opts.brand, err));
|
|
22371
22603
|
}
|
|
22604
|
+
opts.onVerificationUri?.(device.verificationUri);
|
|
22372
22605
|
const invitation = (0, terminal_1.renderPairingInvitation)({
|
|
22373
22606
|
header: opts.brand.strings?.pairingHeader ?? `Pair your ${opts.brand.product.title}`,
|
|
22374
22607
|
verificationUri: device.verificationUri,
|
|
@@ -22712,24 +22945,31 @@ var require_init = __commonJS({
|
|
|
22712
22945
|
var fsp = __importStar(require("fs/promises"));
|
|
22713
22946
|
var commander_1 = require_commander();
|
|
22714
22947
|
var apply_1 = require_apply();
|
|
22948
|
+
var environment_1 = require_environment();
|
|
22715
22949
|
var device_code_1 = require_device_code();
|
|
22716
22950
|
var store_1 = require_store();
|
|
22717
22951
|
var spend_store_1 = require_spend_store();
|
|
22718
22952
|
var typed_exit_1 = require_typed_exit();
|
|
22719
22953
|
var preflight_1 = require_preflight();
|
|
22720
22954
|
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
|
|
22955
|
+
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
22956
|
try {
|
|
22723
22957
|
const profile = (0, apply_1.resolveProfile)(brand, options.profile);
|
|
22724
22958
|
const setupOptions = parseSetupOptions(brand, options);
|
|
22725
|
-
const
|
|
22726
|
-
|
|
22727
|
-
|
|
22728
|
-
|
|
22729
|
-
|
|
22730
|
-
|
|
22731
|
-
|
|
22732
|
-
|
|
22959
|
+
const asJson = options.json === true;
|
|
22960
|
+
const environment = (0, environment_1.resolveEnvironment)(brand, profile, {
|
|
22961
|
+
keepUrl: options.keepUrl,
|
|
22962
|
+
jobsUrl: options.jobsUrl
|
|
22963
|
+
});
|
|
22964
|
+
const effectiveBrand = (0, environment_1.applyEnvironment)(brand, environment);
|
|
22965
|
+
const report = (0, environment_1.isWorthReporting)(environment);
|
|
22966
|
+
if (report) {
|
|
22967
|
+
if (asJson)
|
|
22968
|
+
console.log((0, environment_1.environmentReportJson)(environment));
|
|
22969
|
+
else
|
|
22970
|
+
for (const line of (0, environment_1.renderEnvironmentReport)(environment))
|
|
22971
|
+
console.log(line);
|
|
22972
|
+
}
|
|
22733
22973
|
const paths = (0, apply_1.resolvePaths)(effectiveBrand, profile);
|
|
22734
22974
|
await fsp.mkdir(paths.configDir, { recursive: true });
|
|
22735
22975
|
for (const step of effectiveBrand.setupSteps ?? []) {
|
|
@@ -22759,7 +22999,14 @@ var require_init = __commonJS({
|
|
|
22759
22999
|
}
|
|
22760
23000
|
const pairing = await (0, device_code_1.pair)({
|
|
22761
23001
|
brand: effectiveBrand,
|
|
22762
|
-
pairingCode: options.pairingCode
|
|
23002
|
+
pairingCode: options.pairingCode,
|
|
23003
|
+
// Keep's answer is the half no local table can predict: it
|
|
23004
|
+
// builds `verification_uri` from its OWN configuration, so a
|
|
23005
|
+
// tier pointed at a page nobody serves looks exactly like a
|
|
23006
|
+
// healthy one until an operator opens the link.
|
|
23007
|
+
...report ? {
|
|
23008
|
+
onVerificationUri: (uri) => console.log(asJson ? (0, environment_1.verificationJson)(uri) : (0, environment_1.renderVerificationLine)(uri))
|
|
23009
|
+
} : {}
|
|
22763
23010
|
});
|
|
22764
23011
|
const store = new store_1.PairingConfigStore(paths.configFile);
|
|
22765
23012
|
await store.write(pairing);
|
|
@@ -22799,6 +23046,13 @@ var require_init = __commonJS({
|
|
|
22799
23046
|
}
|
|
22800
23047
|
});
|
|
22801
23048
|
}
|
|
23049
|
+
function profileFlagDescription(brand) {
|
|
23050
|
+
const known = Object.keys(brand.environments ?? {}).sort();
|
|
23051
|
+
const base = 'Config profile to pair into (isolates this pairing under its own dir; default "default")';
|
|
23052
|
+
if (known.length === 0)
|
|
23053
|
+
return base;
|
|
23054
|
+
return `${base}. These names also select the backend: ${known.join(", ")}`;
|
|
23055
|
+
}
|
|
22802
23056
|
function parseSetupOptions(brand, raw) {
|
|
22803
23057
|
const out = {};
|
|
22804
23058
|
if (raw.executor !== void 0) {
|
|
@@ -25096,7 +25350,8 @@ var require_dist5 = __commonJS({
|
|
|
25096
25350
|
"../../packages/shuttle/dist/index.js"(exports2) {
|
|
25097
25351
|
"use strict";
|
|
25098
25352
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
25099
|
-
exports2.
|
|
25353
|
+
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;
|
|
25354
|
+
exports2.revokePairing = void 0;
|
|
25100
25355
|
var cli_1 = require_cli();
|
|
25101
25356
|
Object.defineProperty(exports2, "createCli", { enumerable: true, get: function() {
|
|
25102
25357
|
return cli_1.createCli;
|
|
@@ -25220,6 +25475,31 @@ var require_dist5 = __commonJS({
|
|
|
25220
25475
|
Object.defineProperty(exports2, "renderTemplate", { enumerable: true, get: function() {
|
|
25221
25476
|
return apply_1.renderTemplate;
|
|
25222
25477
|
} });
|
|
25478
|
+
var environment_1 = require_environment();
|
|
25479
|
+
Object.defineProperty(exports2, "applyEnvironment", { enumerable: true, get: function() {
|
|
25480
|
+
return environment_1.applyEnvironment;
|
|
25481
|
+
} });
|
|
25482
|
+
Object.defineProperty(exports2, "environmentReportJson", { enumerable: true, get: function() {
|
|
25483
|
+
return environment_1.environmentReportJson;
|
|
25484
|
+
} });
|
|
25485
|
+
Object.defineProperty(exports2, "hostOf", { enumerable: true, get: function() {
|
|
25486
|
+
return environment_1.hostOf;
|
|
25487
|
+
} });
|
|
25488
|
+
Object.defineProperty(exports2, "isWorthReporting", { enumerable: true, get: function() {
|
|
25489
|
+
return environment_1.isWorthReporting;
|
|
25490
|
+
} });
|
|
25491
|
+
Object.defineProperty(exports2, "renderEnvironmentReport", { enumerable: true, get: function() {
|
|
25492
|
+
return environment_1.renderEnvironmentReport;
|
|
25493
|
+
} });
|
|
25494
|
+
Object.defineProperty(exports2, "renderVerificationLine", { enumerable: true, get: function() {
|
|
25495
|
+
return environment_1.renderVerificationLine;
|
|
25496
|
+
} });
|
|
25497
|
+
Object.defineProperty(exports2, "resolveEnvironment", { enumerable: true, get: function() {
|
|
25498
|
+
return environment_1.resolveEnvironment;
|
|
25499
|
+
} });
|
|
25500
|
+
Object.defineProperty(exports2, "verificationJson", { enumerable: true, get: function() {
|
|
25501
|
+
return environment_1.verificationJson;
|
|
25502
|
+
} });
|
|
25223
25503
|
var defaults_1 = require_defaults2();
|
|
25224
25504
|
Object.defineProperty(exports2, "SUBSTRATE_BRAND", { enumerable: true, get: function() {
|
|
25225
25505
|
return defaults_1.SUBSTRATE_BRAND;
|
|
@@ -25259,7 +25539,7 @@ var import_path = require("path");
|
|
|
25259
25539
|
var import_promises = require("fs/promises");
|
|
25260
25540
|
var import_yaml = __toESM(require_dist4());
|
|
25261
25541
|
var import_shuttle = __toESM(require_dist5());
|
|
25262
|
-
var buildVersion = true ? "0.
|
|
25542
|
+
var buildVersion = true ? "0.30.1" : pkg.version;
|
|
25263
25543
|
var sifterBrand = {
|
|
25264
25544
|
product: {
|
|
25265
25545
|
id: "sifter",
|
|
@@ -25304,7 +25584,46 @@ var sifterBrand = {
|
|
|
25304
25584
|
"https://api.sift.whittlelabs.com",
|
|
25305
25585
|
"https://api.sift.stage.whittlelabs.com",
|
|
25306
25586
|
"http://localhost:3008"
|
|
25307
|
-
]
|
|
25587
|
+
],
|
|
25588
|
+
// Transcript retention is a diagnostic switch a person flips for one
|
|
25589
|
+
// session, so it rides the environment rather than a file they would
|
|
25590
|
+
// have to remember to unset. The shared package reads no env of its
|
|
25591
|
+
// own (BE-005); this brand is where the variable becomes config.
|
|
25592
|
+
...process.env.SHUTTLE_KEEP_TRANSCRIPTS ? { keepTranscripts: process.env.SHUTTLE_KEEP_TRANSCRIPTS } : {}
|
|
25593
|
+
}
|
|
25594
|
+
},
|
|
25595
|
+
// The environments Whittle Labs operates, keyed by the profile name that
|
|
25596
|
+
// selects one. `init --profile stage` resolves both URLs from here, so a
|
|
25597
|
+
// non-prod pairing is one flag instead of three and there is no host to
|
|
25598
|
+
// mistype. `--keep-url` / `--jobs-url` still win, which is what keeps a
|
|
25599
|
+
// backend Whittle Labs does not run reachable.
|
|
25600
|
+
//
|
|
25601
|
+
// `prod` is stated even though it duplicates the defaults below: a person
|
|
25602
|
+
// reading `--help` should see the whole set, and a profile literally named
|
|
25603
|
+
// `prod` should resolve rather than fall through to a default that only
|
|
25604
|
+
// happens to agree. The DEFAULT profile ("default") is deliberately not a
|
|
25605
|
+
// key — it is not an environment name, and it keeps its historic meaning
|
|
25606
|
+
// of "the brand's own defaults", which are production's.
|
|
25607
|
+
//
|
|
25608
|
+
// `studio` is the local dev fleet on this machine, reachable over Tailscale
|
|
25609
|
+
// at the `studio.whittlelabs.com` hostname rather than `localhost`, because
|
|
25610
|
+
// a pairing made from a phone or a second machine has to resolve too.
|
|
25611
|
+
environments: {
|
|
25612
|
+
prod: {
|
|
25613
|
+
keepApiUrl: "https://keep.whittlelabs.com",
|
|
25614
|
+
jobsApiUrl: "https://jobs.whittlelabs.com"
|
|
25615
|
+
},
|
|
25616
|
+
stage: {
|
|
25617
|
+
keepApiUrl: "https://keep.stage.whittlelabs.com",
|
|
25618
|
+
jobsApiUrl: "https://jobs.stage.whittlelabs.com"
|
|
25619
|
+
},
|
|
25620
|
+
test: {
|
|
25621
|
+
keepApiUrl: "https://keep.test.whittlelabs.com",
|
|
25622
|
+
jobsApiUrl: "https://jobs.test.whittlelabs.com"
|
|
25623
|
+
},
|
|
25624
|
+
studio: {
|
|
25625
|
+
keepApiUrl: "http://studio.whittlelabs.com:3007",
|
|
25626
|
+
jobsApiUrl: "http://studio.whittlelabs.com:3005"
|
|
25308
25627
|
}
|
|
25309
25628
|
},
|
|
25310
25629
|
keepRegistration: {
|