@hue-run/sdk 0.3.1 → 0.4.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.
Files changed (45) hide show
  1. package/CLI.md +270 -47
  2. package/ENVIRONMENTS.md +10 -0
  3. package/EVALUATIONS.md +1 -1
  4. package/README.md +20 -4
  5. package/dist/client.d.ts +5 -5
  6. package/dist/client.js +13 -6
  7. package/dist/environment/tools.d.ts +6 -1
  8. package/dist/environment/tools.js +7 -1
  9. package/dist/environment/types.d.ts +6 -1
  10. package/dist/evals/environment-target.js +1 -1
  11. package/dist/receipt.js +36 -8
  12. package/dist/setup/application.d.ts +74 -0
  13. package/dist/setup/application.js +766 -0
  14. package/dist/setup/backend.d.ts +229 -0
  15. package/dist/setup/backend.js +855 -0
  16. package/dist/setup/checkpoint.js +100 -30
  17. package/dist/setup/cli.js +20 -4
  18. package/dist/setup/configure.d.ts +13 -0
  19. package/dist/setup/configure.js +454 -0
  20. package/dist/setup/credential.d.ts +2 -0
  21. package/dist/setup/credential.js +9 -0
  22. package/dist/setup/detect.js +4 -1
  23. package/dist/setup/installation.d.ts +118 -0
  24. package/dist/setup/installation.js +605 -0
  25. package/dist/setup/lock.d.ts +2 -0
  26. package/dist/setup/lock.js +38 -0
  27. package/dist/setup/machine.d.ts +1 -10
  28. package/dist/setup/machine.js +8 -7
  29. package/dist/setup/render.d.ts +3 -1
  30. package/dist/setup/render.js +209 -6
  31. package/dist/setup/runner.d.ts +26 -76
  32. package/dist/setup/runner.js +320 -45
  33. package/dist/setup/socket.d.ts +7 -0
  34. package/dist/setup/socket.js +144 -0
  35. package/dist/setup/source.d.ts +9 -0
  36. package/dist/setup/source.js +269 -0
  37. package/dist/setup/types.d.ts +16 -9
  38. package/dist/setup/types.js +1 -1
  39. package/dist/setup.d.ts +6 -2
  40. package/dist/setup.js +3 -0
  41. package/dist/types.d.ts +24 -0
  42. package/dist/version.d.ts +1 -1
  43. package/dist/version.js +1 -1
  44. package/package.json +2 -1
  45. package/setup-events.schema.json +16 -9
@@ -14,8 +14,14 @@ export function transitionSetup(state, input) {
14
14
  }
15
15
  if (state.phase === "detecting" && input.type === "project.detected") {
16
16
  const plan = {
17
- steps: ["detect-project", "configure-telemetry", "verify-receipt", "claim-project"],
18
- mutatesProject: false,
17
+ steps: [
18
+ "detect-project",
19
+ "install-runtime",
20
+ "configure-telemetry",
21
+ "verify-application-receipt",
22
+ "claim-project",
23
+ ],
24
+ mutatesProject: true,
19
25
  backendRequired: true,
20
26
  };
21
27
  return {
@@ -31,11 +37,6 @@ export function transitionSetup(state, input) {
31
37
  { event: "project.detected", project: input.project },
32
38
  { event: "step.completed", step: "detect-project", outcome: "unchanged" },
33
39
  { event: "plan.ready", plan },
34
- {
35
- event: "action.required",
36
- action: "configure",
37
- message: "Local inspection is complete. Telemetry configuration is not available in this build; no project files were changed.",
38
- },
39
40
  ],
40
41
  };
41
42
  }
@@ -1,6 +1,8 @@
1
- import type { SetupEvent } from "./types.js";
1
+ import { type SetupEvent } from "./types.js";
2
2
  /** Supported setup transcript formats. */
3
3
  export type SetupOutputMode = "human" | "plain" | "jsonl";
4
+ /** Removes capability-shaped fragments and bounds text before it reaches a public transcript. */
5
+ export declare function redactSetupTranscriptText(value: string): string;
4
6
  /** Renders one newline-free JSON object for JSONL output. */
5
7
  export declare function renderJsonlEvent(event: SetupEvent): string;
6
8
  /** Renders one ANSI-free append-only transcript entry. */
@@ -1,3 +1,4 @@
1
+ import { SETUP_EVENT_CONTRACT_VERSION } from "./types.js";
1
2
  const ansi = {
2
3
  cyan: "\u001b[36m",
3
4
  green: "\u001b[32m",
@@ -6,6 +7,206 @@ const ansi = {
6
7
  dim: "\u001b[2m",
7
8
  reset: "\u001b[0m",
8
9
  };
10
+ /** Removes capability-shaped fragments and bounds text before it reaches a public transcript. */
11
+ export function redactSetupTranscriptText(value) {
12
+ if (typeof value !== "string")
13
+ return "[invalid text]";
14
+ // Normalize encoded/separated spellings before matching, and redact before truncating.
15
+ let normalized = value;
16
+ for (let pass = 0; pass < 3; pass++)
17
+ normalized = normalized
18
+ .replace(/%([a-f0-9]{2})/giu, (_, hex) => String.fromCharCode(Number.parseInt(hex, 16)))
19
+ .replace(/\\u([a-f0-9]{4})/giu, (_, hex) => String.fromCharCode(Number.parseInt(hex, 16)))
20
+ .replaceAll("\\/", "/");
21
+ normalized = normalized.replace(/[\p{Cc}\p{Cf}]/gu, "");
22
+ const withoutUrls = normalized.replace(/https?:\/\/[^\s"'<>]+/giu, (candidate) => {
23
+ try {
24
+ const url = new URL(candidate);
25
+ return url.hash || url.pathname.includes("/setup/claim")
26
+ ? "[private claim handoff]"
27
+ : candidate;
28
+ }
29
+ catch {
30
+ return "[redacted URL]";
31
+ }
32
+ });
33
+ const redacted = withoutUrls
34
+ // Deliberately recognize incomplete and unknown namespace suffixes too. A
35
+ // transport exception may contain only a truncated credential.
36
+ .replace(/hue_(?:sk|setup|install|claim)_[A-Za-z0-9_-]*/giu, "[private credential]")
37
+ .replace(/#[A-Za-z0-9_-]+/gu, "#[redacted]")
38
+ .replace(/\b(claim[_-]?(?:secret|token))(["']?\s*[:=]\s*["']?)[A-Za-z0-9_-]+/giu, "$1$2[redacted]")
39
+ .replace(/(?<![A-Za-z0-9_-])[A-Za-z0-9_-]{43}(?![A-Za-z0-9_-])/gu, "[private capability]");
40
+ return redacted.slice(0, 1000);
41
+ }
42
+ function choice(value, allowed) {
43
+ return allowed.includes(value) ? value : allowed[0];
44
+ }
45
+ function choices(values, allowed) {
46
+ return Array.isArray(values)
47
+ ? [...new Set(values.filter((value) => allowed.includes(value)))]
48
+ : [];
49
+ }
50
+ const steps = [
51
+ "detect-project",
52
+ "install-runtime",
53
+ "configure-telemetry",
54
+ "verify-application-receipt",
55
+ "claim-project",
56
+ ];
57
+ function publicEvent(event) {
58
+ const base = {
59
+ contractVersion: SETUP_EVENT_CONTRACT_VERSION,
60
+ runId: redactSetupTranscriptText(event.runId),
61
+ sequence: Number.isSafeInteger(event.sequence) ? event.sequence : 1,
62
+ timestamp: redactSetupTranscriptText(event.timestamp),
63
+ };
64
+ switch (event.event) {
65
+ case "run.started":
66
+ return {
67
+ ...base,
68
+ event: "run.started",
69
+ command: choice(event.command, ["setup", "resume", "status", "claim"]),
70
+ mode: choice(event.mode, ["human", "plain", "jsonl"]),
71
+ resumed: event.resumed === true,
72
+ };
73
+ case "project.detected":
74
+ return {
75
+ ...base,
76
+ event: "project.detected",
77
+ project: {
78
+ root: redactSetupTranscriptText(event.project.root),
79
+ fingerprint: redactSetupTranscriptText(event.project.fingerprint),
80
+ languages: choices(event.project.languages, ["typescript", "python"]),
81
+ packageManagers: choices(event.project.packageManagers, [
82
+ "bun",
83
+ "npm",
84
+ "pnpm",
85
+ "yarn",
86
+ "uv",
87
+ "poetry",
88
+ "pip",
89
+ ]),
90
+ frameworks: choices(event.project.frameworks, [
91
+ "nextjs",
92
+ "nestjs",
93
+ "express",
94
+ "fastapi",
95
+ "django",
96
+ "flask",
97
+ "vercel-ai-sdk",
98
+ ]),
99
+ hue: choice(event.project.hue, ["absent", "typescript", "python", "multiple"]),
100
+ openTelemetry: choice(event.project.openTelemetry, [
101
+ "absent",
102
+ "typescript",
103
+ "python",
104
+ "multiple",
105
+ ]),
106
+ },
107
+ };
108
+ case "plan.ready":
109
+ return {
110
+ ...base,
111
+ event: "plan.ready",
112
+ plan: {
113
+ steps: choices(event.plan.steps, steps),
114
+ mutatesProject: event.plan.mutatesProject === true,
115
+ backendRequired: event.plan.backendRequired === true,
116
+ },
117
+ };
118
+ case "step.started":
119
+ return { ...base, event: "step.started", step: choice(event.step, steps) };
120
+ case "step.completed":
121
+ return {
122
+ ...base,
123
+ event: "step.completed",
124
+ step: choice(event.step, steps),
125
+ outcome: choice(event.outcome, ["unchanged", "changed", "verified", "skipped"]),
126
+ };
127
+ case "file.changed":
128
+ return {
129
+ ...base,
130
+ event: "file.changed",
131
+ path: redactSetupTranscriptText(event.path),
132
+ change: choice(event.change, ["created", "updated"]),
133
+ };
134
+ case "diagnostic":
135
+ return {
136
+ ...base,
137
+ event: "diagnostic",
138
+ level: choice(event.level, ["info", "warning", "error"]),
139
+ code: redactSetupTranscriptText(event.code),
140
+ message: redactSetupTranscriptText(event.message),
141
+ };
142
+ case "privacy.notice":
143
+ return {
144
+ ...base,
145
+ event: "privacy.notice",
146
+ privacyUrl: "https://hue.run/privacy",
147
+ effectiveDate: "2026-08-24",
148
+ securityUrl: "https://trust.hue.run/",
149
+ };
150
+ case "action.required":
151
+ return {
152
+ ...base,
153
+ event: "action.required",
154
+ action: choice(event.action, [
155
+ "claim-project",
156
+ "configure",
157
+ "select-project",
158
+ "integrate-application",
159
+ "run-instrumented-request",
160
+ "open-claim-handoff",
161
+ "restart-claim-handoff",
162
+ ]),
163
+ message: redactSetupTranscriptText(event.message),
164
+ ...(event.command ? { command: redactSetupTranscriptText(event.command) } : {}),
165
+ };
166
+ case "trial.created":
167
+ return {
168
+ ...base,
169
+ event: "trial.created",
170
+ trialId: redactSetupTranscriptText(event.trialId),
171
+ expiresAt: redactSetupTranscriptText(event.expiresAt),
172
+ };
173
+ case "receipt.verified":
174
+ return {
175
+ ...base,
176
+ event: "receipt.verified",
177
+ receiptId: redactSetupTranscriptText(event.receiptId),
178
+ traceId: redactSetupTranscriptText(event.traceId),
179
+ source: "repository-http-boundary",
180
+ };
181
+ case "claim.required":
182
+ return {
183
+ ...base,
184
+ event: "claim.required",
185
+ claimId: redactSetupTranscriptText(event.claimId),
186
+ };
187
+ case "claim.completed":
188
+ return {
189
+ ...base,
190
+ event: "claim.completed",
191
+ claimId: redactSetupTranscriptText(event.claimId),
192
+ };
193
+ case "run.completed":
194
+ return {
195
+ ...base,
196
+ event: "run.completed",
197
+ outcome: choice(event.outcome, ["ready", "action_required", "unchanged"]),
198
+ checkpointed: event.checkpointed === true,
199
+ };
200
+ case "run.failed":
201
+ return {
202
+ ...base,
203
+ event: "run.failed",
204
+ code: redactSetupTranscriptText(event.code),
205
+ message: redactSetupTranscriptText(event.message),
206
+ resumable: event.resumable === true,
207
+ };
208
+ }
209
+ }
9
210
  function wrap(text, width, prefix) {
10
211
  const available = Math.max(20, width - prefix.length);
11
212
  const words = text.split(/\s+/u);
@@ -46,14 +247,16 @@ function summary(event) {
46
247
  return `${event.change === "created" ? "Created" : "Updated"} ${event.path}.`;
47
248
  case "diagnostic":
48
249
  return `${event.code}: ${event.message}`;
250
+ case "privacy.notice":
251
+ return `Privacy notice effective ${event.effectiveDate}: ${event.privacyUrl}. Security: ${event.securityUrl}`;
49
252
  case "action.required":
50
253
  return `${event.message}${event.command ? ` Next: ${event.command}.` : ""}`;
51
254
  case "trial.created":
52
255
  return `Anonymous trial ${event.trialId} created; expires ${event.expiresAt}.`;
53
256
  case "receipt.verified":
54
- return `Instrumentation receipt ${event.receiptId} verified for trace ${event.traceId}.`;
257
+ return `Repository HTTP boundary receipt ${event.receiptId} verified for trace ${event.traceId}.`;
55
258
  case "claim.required":
56
- return `Claim ${event.claimId} is ready: ${event.url}`;
259
+ return `Account linkage is ready for project owner action (${event.claimId}).`;
57
260
  case "claim.completed":
58
261
  return `Claim ${event.claimId} completed.`;
59
262
  case "run.completed":
@@ -64,11 +267,11 @@ function summary(event) {
64
267
  }
65
268
  /** Renders one newline-free JSON object for JSONL output. */
66
269
  export function renderJsonlEvent(event) {
67
- return JSON.stringify(event);
270
+ return JSON.stringify(publicEvent(event));
68
271
  }
69
272
  /** Renders one ANSI-free append-only transcript entry. */
70
273
  export function renderPlainEvent(event, width = 80) {
71
- const text = summary(event);
274
+ const text = summary(publicEvent(event));
72
275
  if (!text)
73
276
  return "";
74
277
  const marker = event.event === "run.failed"
@@ -80,7 +283,7 @@ export function renderPlainEvent(event, width = 80) {
80
283
  }
81
284
  /** Renders one lightweight append-only terminal entry; it never moves the cursor or clears the screen. */
82
285
  export function renderHumanEvent(event, width = 80, color = true) {
83
- const text = summary(event);
286
+ const text = summary(publicEvent(event));
84
287
  if (!text)
85
288
  return "";
86
289
  const [symbol, tone] = event.event === "run.failed"
@@ -102,7 +305,7 @@ export function renderHumanEvent(event, width = 80, color = true) {
102
305
  export function selectSetupOutputMode(input) {
103
306
  if (input.agent)
104
307
  return "jsonl";
105
- if (input.explicit === "jsonl" || input.explicit === "plain")
308
+ if (input.explicit)
106
309
  return input.explicit;
107
310
  const env = input.env ?? process.env;
108
311
  if (!input.isTTY || env.NO_COLOR !== undefined || env.TERM === "dumb" || env.CI !== undefined)
@@ -1,101 +1,51 @@
1
+ import type { SetupBackendAdapter } from "./backend.js";
1
2
  import type { SetupMachineState } from "./machine.js";
2
3
  import { type SetupEvent, type SetupProjectDetection } from "./types.js";
3
- /** Non-secret setup-trial identity returned by a future account-attachment adapter. */
4
- export interface SetupBackendTrial {
5
- /** Non-secret trial identifier. */
6
- trialId: string;
7
- /** ISO-8601 trial expiration. */
8
- expiresAt: string;
9
- }
10
- /** Instrumentation-only receipt evidence returned by a future backend adapter. */
11
- export interface SetupBackendReceipt {
12
- /** Non-secret receipt identifier. */
13
- receiptId: string;
14
- /** Verified lowercase trace identifier; it does not establish content or Scenario suitability. */
15
- traceId: string;
16
- }
17
- /** @inline */
18
- interface SetupBackendClaimRequired {
19
- /** Claim is ready for a person. */
20
- status: "required";
21
- /** Non-secret claim identifier. */
22
- claimId: string;
23
- /** User-facing URL, which must never be checkpointed. */
24
- url: string;
25
- }
26
- /** @inline */
27
- interface SetupBackendClaimCompleted {
28
- /** Claim has been confirmed by the backend. */
29
- status: "completed";
30
- /** Non-secret claim identifier. */
31
- claimId: string;
32
- }
33
- /** Claim state returned by a future backend adapter. Claim URLs are never checkpointed. */
34
- export type SetupBackendClaim = SetupBackendClaimRequired | SetupBackendClaimCompleted;
35
- /** Installer-only network boundary for account attachment. It never creates a Scenario, worker, evaluation, or Hue Run. */
36
- export interface SetupBackendAdapter {
37
- /** Creates or idempotently recovers an anonymous trial hard-pinned to `trial_metadata_v1`. */
38
- createTrial(input: {
39
- runId: string;
40
- projectFingerprint: string;
41
- idempotencyKey: string;
42
- }, signal?: AbortSignal): Promise<SetupBackendTrial>;
43
- /**
44
- * Returns instrumentation-only receipt evidence, or `undefined` while pending.
45
- * A receipt never authorizes content capture or Scenario publication.
46
- */
47
- verifyReceipt(input: {
48
- trialId: string;
49
- idempotencyKey: string;
50
- }, signal?: AbortSignal): Promise<SetupBackendReceipt | undefined>;
51
- /** Reads account-claim state after receipt verification without opening the claim URL. */
52
- getClaim(input: {
53
- trialId: string;
54
- idempotencyKey: string;
55
- }, signal?: AbortSignal): Promise<SetupBackendClaim>;
56
- }
57
- /** Static project-inspection boundary. */
4
+ /** Detects the supported language and safe setup plan for a project root. */
58
5
  export interface SetupProjectAdapter {
59
- /** Inspects bounded metadata without executing project code. */
6
+ /** Inspects a project without executing its code. */
60
7
  detect(root: string, signal?: AbortSignal): Promise<SetupProjectDetection>;
61
8
  }
62
- /** Secret-free durable state boundary. */
9
+ /** Persists secret-free setup progress across interruptions. */
63
10
  export interface SetupCheckpointAdapter {
64
- /** Loads and validates state for this exact project identity. */
11
+ /** Reads the checkpoint for one setup run and project. */
65
12
  load(runId: string, projectRoot: string): Promise<SetupMachineState | undefined>;
66
- /** Durably saves secret-free state before the next effect. */
13
+ /** Atomically saves the latest setup state. */
67
14
  save(state: SetupMachineState): Promise<void>;
68
15
  }
69
- /** Options for one deterministic setup invocation. */
16
+ /** Backend operations consumed by the resumable setup runner. */
17
+ export type SetupBackendOperations = Pick<SetupBackendAdapter, "prepare" | "resetLocalCache" | "preflight" | "localInstallation" | "provision" | "status" | "credentials" | "installRuntime" | "configure" | "exerciseApplication" | "prepareClaimHandoff" | "verifyApplication" | "verifyRevokedCredential">;
18
+ /** Inputs for one invocation of the resumable setup runner. */
70
19
  export interface SetupRunOptions {
71
- /** CLI operation being orchestrated. */
20
+ /** Operation requested by the caller. */
72
21
  command: "setup" | "resume" | "status" | "claim";
73
- /** Renderer mode recorded in `run.started`. */
22
+ /** Output contract selected by the CLI. */
74
23
  mode: "human" | "plain" | "jsonl";
75
- /** Stable installer-session identifier, unrelated to Hue Runs. */
24
+ /** Stable identifier for this resumable invocation. */
76
25
  runId: string;
77
- /** Canonical project root. */
26
+ /** Absolute project directory being configured. */
78
27
  projectRoot: string;
79
- /** Injected static-inspection adapter. */
28
+ /** Safe project detector. */
80
29
  project: SetupProjectAdapter;
81
- /** Injected secret-free checkpoint adapter. */
30
+ /** Secret-free checkpoint persistence. */
82
31
  checkpoints: SetupCheckpointAdapter;
83
- /** Reserved injection point for the follow-up integration; intentionally unused by this slice. */
84
- backend?: SetupBackendAdapter;
85
- /** Receives each ordered event exactly once. */
32
+ /** Real protocol adapter; omitting it can only report that action is required. */
33
+ backend?: SetupBackendOperations;
34
+ /** Receives each ordered setup event. */
86
35
  emit(event: SetupEvent): void | Promise<void>;
87
- /** Injectable clock for deterministic tests. */
36
+ /** Injectable clock used by deterministic tests. */
88
37
  now?: () => Date;
89
- /** Optional cancellation signal. */
38
+ /** Cancels bounded local and network work. */
90
39
  signal?: AbortSignal;
40
+ /** Explicit owner request to replace the current browser handoff. Human claim commands only. */
41
+ claimRestart?: boolean;
91
42
  }
92
- /** Result of one setup invocation. */
43
+ /** Terminal outcome and latest resumable state from one setup invocation. */
93
44
  export interface SetupRunResult {
94
- /** Non-fabricated local outcome. */
45
+ /** Whether setup is verified, needs owner action, or made no change. */
95
46
  outcome: "ready" | "action_required" | "unchanged";
96
- /** Last validated state, when one exists. */
47
+ /** Latest checkpoint state when project detection has begun. */
97
48
  state?: SetupMachineState;
98
49
  }
99
- /** Runs one installer setup-session command and emits one terminal event; it never launches a Hue Run. */
50
+ /** Runs one resumable setup command and emits exactly one terminal event. */
100
51
  export declare function runSetup(options: SetupRunOptions): Promise<SetupRunResult>;
101
- export {};