replicas-cli 0.3.106 → 0.3.108

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 (2) hide show
  1. package/dist/index.cjs +146 -146
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -7567,9 +7567,6 @@ var import_open = __toESM(require("open"));
7567
7567
  function isRecord(value) {
7568
7568
  return typeof value === "object" && value !== null && !Array.isArray(value);
7569
7569
  }
7570
- function isNonEmptyString(value) {
7571
- return typeof value === "string" && value.trim().length > 0;
7572
- }
7573
7570
 
7574
7571
  // ../shared/src/json.ts
7575
7572
  function safeJsonParse(str, fallback) {
@@ -24359,7 +24356,6 @@ var ENVIRONMENT_SETUP_LIMITS = {
24359
24356
  mediaItems: 8,
24360
24357
  repositories: 20
24361
24358
  };
24362
- var ENVIRONMENT_SETUP_VARIABLE_WAIT = { defaultMs: 5 * 6e4, maxMs: 15 * 6e4, pollMs: 5e3 };
24363
24359
  var VARIABLE_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
24364
24360
  var text2 = (max) => max === void 0 ? external_exports.string().trim() : external_exports.string().trim().max(max);
24365
24361
  var requiredText = (max) => text2(max).min(1, "must not be empty");
@@ -24438,19 +24434,16 @@ function environmentSchema(limits) {
24438
24434
  warm_hook: clearableText(limits.hookChars)
24439
24435
  });
24440
24436
  }
24441
- function variableSchema(limits, requiredDefault) {
24442
- return external_exports.object({
24443
- key: external_exports.string().regex(VARIABLE_KEY_RE, "must be a valid environment variable name").max(limits.variableKeyChars ?? Number.POSITIVE_INFINITY),
24444
- description: optionalText2(limits.variableDescriptionChars).transform((value) => value ?? ""),
24445
- required: external_exports.boolean().default(requiredDefault)
24446
- });
24447
- }
24448
24437
  function proposalSchema(limits) {
24449
24438
  return external_exports.object({
24450
24439
  summary: requiredText(limits.summaryChars),
24451
24440
  stack: list(text2(), limits.stackItems).default([]).transform((items) => items.filter(Boolean)),
24452
24441
  environment: environmentSchema(limits),
24453
- variables: list(variableSchema(limits, false), limits.variables).default([]),
24442
+ variables: list(external_exports.object({
24443
+ key: external_exports.string().regex(VARIABLE_KEY_RE, "must be a valid environment variable name").max(limits.variableKeyChars ?? Number.POSITIVE_INFINITY),
24444
+ description: optionalText2(limits.variableDescriptionChars).transform((value) => value ?? ""),
24445
+ required: external_exports.boolean().default(false)
24446
+ }), limits.variables).default([]),
24454
24447
  automations: automationListSchema(limits),
24455
24448
  verification: list(external_exports.object({
24456
24449
  label: requiredText(limits.nameChars),
@@ -24465,11 +24458,6 @@ function proposalSchema(limits) {
24465
24458
  }
24466
24459
  var environmentSetupProposalSchema = proposalSchema({});
24467
24460
  var environmentSetupProposalSubmissionSchema = proposalSchema(ENVIRONMENT_SETUP_LIMITS);
24468
- var environmentSetupVariableRequestSchema = variableSchema({}, false).extend({
24469
- status: external_exports.enum(["pending", "provided", "skipped"]),
24470
- requested_at: external_exports.string(),
24471
- resolved_at: external_exports.string().nullable().default(null)
24472
- });
24473
24461
  var environmentSetupRunStateSchema = external_exports.object({
24474
24462
  status: external_exports.enum(ENVIRONMENT_SETUP_RUN_STATUSES),
24475
24463
  steps: external_exports.partialRecord(external_exports.enum(ENVIRONMENT_SETUP_STEPS.map((step) => step.key)), external_exports.enum(ENVIRONMENT_SETUP_STEP_STATUSES)).default({}),
@@ -24477,25 +24465,13 @@ var environmentSetupRunStateSchema = external_exports.object({
24477
24465
  applied: external_exports.object({
24478
24466
  applied_at: external_exports.string(),
24479
24467
  automation_ids: external_exports.array(external_exports.string())
24480
- }).nullable().default(null),
24481
- variable_requests: external_exports.array(environmentSetupVariableRequestSchema).default([])
24468
+ }).nullable().default(null)
24482
24469
  });
24483
24470
  var applyEnvironmentSetupRequestSchema = external_exports.object({
24484
24471
  environment: environmentSchema(ENVIRONMENT_SETUP_LIMITS).extend({ name: requiredText(ENVIRONMENT_SETUP_LIMITS.nameChars) }),
24485
24472
  variables: list(external_exports.object({ key: external_exports.string().regex(VARIABLE_KEY_RE), value: external_exports.string() }), ENVIRONMENT_SETUP_LIMITS.variables).default([]).refine((variables) => new Set(variables.map((variable) => variable.key)).size === variables.length, "must have unique keys").transform((variables) => variables.filter((variable) => variable.value.length > 0)),
24486
24473
  automations: automationListSchema(ENVIRONMENT_SETUP_LIMITS)
24487
24474
  });
24488
- var requestEnvironmentSetupVariablesSchema = external_exports.object({
24489
- variables: list(variableSchema(ENVIRONMENT_SETUP_LIMITS, true), ENVIRONMENT_SETUP_LIMITS.variables).min(1, "must not be empty")
24490
- }).superRefine(({ variables }, ctx) => {
24491
- if (new Set(variables.map((variable) => variable.key)).size !== variables.length) {
24492
- ctx.addIssue({ code: "custom", path: ["variables"], message: "must have unique keys" });
24493
- }
24494
- });
24495
- var respondEnvironmentSetupVariablesSchema = external_exports.object({
24496
- values: external_exports.record(external_exports.string().regex(VARIABLE_KEY_RE), external_exports.string()).default({}),
24497
- skipped: external_exports.array(external_exports.string().regex(VARIABLE_KEY_RE)).default([])
24498
- });
24499
24475
 
24500
24476
  // ../shared/src/default-skills/replicas-agent/abilities/environment-setup.ts
24501
24477
  var SECTION3 = `### Environment setup (agent-led)
@@ -24536,11 +24512,9 @@ When this workspace was started as a setup run, the dashboard shows a live check
24536
24512
  \`\`\`bash
24537
24513
  replicas environment-setup status
24538
24514
  replicas environment-setup step <key> <status>
24539
- replicas environment-setup request-variables -f <path>
24540
- replicas environment-setup wait-variables [-t 5m]
24541
24515
  \`\`\`
24542
24516
 
24543
- \`status\` prints the environment, repositories, current steps, and variable requests. \`request-variables\` returns immediately; \`wait-variables\` blocks until the required values arrive, are skipped, or the time runs out. Step statuses are \`pending\`, \`in_progress\`, \`completed\`, and \`skipped\`. Step keys, in order: \`understand\`, \`install\`, \`verify\`, \`configure\`, \`propose\`. Mark the first step \`in_progress\` before your first tool call, keep exactly one step \`in_progress\`, and mark a step \`completed\` only after its work actually succeeded. Use \`skipped\` when something genuinely cannot run (for example no browser UI to record) and say so in the proposal.
24517
+ \`status\` prints the environment, repositories, and current steps. Step statuses are \`pending\`, \`in_progress\`, \`completed\`, and \`skipped\`. Step keys, in order: \`understand\`, \`install\`, \`verify\`, \`configure\`, \`propose\`. Mark the first step \`in_progress\` before your first tool call, keep exactly one step \`in_progress\`, and mark a step \`completed\` only after its work actually succeeded. Use \`skipped\` when something genuinely cannot run (for example no browser UI to record) and say so in the proposal.
24544
24518
 
24545
24519
  If \`replicas environment-setup status\` reports that there is no active setup run, you are in an ordinary workspace, either because the user asked for this or because the fix you found while debugging belongs in the environment. Follow the same discovery and verification steps, show the exact change in chat, and offer to apply and test it yourself. Ask the user before changing the environment, since it affects every workspace built from it. Once they approve:
24546
24520
 
@@ -24566,34 +24540,18 @@ Use subagents for broad, independent analysis when available, but read the files
24566
24540
 
24567
24541
  ## Asking for variables
24568
24542
 
24569
- The user may be watching the panel or may have walked away, so ask early and wait late:
24570
-
24571
- 1. Right after step 1, request in one batch only the secrets you need to run the app and test its main flow (usually including sign-in) that you cannot replace locally. First check for a local stand-in the app supports, such as a local database, an auth emulator, or documented test credentials. Do not request values you can generate, values that duplicate another key, or values that are not secret, and list optional integrations in the proposal instead of requesting them. \`required\` means the app cannot start, or its main flow cannot be tested, without it.
24572
-
24573
- \`\`\`bash
24574
- replicas environment-setup request-variables --file /tmp/variables.json
24575
- \`\`\`
24576
-
24577
- \`\`\`json
24578
- {
24579
- "variables": [
24580
- { "key": "FIREBASE_PRIVATE_KEY", "description": "Service account private key for a dev Firebase project; sign-in cannot be tested without it", "required": true }
24581
- ]
24582
- }
24583
- \`\`\`
24543
+ The user may be watching or may have walked away, so ask early and wait late:
24584
24544
 
24585
- 2. Keep working on everything that does not need the values. The request appears in the Environment panel next to the chat; the user types values there (stored encrypted on the environment, never in chat) or skips them.
24586
- 3. At the point you actually need a value, say in chat which keys you are waiting for and why, then block on it once:
24545
+ 1. Right after step 1, ask in one command for only the secrets you need to run the app and test its main flow (usually including sign-in) that you cannot replace locally. First check for a local stand-in the app supports, such as a local database, an auth emulator, or documented test credentials. Do not request values you can generate, values that duplicate another key, or values that are not secret, and list optional integrations in the proposal instead of requesting them.
24587
24546
 
24588
24547
  \`\`\`bash
24589
- replicas environment-setup wait-variables --timeout 15m
24590
- replicas environment-setup wait-variables --keys FIREBASE_PRIVATE_KEY --timeout 15m
24548
+ replicas environment vars request FIREBASE_PRIVATE_KEY --reason "Service account key for a dev Firebase project, to test sign-in" --no-wait
24591
24549
  \`\`\`
24592
24550
 
24593
- Without \`--keys\` it waits for every pending required key. It returns when the keys are provided or skipped, or when the time runs out, and prints which happened. Provided keys are exported for every new shell; restart anything you started with \`replicas service start\` before that point so it picks them up. Never print the values.
24594
- 4. For anything skipped or still unanswered after that wait, use the safest local substitute (local database, mock, documented test value), say so in the verification rows, and list the key in the proposal, marked required only if the app cannot start or its main flow cannot be tested without it. A key the user skipped is not asked again; do not resend it.
24595
- 5. Values the user provided are already saved on the environment. Still list those keys in the proposal so the review shows them as set.
24596
- 6. Requests are only accepted before you propose. Anything still missing after that goes into the proposal for the user to fill in at review.
24551
+ 2. Keep working on everything that does not need the values. The form appears above the chat composer; the user pastes values there (saved encrypted on the environment, never in chat) or skips them.
24552
+ 3. At the point you actually need a value, say in chat which keys you are waiting for and why, then run the same command without \`--no-wait\`. It returns when the keys are provided or skipped, or when the time runs out (\`--timeout 15m\`), and loads provided values into \`~/.replicas/runtime-env.sh\`. Run commands as \`. ~/.replicas/runtime-env.sh && <command>\` and restart anything you started with \`replicas service start\` before that point. Never print the values.
24553
+ 4. For anything skipped or still unanswered, use the safest local substitute (local database, mock, documented test value), say so in the verification rows, and list the key in the proposal, marked required only if the app cannot start or its main flow cannot be tested without it. Do not ask again for a key the user skipped.
24554
+ 5. Values the user saved are already on the environment. Still list those keys in the proposal so the review shows them as set.
24597
24555
 
24598
24556
  ## 2. Install and run
24599
24557
 
@@ -24606,7 +24564,7 @@ Work from the smallest failing layer, in this workspace:
24606
24564
 
24607
24565
  Use the repository's pinned package manager and lockfile. Do not upgrade dependencies, rewrite lockfiles, or change application code to make setup pass; environment configuration and narrowly necessary fixes to setup scripts are in scope. Do not deploy, publish, or touch production resources.
24608
24566
 
24609
- When something needs a secret you do not have, request it and run \`wait-variables\` at the point you need it (see "Asking for variables"). If it is skipped or unanswered, use the safest local substitute, keep going, and record the variable in the proposal with a clear description. Never invent or paste secret values anywhere.
24567
+ When something needs a secret you do not have, request it and wait for it at the point you need it (see "Asking for variables"). If it is skipped or unanswered, use the safest local substitute, keep going, and record the variable in the proposal with a clear description. Never invent or paste secret values anywhere.
24610
24568
 
24611
24569
  ## 3. Verify and record
24612
24570
 
@@ -24744,7 +24702,7 @@ Keep it under 250 words plus the lists. Do not paste the hooks or system prompt
24744
24702
 
24745
24703
  ## Safety
24746
24704
 
24747
- - Never put tokens, passwords, or private keys in hooks, prompts, files, logs, or chat. Values that arrive through \`wait-variables\` are exported to your shell; never echo, cat, or paste them.
24705
+ - Never put tokens, passwords, or private keys in hooks, prompts, files, logs, or chat. Values that arrive through \`replicas environment vars request\` are written to \`~/.replicas/runtime-env.sh\`; never echo, cat, or paste them.
24748
24706
  - Do not weaken network, certificate, or package-integrity settings to make setup pass.
24749
24707
  - Do not run destructive git operations, force pushes, or anything against production endpoints.
24750
24708
  - Do not change application code to hide an environment problem. Report it instead.
@@ -25960,6 +25918,7 @@ In agent mode the CLI hides commands that don't make sense for in-workspace agen
25960
25918
  | --- | --- |
25961
25919
  | "What environments do I have?" | \`replicas environment list\` |
25962
25920
  | "Add API key X to my staging env" | \`replicas environment vars set <env> <KEY> <VALUE>\` |
25921
+ | You need a secret or API key you don't have | \`replicas environment vars request <KEY>... --reason "..."\` (see "Asking the user for variables") |
25963
25922
  | "Make me a \`.env\` file in the workspace with these vars" | \`replicas environment files set <env> .env --content "..."\` |
25964
25923
  | "Create a new env for my \`acme/web\` repo" | \`replicas environment create <name> -r acme/web\` |
25965
25924
  | "What automations do I have?" | \`replicas automation list\` |
@@ -26007,6 +25966,18 @@ replicas environment vars delete <env> <KEY|VARIABLE_ID> [--force]
26007
25966
 
26008
25967
  These variables are injected into every workspace built from this environment as actual \`env\` vars \u2014 perfect for API keys, feature flags, etc. Anything you want available to the agent's tools (Claude, codex, your code, etc.) goes here.
26009
25968
 
25969
+ ### Asking the user for variables
25970
+
25971
+ When you need a secret, API key, or other value you don't have, ask with one command. Never ask for values in chat, and never write instructions for adding them in the dashboard:
25972
+
25973
+ \`\`\`bash
25974
+ replicas environment vars request AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY --reason "Deploy the stack to AWS" [--optional AWS_REGION]
25975
+ \`\`\`
25976
+
25977
+ The user gets a form above the chat composer in Replicas, where they can paste a whole \`.env\` and choose to save the values to the environment or keep them in this workspace only. The command waits up to 10 minutes (\`--timeout\`), then writes the values to \`~/.replicas/runtime-env.sh\` and prints only the key names. Your current shell doesn't have them yet, so run commands as \`. ~/.replicas/runtime-env.sh && <command>\`, and restart anything you started earlier. Never print the values.
25978
+
25979
+ In workspaces started from Slack, Linear, or an automation, nobody may be watching Replicas, so the command returns right away with a link to the form; share it where you are talking with the user. If the values don't arrive in time, the request stays open. Either way you get a message when the user saves; run the same command again then. If a saved value is wrong, for example the service rejects it, add \`--replace\` to ask for a new one.
25980
+
26010
25981
  ### Environment files
26011
25982
 
26012
25983
  Files materialize on the workspace filesystem when a workspace starts, at the destination path you specify. Use this for things like \`.env\` files at the repo root, dotfiles in \`~\`, prompt files for agents, etc.
@@ -26166,8 +26137,7 @@ Repos are connected via the GitHub integration in the dashboard \u2014 not from
26166
26137
  var REPLICAS_ABILITY = {
26167
26138
  label: "Replicas",
26168
26139
  description: "Teach the agent about Replicas itself \u2014 automations, environments, the in-workspace CLI.",
26169
- // No bullet — help_instructions covers the `replicas` CLI surface in detail.
26170
- bullet: "",
26140
+ bullet: "- Getting a secret, API key, or token you don't have from the user: run `replicas environment vars request <KEY>...`, which shows them a form. Never ask for the value in chat or tell them to add it in settings.",
26171
26141
  section: SECTION11,
26172
26142
  referenceFile: { name: "REPLICAS.md", content: REFERENCE11 },
26173
26143
  locked: true
@@ -28760,7 +28730,7 @@ var headlessAgentRequestSchema = external_exports.object({
28760
28730
  });
28761
28731
 
28762
28732
  // ../shared/src/cli-version.ts
28763
- var CLI_VERSION = "0.3.106";
28733
+ var CLI_VERSION = "0.3.108";
28764
28734
 
28765
28735
  // ../shared/src/version.ts
28766
28736
  function compareVersions(v1, v2) {
@@ -29301,6 +29271,11 @@ var workspaceChangedEventSchema = external_exports.discriminatedUnion("type", [
29301
29271
  workspaceId: external_exports.string(),
29302
29272
  ts: external_exports.string()
29303
29273
  }),
29274
+ external_exports.object({
29275
+ type: external_exports.literal("variable-request.changed"),
29276
+ workspaceId: external_exports.string(),
29277
+ ts: external_exports.string()
29278
+ }),
29304
29279
  external_exports.object({
29305
29280
  type: external_exports.literal("pull-request.changed"),
29306
29281
  prUrl: external_exports.string(),
@@ -29515,6 +29490,29 @@ async function readSseStream(body, onEvent, shouldContinue = () => true) {
29515
29490
  }
29516
29491
  }
29517
29492
 
29493
+ // ../shared/src/routes/variable-requests.ts
29494
+ var VARIABLE_REQUEST_WAIT = { defaultMs: 10 * 6e4, maxMs: 30 * 6e4, pollMs: 3e3, waiterStaleMs: 15e3 };
29495
+ var VARIABLE_REQUEST_DESTINATIONS = ["environment", "workspace"];
29496
+ var requestedVariableSchema = external_exports.object({
29497
+ key: external_exports.string().regex(VARIABLE_KEY_RE, "must be a valid environment variable name").max(ENVIRONMENT_SETUP_LIMITS.variableKeyChars),
29498
+ required: external_exports.boolean(),
29499
+ replace: external_exports.boolean().optional()
29500
+ });
29501
+ var requestedVariablesSchema = external_exports.array(requestedVariableSchema).min(1, "must not be empty").max(ENVIRONMENT_SETUP_LIMITS.variables).refine((variables) => new Set(variables.map((variable) => variable.key)).size === variables.length, "must have unique keys");
29502
+ var createVariableRequestSchema = external_exports.object({
29503
+ variables: requestedVariablesSchema,
29504
+ reason: external_exports.string().trim().max(500).nullish().transform((value) => value || null),
29505
+ wait: external_exports.boolean().default(true)
29506
+ });
29507
+ var respondVariableRequestSchema = external_exports.discriminatedUnion("action", [
29508
+ external_exports.object({
29509
+ action: external_exports.literal("provide"),
29510
+ destination: external_exports.enum(VARIABLE_REQUEST_DESTINATIONS),
29511
+ values: external_exports.record(external_exports.string().regex(VARIABLE_KEY_RE), external_exports.string())
29512
+ }),
29513
+ external_exports.object({ action: external_exports.literal("skip") })
29514
+ ]);
29515
+
29518
29516
  // src/lib/auth.ts
29519
29517
  var import_supabase_js2 = require("@supabase/supabase-js");
29520
29518
 
@@ -31519,6 +31517,8 @@ var import_chalk21 = __toESM(require("chalk"));
31519
31517
 
31520
31518
  // src/commands/environment.ts
31521
31519
  var import_fs4 = __toESM(require("fs"));
31520
+ var import_os3 = __toESM(require("os"));
31521
+ var import_path5 = __toESM(require("path"));
31522
31522
  var import_chalk20 = __toESM(require("chalk"));
31523
31523
 
31524
31524
  // src/commands/repositories.ts
@@ -31813,6 +31813,88 @@ async function envVarsDeleteCommand(envIdOrName, keyOrId, options) {
31813
31813
  Deleted variable ${keyOrId}.
31814
31814
  `);
31815
31815
  }
31816
+ var RUNTIME_ENV_FILE = import_path5.default.join(import_os3.default.homedir(), ".replicas", "runtime-env.sh");
31817
+ async function envVarsRequestCommand(requiredKeys, options) {
31818
+ ensureOrgApiAuthenticated();
31819
+ const optional2 = new Set(options.optional.flatMap((value) => value.split(",")).map((key) => key.trim()).filter(Boolean));
31820
+ const keys = [.../* @__PURE__ */ new Set([...requiredKeys, ...optional2])];
31821
+ if (keys.length === 0) throw new Error("Name at least one variable to request");
31822
+ const invalid = keys.find((key) => !VARIABLE_KEY_RE.test(key));
31823
+ if (invalid) throw new Error(`${invalid} is not a valid environment variable name`);
31824
+ const timeout = options.timeout ? /^(\d+)(s|m)?$/.exec(options.timeout.trim()) : null;
31825
+ if (options.timeout && !timeout) throw new Error(`Invalid timeout "${options.timeout}". Use seconds or minutes, for example 90s or 10m`);
31826
+ const timeoutMs = timeout ? Math.min(Math.max(Number(timeout[1]) * (timeout[2] === "m" ? 6e4 : 1e3), VARIABLE_REQUEST_WAIT.pollMs), VARIABLE_REQUEST_WAIT.maxMs) : VARIABLE_REQUEST_WAIT.defaultMs;
31827
+ const readRuntimeEnv = () => import_fs4.default.existsSync(RUNTIME_ENV_FILE) ? import_fs4.default.readFileSync(RUNTIME_ENV_FILE, "utf8") : "";
31828
+ const local = parsePosixEnvFile(readRuntimeEnv());
31829
+ const available = options.replace ? [] : keys.filter((key) => Object.hasOwn(local, key) && local[key].length > 0);
31830
+ const wanted = keys.filter((key) => !available.includes(key));
31831
+ const loadHint = import_chalk20.default.gray(`
31832
+ Values are never printed. Load them into a command with: . ~/.replicas/runtime-env.sh && <command>
31833
+ `);
31834
+ if (wanted.length === 0) {
31835
+ output({ status: "provided", exported: available, missing: [] }, () => {
31836
+ console.log(`
31837
+ ${import_chalk20.default.green("Already available:")} ${available.join(", ")}`);
31838
+ console.log(loadHint);
31839
+ console.log(import_chalk20.default.gray(" If a value is wrong, run this again with --replace to ask the user for a new one.\n"));
31840
+ });
31841
+ return;
31842
+ }
31843
+ const created = await orgAuthenticatedFetch("/v1/variable-requests", {
31844
+ method: "POST",
31845
+ body: { reason: options.reason, variables: wanted.map((key) => ({ key, required: !optional2.has(key), ...options.replace ? { replace: true } : {} })), wait: options.wait !== false }
31846
+ });
31847
+ let { request } = created;
31848
+ if (request.status === "pending" && created.wait) {
31849
+ if (!isJsonMode()) console.log(import_chalk20.default.gray(`
31850
+ Asked the user for ${wanted.join(", ")} above the chat composer. Waiting up to ${timeoutMs >= 6e4 ? `${Math.round(timeoutMs / 6e4)} minute(s)` : `${timeoutMs / 1e3} seconds`}\u2026`));
31851
+ const deadline = Date.now() + timeoutMs;
31852
+ while (request.status === "pending" && Date.now() < deadline) {
31853
+ await new Promise((resolve3) => setTimeout(resolve3, VARIABLE_REQUEST_WAIT.pollMs));
31854
+ ({ request } = await orgAuthenticatedFetch(`/v1/variable-requests/${request.id}`));
31855
+ }
31856
+ if (request.status === "pending") ({ request } = await orgAuthenticatedFetch(`/v1/variable-requests/${request.id}?waiting=false`));
31857
+ }
31858
+ let exported = [];
31859
+ if (request.status === "provided") {
31860
+ const loaded = await orgAuthenticatedFetch(`/v1/variable-requests/${request.id}/load`, { method: "POST" });
31861
+ const variables = loaded.variables.filter((variable) => !variable.key.startsWith("REPLICAS_"));
31862
+ const existing = readRuntimeEnv();
31863
+ const env = { ...parsePosixEnvFile(existing), ...Object.fromEntries(variables.map((variable) => [variable.key, variable.value])) };
31864
+ import_fs4.default.mkdirSync(import_path5.default.dirname(RUNTIME_ENV_FILE), { recursive: true });
31865
+ import_fs4.default.writeFileSync(RUNTIME_ENV_FILE, serializePosixEnvFile(env, parsePosixEnvUnsetKeys(existing).filter((key) => !env[key])), { mode: 384 });
31866
+ import_fs4.default.chmodSync(RUNTIME_ENV_FILE, 384);
31867
+ exported = [...variables.map((variable) => variable.key), ...loaded.already_loaded];
31868
+ }
31869
+ if (request.status !== "pending") await orgAuthenticatedFetch(`/v1/variable-requests/${request.id}/ack`, { method: "POST" });
31870
+ const missing = wanted.filter((key) => !exported.includes(key));
31871
+ output({ status: request.status, request_id: request.id, destination: request.destination, url: created.url, exported: [...available, ...exported], missing }, () => {
31872
+ console.log("");
31873
+ if (exported.length > 0 || available.length > 0) {
31874
+ const where = request.destination === "workspace" ? "this workspace only" : "saved to the environment";
31875
+ console.log(` ${import_chalk20.default.green("Provided:")} ${[...available, ...exported].join(", ")} ${import_chalk20.default.gray(`(${where})`)}`);
31876
+ }
31877
+ if (request.status === "pending") {
31878
+ console.log(` ${import_chalk20.default.yellow("Still waiting for:")} ${missing.join(", ")}`);
31879
+ console.log(import_chalk20.default.gray(options.wait === false ? `
31880
+ Asked above the chat composer (${created.url}). Keep working, and run this command again when you need the values.
31881
+ ` : created.wait ? `
31882
+ The form stays open above the chat composer (${created.url}). End your turn and say what the values are for. When the user saves, you get a message; run this command again then.
31883
+ ` : `
31884
+ The user may not be watching Replicas. Send them this link where you are talking with them (the Slack thread, Linear issue, or pull request) and ask them to add the values there, never in chat: ${created.url}
31885
+ When they save, you get a message; run this command again then.
31886
+ `));
31887
+ return;
31888
+ }
31889
+ if (missing.length > 0) console.log(` ${import_chalk20.default.gray(request.status === "skipped" ? "Skipped by the user:" : "Not provided:")} ${missing.join(", ")}`);
31890
+ if (exported.length === 0) {
31891
+ console.log(import_chalk20.default.gray("\n Continue without them, or ask the user how they want to proceed.\n"));
31892
+ return;
31893
+ }
31894
+ console.log(loadHint);
31895
+ if (options.replace) console.log(import_chalk20.default.gray(" Leave out --replace from now on; running it again asks the user again.\n"));
31896
+ });
31897
+ }
31816
31898
  function printFile(f) {
31817
31899
  console.log(import_chalk20.default.white(` ${f.path}`));
31818
31900
  console.log(import_chalk20.default.gray(` ID: ${f.id}`));
@@ -32819,8 +32901,6 @@ async function pluginsDisconnectCommand(pluginId, options) {
32819
32901
 
32820
32902
  // src/commands/environment-setup.ts
32821
32903
  var import_fs6 = __toESM(require("fs"));
32822
- var import_os3 = __toESM(require("os"));
32823
- var import_path5 = __toESM(require("path"));
32824
32904
  var import_chalk25 = __toESM(require("chalk"));
32825
32905
  var STEP_KEYS = ENVIRONMENT_SETUP_STEPS.map((step) => step.key).join(", ");
32826
32906
  async function fetchCurrentRun() {
@@ -32845,10 +32925,6 @@ async function environmentSetupStatusCommand() {
32845
32925
  const marker = status === "completed" ? import_chalk25.default.green("\u2713") : status === "in_progress" ? import_chalk25.default.cyan("\u25B8") : status === "skipped" ? import_chalk25.default.gray("\u2013") : import_chalk25.default.gray("\u25CB");
32846
32926
  console.log(` ${marker} ${step.key.padEnd(11)} ${import_chalk25.default.gray(step.label)} ${import_chalk25.default.gray(`[${status}]`)}`);
32847
32927
  }
32848
- if (run2.variable_requests.length > 0) {
32849
- console.log("\n Variables:");
32850
- printVariableRequests(run2.variable_requests);
32851
- }
32852
32928
  console.log(run2.proposal ? import_chalk25.default.gray("\n A proposal has been submitted. Run `propose` again to replace it.\n") : import_chalk25.default.gray("\n No proposal yet. Submit one with `replicas environment-setup propose --file <path>`.\n"));
32853
32929
  });
32854
32930
  }
@@ -32894,83 +32970,6 @@ async function environmentSetupProposeCommand(options) {
32894
32970
  console.log(import_chalk25.default.gray("\n The user reviews and saves it from the Environment panel; nothing has been applied yet.\n"));
32895
32971
  });
32896
32972
  }
32897
- var RUNTIME_ENV_FILE = import_path5.default.join(import_os3.default.homedir(), ".replicas", "runtime-env.sh");
32898
- var EXPORTED_KEYS_FILE = import_path5.default.join(import_os3.default.homedir(), ".replicas", "environment-setup-exported-keys.json");
32899
- function printVariableRequests(requests) {
32900
- for (const request of requests) {
32901
- const marker = request.status === "provided" ? import_chalk25.default.green("\u2713") : request.status === "skipped" ? import_chalk25.default.gray("\u2013") : import_chalk25.default.cyan("\u2026");
32902
- console.log(` ${marker} ${request.key.padEnd(32)} ${import_chalk25.default.gray(`[${request.status}${request.required ? ", required" : ""}]`)}`);
32903
- }
32904
- }
32905
- async function environmentSetupRequestVariablesCommand(options) {
32906
- const request = readJsonFile(options.file, "variable request");
32907
- const { run: run2 } = await fetchCurrentRun();
32908
- const result = await orgAuthenticatedFetch(`/v1/environment-setups/${run2.id}/variable-requests`, {
32909
- method: "POST",
32910
- body: request
32911
- });
32912
- output(result, ({ run: { variable_requests } }) => {
32913
- console.log(import_chalk25.default.green("\n\u2713 Variables requested.\n"));
32914
- printVariableRequests(variable_requests);
32915
- console.log(import_chalk25.default.gray("\n The user sees this in the Environment panel. Keep working and run `replicas environment-setup wait-variables` when you need a value.\n"));
32916
- });
32917
- }
32918
- function parseWaitTimeoutMs(raw) {
32919
- if (!raw) return ENVIRONMENT_SETUP_VARIABLE_WAIT.defaultMs;
32920
- const match = /^(\d+)(s|m)?$/.exec(raw.trim());
32921
- if (!match) throw new Error(`Invalid timeout "${raw}". Use seconds or minutes, for example 90s or 5m`);
32922
- const ms = Number(match[1]) * (match[2] === "m" ? 6e4 : 1e3);
32923
- return Math.min(Math.max(ms, ENVIRONMENT_SETUP_VARIABLE_WAIT.pollMs), ENVIRONMENT_SETUP_VARIABLE_WAIT.maxMs);
32924
- }
32925
- function exportToRuntimeEnv(variables) {
32926
- import_fs6.default.mkdirSync(import_path5.default.dirname(RUNTIME_ENV_FILE), { recursive: true });
32927
- const existing = import_fs6.default.existsSync(RUNTIME_ENV_FILE) ? import_fs6.default.readFileSync(RUNTIME_ENV_FILE, "utf8") : "";
32928
- const exportedKeys = import_fs6.default.existsSync(EXPORTED_KEYS_FILE) ? JSON.parse(import_fs6.default.readFileSync(EXPORTED_KEYS_FILE, "utf8")) : [];
32929
- const owned = new Set(Array.isArray(exportedKeys) ? exportedKeys.filter(isNonEmptyString) : []);
32930
- const env = parsePosixEnvFile(existing);
32931
- for (const key of owned) delete env[key];
32932
- const exported = variables.filter((variable) => !variable.key.startsWith("REPLICAS_"));
32933
- for (const variable of exported) env[variable.key] = variable.value;
32934
- const unsetKeys = new Set([...parsePosixEnvUnsetKeys(existing), ...owned].filter((key) => !env[key]));
32935
- const content = serializePosixEnvFile(env, unsetKeys);
32936
- if (content !== existing) {
32937
- import_fs6.default.writeFileSync(RUNTIME_ENV_FILE, content, { mode: 384 });
32938
- import_fs6.default.chmodSync(RUNTIME_ENV_FILE, 384);
32939
- }
32940
- import_fs6.default.writeFileSync(EXPORTED_KEYS_FILE, JSON.stringify(exported.map((variable) => variable.key)), { mode: 384 });
32941
- return exported.map((variable) => variable.key);
32942
- }
32943
- var formatDuration = (ms) => ms >= 6e4 ? `${Math.round(ms / 6e4)} minute(s)` : `${Math.round(ms / 1e3)} seconds`;
32944
- async function environmentSetupWaitVariablesCommand(options) {
32945
- const timeoutMs = parseWaitTimeoutMs(options.timeout);
32946
- const wanted = options.keys ? new Set(options.keys.split(",").map((key) => key.trim()).filter(Boolean)) : null;
32947
- const blocking = (requests) => requests.filter((request) => request.status === "pending" && (wanted ? wanted.has(request.key) : request.required));
32948
- let { run: run2 } = await fetchCurrentRun();
32949
- const before = run2.variable_requests;
32950
- if (blocking(before).length > 0) {
32951
- console.log(import_chalk25.default.gray(`
32952
- Waiting up to ${formatDuration(timeoutMs)} for: ${blocking(before).map((request) => request.key).join(", ")}`));
32953
- const deadline = Date.now() + timeoutMs;
32954
- while (blocking(run2.variable_requests).length > 0 && Date.now() < deadline) {
32955
- await new Promise((resolve3) => setTimeout(resolve3, ENVIRONMENT_SETUP_VARIABLE_WAIT.pollMs));
32956
- run2 = (await fetchCurrentRun()).run;
32957
- }
32958
- }
32959
- const { variables } = await orgAuthenticatedFetch("/v1/environment-setups/current/variables");
32960
- const exported = exportToRuntimeEnv(variables);
32961
- const reserved = variables.map((variable) => variable.key).filter((key) => !exported.includes(key));
32962
- const skipped = run2.variable_requests.filter((request) => request.status === "skipped").map((request) => request.key);
32963
- const timedOut = blocking(run2.variable_requests).map((request) => request.key);
32964
- output({ exported, reserved, skipped, timed_out: timedOut }, () => {
32965
- console.log("");
32966
- if (exported.length > 0) console.log(` ${import_chalk25.default.green("Available in new shells:")} ${exported.join(", ")}`);
32967
- if (reserved.length > 0) console.log(` ${import_chalk25.default.yellow("Reserved by Replicas:")} ${reserved.join(", ")} (reserved names, not exported; rename these in the proposal)`);
32968
- if (skipped.length > 0) console.log(` ${import_chalk25.default.gray("Skipped by the user:")} ${skipped.join(", ")}`);
32969
- if (timedOut.length > 0) console.log(` ${import_chalk25.default.yellow("Not answered in time:")} ${timedOut.join(", ")}`);
32970
- if (variables.length === 0 && skipped.length === 0 && timedOut.length === 0) console.log(import_chalk25.default.gray(" No variables were requested."));
32971
- console.log(import_chalk25.default.gray("\n Values are never printed. Restart services you started before this command so they pick the variables up. Use a local substitute for anything skipped or unanswered and list it as required in the proposal.\n"));
32972
- });
32973
- }
32974
32973
 
32975
32974
  // src/commands/automation.ts
32976
32975
  var import_chalk26 = __toESM(require("chalk"));
@@ -34913,6 +34912,9 @@ var envVars = environment.command("vars").description("Manage environment variab
34913
34912
  envVars.command("list <env>").description("List variables in an environment (values masked by default)").option("--reveal", "Show full variable values instead of masking them").action(run(envVarsListCommand));
34914
34913
  envVars.command("set <env> <key> <value>").description("Create or update a variable").action(run(envVarsSetCommand));
34915
34914
  envVars.command("delete <env> <key-or-id>").description("Delete a variable by key or ID").option("-f, --force", "Skip confirmation prompt").action(run(envVarsDeleteCommand));
34915
+ if (isAgentMode()) {
34916
+ envVars.command("request [keys...]").description("Ask the user for variables and load them into this workspace").option("-r, --reason <text>", "One sentence on what the values are for, shown to the user").option("--optional <keys>", "Comma-separated keys to ask for that the user may leave empty (repeatable)", (value, previous) => [...previous, value], []).option("--replace", "Ask for new values even if the keys are already set, for example when a saved key is rejected").option("-t, --timeout <duration>", "How long to wait for the user, for example 90s or 10m (default 10m)").option("--no-wait", "Return right after asking instead of waiting for the user").action(run(envVarsRequestCommand));
34917
+ }
34916
34918
  var envFiles = environment.command("files").description("Manage environment files (mounted into workspaces at workspace start)");
34917
34919
  envFiles.command("list <env>").description("List files in an environment").action(run(envFilesListCommand));
34918
34920
  envFiles.command("set <env> <destination-path>").description("Create or update a file at <destination-path> inside the workspace").option("-c, --content <content>", "Inline file content").option("-f, --file <local-path>", "Read content from a local file").option("-n, --name <name>", "Display name (defaults to basename of destination path)").action(run(envFilesSetCommand));
@@ -34969,8 +34971,6 @@ if (isEnvironmentSetupWorkspace()) {
34969
34971
  environmentSetup.command("status").description("Show the setup run attached to this workspace").action(run(environmentSetupStatusCommand));
34970
34972
  environmentSetup.command("step <key> <status>").description("Update a checklist step (understand | install | verify | configure | propose) to pending | in_progress | completed | skipped").action(run(environmentSetupStepCommand));
34971
34973
  environmentSetup.command("propose").description("Submit the proposed configuration for the user to review and save").requiredOption("-f, --file <path>", "Path to the proposal JSON file").action(run(environmentSetupProposeCommand));
34972
- environmentSetup.command("request-variables").description("Ask the user for variables the app needs; returns immediately so you can keep working").requiredOption("-f, --file <path>", 'Path to a JSON file: { "variables": [{ "key", "description", "required" }] }').action(run(environmentSetupRequestVariablesCommand));
34973
- environmentSetup.command("wait-variables").description("Wait until requested required variables are provided or skipped, then export them for new shells (default 5m)").option("-t, --timeout <duration>", "How long to wait, e.g. 90s or 5m (max 15m)").option("-k, --keys <keys>", "Comma-separated keys to wait for instead of every pending required key").action(run(environmentSetupWaitVariablesCommand));
34974
34974
  }
34975
34975
  if (isAgentMode()) {
34976
34976
  const identity = program.command("identity").description("Obtain short-lived workspace identity tokens (OIDC) for the services this workspace calls");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-cli",
3
- "version": "0.3.106",
3
+ "version": "0.3.108",
4
4
  "description": "CLI for managing Replicas workspaces - SSH into cloud dev environments with automatic port forwarding",
5
5
  "main": "dist/index.cjs",
6
6
  "bin": {