replicas-cli 0.2.757 → 0.2.760

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 +77 -68
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -22927,12 +22927,14 @@ function coerceClaudePartialMessagePayload(payload) {
22927
22927
  const parentToolUseId = payload.parent_tool_use_id;
22928
22928
  const text2 = typeof payload.text === "string" && payload.text ? payload.text : void 0;
22929
22929
  const thinking = typeof payload.thinking === "string" && payload.thinking ? payload.thinking : void 0;
22930
- if (!text2 && !thinking) return null;
22930
+ const tool = isRecord(payload.tool) && typeof payload.tool.id === "string" && typeof payload.tool.name === "string" ? typeof payload.tool.filePath === "string" ? { id: payload.tool.id, name: payload.tool.name, filePath: payload.tool.filePath } : typeof payload.tool.command === "string" ? { id: payload.tool.id, name: payload.tool.name, command: payload.tool.command } : void 0 : void 0;
22931
+ if (!text2 && !thinking && !tool) return null;
22931
22932
  return {
22932
22933
  streamId,
22933
22934
  parent_tool_use_id: typeof parentToolUseId === "string" ? parentToolUseId : null,
22934
22935
  ...text2 ? { text: text2 } : {},
22935
22936
  ...thinking ? { thinking } : {},
22937
+ ...tool ? { tool } : {},
22936
22938
  status: payload.status === "completed" ? "completed" : "in_progress"
22937
22939
  };
22938
22940
  }
@@ -24152,7 +24154,7 @@ Use subagents for broad, independent analysis when available, but read the files
24152
24154
 
24153
24155
  The user may be watching the panel or may have walked away, so ask early and wait late:
24154
24156
 
24155
- 1. Right after step 1, request every secret you found in one batch. \`required\` means the app cannot run locally without it; leave it \`false\` for anything you can work around.
24157
+ 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.
24156
24158
 
24157
24159
  \`\`\`bash
24158
24160
  replicas environment-setup request-variables --file /tmp/variables.json
@@ -24161,24 +24163,23 @@ replicas environment-setup request-variables --file /tmp/variables.json
24161
24163
  \`\`\`json
24162
24164
  {
24163
24165
  "variables": [
24164
- { "key": "DATABASE_URL", "description": "Postgres connection string; a local docker compose database works", "required": false },
24165
- { "key": "STRIPE_SECRET_KEY", "description": "Stripe test-mode key; checkout cannot run without it", "required": true }
24166
+ { "key": "FIREBASE_PRIVATE_KEY", "description": "Service account private key for a dev Firebase project; sign-in cannot be tested without it", "required": true }
24166
24167
  ]
24167
24168
  }
24168
24169
  \`\`\`
24169
24170
 
24170
24171
  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.
24171
- 3. At the point you actually need a value, block on it:
24172
+ 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:
24172
24173
 
24173
24174
  \`\`\`bash
24174
- replicas environment-setup wait-variables --timeout 5m
24175
- replicas environment-setup wait-variables --keys STRIPE_SECRET_KEY
24175
+ replicas environment-setup wait-variables --timeout 15m
24176
+ replicas environment-setup wait-variables --keys FIREBASE_PRIVATE_KEY --timeout 15m
24176
24177
  \`\`\`
24177
24178
 
24178
24179
  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.
24179
- 4. For anything skipped or unanswered, use the safest local substitute (local database, mock, documented test value), say so in the verification rows, and list the key as required in the proposal. A key the user skipped is not asked again; do not resend it.
24180
+ 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.
24180
24181
  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.
24181
- 6. Requests are only accepted before you propose. Anything still missing after that goes into the proposal as a required variable, which the user fills in at review.
24182
+ 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.
24182
24183
 
24183
24184
  ## 2. Install and run
24184
24185
 
@@ -24191,7 +24192,7 @@ Work from the smallest failing layer, in this workspace:
24191
24192
 
24192
24193
  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.
24193
24194
 
24194
- 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 as required in the proposal with a clear description. Never invent or paste secret values anywhere.
24195
+ 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.
24195
24196
 
24196
24197
  ## 3. Verify and record
24197
24198
 
@@ -25405,7 +25406,7 @@ In agent mode the CLI hides commands that don't make sense for in-workspace agen
25405
25406
  | \`replicas whoami\` | Print the workspace + org identity |
25406
25407
  | \`replicas identity token [--audience <aud>]\` | Print the current short-lived workspace identity token for an audience |
25407
25408
  | \`replicas identity token --format gcp --audience <aud>\` | Supply a current token to Google's executable credential source |
25408
- | \`replicas identity aws-credentials --role-arn <arn> [--region <region>]\` | Exchange the current token for AWS credential_process JSON (requires AWS CLI) |
25409
+ | \`replicas identity aws-credentials --role-arn <arn> [--region <region>] [--source-identity]\` | Exchange the current token for AWS credential_process JSON (requires AWS CLI); \`--source-identity\` records the workspace ID in CloudTrail and needs \`sts:SetSourceIdentity\` in the role trust policy |
25409
25410
  | \`replicas identity exec [--audience <aud>] -- <command>\` | Run a command with the current token and audience in its environment |
25410
25411
  | \`replicas init\` | Create a \`replicas.json\` / \`replicas.yaml\` in the current directory |
25411
25412
  | \`replicas connect <name>\` | SSH into another workspace (requires user creds \u2014 usually only useful when scripting locally) |
@@ -27063,6 +27064,51 @@ function parseMcpToolName(name) {
27063
27064
 
27064
27065
  // ../shared/src/display-message/parsers/claude-parser.ts
27065
27066
  var ABORTED_TERMINAL_REASONS = /* @__PURE__ */ new Set(["aborted_streaming", "aborted_tools"]);
27067
+ var COMMAND_TOOL_NAMES = /* @__PURE__ */ new Set(["Bash", "bash", "shell"]);
27068
+ var FILE_TOOL_NAMES = /* @__PURE__ */ new Set(["Write", "Edit"]);
27069
+ function fileOrCommandMessage(toolName, toolUseId, input, timestamp) {
27070
+ if (COMMAND_TOOL_NAMES.has(toolName)) {
27071
+ const command = typeof input.command === "string" ? input.command : "";
27072
+ const skillNames = skillNamesFromCommand(command);
27073
+ return {
27074
+ id: `command-${toolUseId}`,
27075
+ type: "command",
27076
+ command,
27077
+ ...skillNames.length > 0 ? { skillNames } : {},
27078
+ output: "",
27079
+ status: "in_progress",
27080
+ timestamp
27081
+ };
27082
+ }
27083
+ const filePath = typeof input.file_path === "string" ? input.file_path : "";
27084
+ let diff;
27085
+ if (toolName === "Edit" && typeof input.old_string === "string" && typeof input.new_string === "string") {
27086
+ const oldLines = input.old_string.replace(/\n$/, "").split("\n");
27087
+ const newLines = input.new_string.replace(/\n$/, "").split("\n");
27088
+ diff = [
27089
+ `--- a/${filePath}`,
27090
+ `+++ b/${filePath}`,
27091
+ `@@ -1,${oldLines.length} +1,${newLines.length} @@`,
27092
+ ...oldLines.map((l) => `-${l}`),
27093
+ ...newLines.map((l) => `+${l}`)
27094
+ ].join("\n");
27095
+ } else if (toolName === "Write" && typeof input.content === "string") {
27096
+ const newLines = input.content.replace(/\n$/, "").split("\n");
27097
+ diff = [
27098
+ `--- /dev/null`,
27099
+ `+++ b/${filePath}`,
27100
+ `@@ -0,0 +1,${newLines.length} @@`,
27101
+ ...newLines.map((l) => `+${l}`)
27102
+ ].join("\n");
27103
+ }
27104
+ return {
27105
+ id: `patch-${toolUseId}`,
27106
+ type: "patch",
27107
+ operations: [{ action: toolName === "Write" ? "add" : "update", path: filePath, diff }],
27108
+ status: "in_progress",
27109
+ timestamp
27110
+ };
27111
+ }
27066
27112
  function createSubagentMessage(messageIndex, timestamp, fields) {
27067
27113
  const { id = `subagent-${timestamp}-${messageIndex}`, ...message } = fields;
27068
27114
  return { id, type: "subagent", ...message, timestamp };
@@ -27099,6 +27145,7 @@ function parseClaudeEvents(events, parentToolUseId) {
27099
27145
  const workflowAgentMessageMap = /* @__PURE__ */ new Map();
27100
27146
  const ambientTaskIds = /* @__PURE__ */ new Set();
27101
27147
  const partialIndexes = /* @__PURE__ */ new Map();
27148
+ const streamingTools = /* @__PURE__ */ new Map();
27102
27149
  const completedStreamIds = /* @__PURE__ */ new Set();
27103
27150
  const supersededStreamIds = /* @__PURE__ */ new Set();
27104
27151
  let liveStreamId = null;
@@ -27176,6 +27223,7 @@ function parseClaudeEvents(events, parentToolUseId) {
27176
27223
  if (existing.agent !== void 0) messages[existing.agent] = agentMessage;
27177
27224
  else existing.agent = messages.push(agentMessage) - 1;
27178
27225
  }
27226
+ if (payload.tool) streamingTools.set(payload.streamId, { tool: payload.tool, timestamp: event.timestamp });
27179
27227
  partialIndexes.set(payload.streamId, existing);
27180
27228
  return;
27181
27229
  }
@@ -27302,56 +27350,9 @@ function parseClaudeEvents(events, parentToolUseId) {
27302
27350
  status: "in_progress",
27303
27351
  nestedEvents
27304
27352
  }));
27305
- } else if (toolName === "Bash" || toolName === "bash" || toolName === "shell") {
27353
+ } else if (COMMAND_TOOL_NAMES.has(toolName) || FILE_TOOL_NAMES.has(toolName)) {
27306
27354
  const inputObj = typeof toolInput === "string" ? safeJsonParse(toolInput, {}) : toolInput;
27307
- const command = inputObj.command || "";
27308
- const skillNames = skillNamesFromCommand(command);
27309
- messages.push({
27310
- id: `command-${event.timestamp}-${messages.length}`,
27311
- type: "command",
27312
- command,
27313
- ...skillNames.length > 0 ? { skillNames } : {},
27314
- output: "",
27315
- status: "in_progress",
27316
- timestamp: event.timestamp
27317
- });
27318
- } else if (toolName === "Write" || toolName === "Edit") {
27319
- const inputObj = typeof toolInput === "string" ? safeJsonParse(toolInput, {}) : toolInput;
27320
- const filePath = inputObj.file_path || "";
27321
- const action = toolName === "Write" ? "add" : "update";
27322
- let diff;
27323
- if (toolName === "Edit" && inputObj.old_string != null && inputObj.new_string != null) {
27324
- const oldLines = inputObj.old_string.replace(/\n$/, "").split("\n");
27325
- const newLines = inputObj.new_string.replace(/\n$/, "").split("\n");
27326
- const hunk = `@@ -1,${oldLines.length} +1,${newLines.length} @@`;
27327
- diff = [
27328
- `--- a/${filePath}`,
27329
- `+++ b/${filePath}`,
27330
- hunk,
27331
- ...oldLines.map((l) => `-${l}`),
27332
- ...newLines.map((l) => `+${l}`)
27333
- ].join("\n");
27334
- } else if (toolName === "Write" && inputObj.content != null) {
27335
- const newLines = inputObj.content.replace(/\n$/, "").split("\n");
27336
- const hunk = `@@ -0,0 +1,${newLines.length} @@`;
27337
- diff = [
27338
- `--- /dev/null`,
27339
- `+++ b/${filePath}`,
27340
- hunk,
27341
- ...newLines.map((l) => `+${l}`)
27342
- ].join("\n");
27343
- }
27344
- messages.push({
27345
- id: `patch-${event.timestamp}-${messages.length}`,
27346
- type: "patch",
27347
- operations: [{
27348
- action,
27349
- path: filePath,
27350
- diff
27351
- }],
27352
- status: "in_progress",
27353
- timestamp: event.timestamp
27354
- });
27355
+ messages.push(fileOrCommandMessage(toolName, toolUseId, inputObj, event.timestamp));
27355
27356
  } else if (toolName === "TaskCreate" || toolName === "TaskUpdate" || toolName === "TaskList") {
27356
27357
  taskAccumulator.processContentBlock(block);
27357
27358
  const snapshot = taskSnapshot();
@@ -27531,6 +27532,11 @@ function parseClaudeEvents(events, parentToolUseId) {
27531
27532
  };
27532
27533
  }
27533
27534
  });
27535
+ for (const [streamId, { tool, timestamp }] of streamingTools) {
27536
+ if (toolCallMap.has(tool.id) || supersededStreamIds.has(streamId)) continue;
27537
+ if (!COMMAND_TOOL_NAMES.has(tool.name) && !FILE_TOOL_NAMES.has(tool.name)) continue;
27538
+ messages.push(fileOrCommandMessage(tool.name, tool.id, "filePath" in tool ? { file_path: tool.filePath } : { command: tool.command }, timestamp));
27539
+ }
27534
27540
  filteredEvents.forEach((event) => {
27535
27541
  if (event.type === "claude-user") {
27536
27542
  const content = normalizeContentBlocks(event.payload.message?.content);
@@ -28012,7 +28018,7 @@ var headlessFilesystemAgentResultSchema = external_exports.object({
28012
28018
  });
28013
28019
 
28014
28020
  // ../shared/src/cli-version.ts
28015
- var CLI_VERSION = "0.2.757";
28021
+ var CLI_VERSION = "0.2.760";
28016
28022
 
28017
28023
  // ../shared/src/version.ts
28018
28024
  function compareVersions(v1, v2) {
@@ -28703,6 +28709,7 @@ async function readSseStream(body, onEvent, shouldContinue = () => true) {
28703
28709
  var import_yaml2 = __toESM(require_dist());
28704
28710
 
28705
28711
  // ../shared/src/workspace-identity.ts
28712
+ var AWS_SOURCE_IDENTITY_CLAIM = "https://aws.amazon.com/source_identity";
28706
28713
  var WORKSPACE_TOKEN_TTL_SECONDS = 15 * 60;
28707
28714
  var IDENTITY_PROXY_PORT = 17323;
28708
28715
  var IDENTITY_TOKEN_PATH = "/token";
@@ -28712,7 +28719,8 @@ var WORKSPACE_TOKEN_ENV = {
28712
28719
  };
28713
28720
  var workspaceAudienceSchema = external_exports.string().min(1).max(255).regex(/^[\x21-\x7e]+$/, "Audience must be a single printable token without spaces");
28714
28721
  var workspaceTokenRequestSchema = external_exports.object({
28715
- audience: workspaceAudienceSchema.optional()
28722
+ audience: workspaceAudienceSchema.optional(),
28723
+ aws_source_identity: external_exports.boolean().optional()
28716
28724
  });
28717
28725
  var workspaceTokenClaimsSchema = external_exports.object({
28718
28726
  iss: external_exports.string(),
@@ -28726,7 +28734,8 @@ var workspaceTokenClaimsSchema = external_exports.object({
28726
28734
  workspace_id: external_exports.string().uuid(),
28727
28735
  environment_id: external_exports.string().uuid(),
28728
28736
  creator_id: external_exports.string().uuid().nullable(),
28729
- creator_email: external_exports.string().nullable().default(null)
28737
+ creator_email: external_exports.string().nullable().default(null),
28738
+ [AWS_SOURCE_IDENTITY_CLAIM]: external_exports.string().uuid().optional()
28730
28739
  }).passthrough();
28731
28740
  var workspaceTokenResponseSchema = external_exports.object({
28732
28741
  issuer: external_exports.string(),
@@ -29250,13 +29259,13 @@ var import_node_child_process = require("child_process");
29250
29259
  var import_node_os = require("os");
29251
29260
  var import_node_path = require("path");
29252
29261
  var import_chalk5 = __toESM(require("chalk"));
29253
- async function fetchToken(audience) {
29262
+ async function fetchToken(audience, awsSourceIdentity) {
29254
29263
  let response;
29255
29264
  try {
29256
29265
  response = await fetch(identityTokenUrl(), {
29257
29266
  method: "POST",
29258
29267
  headers: { "content-type": "application/json" },
29259
- body: JSON.stringify(audience ? { audience } : {})
29268
+ body: JSON.stringify({ audience: audience || void 0, aws_source_identity: awsSourceIdentity || void 0 })
29260
29269
  });
29261
29270
  } catch {
29262
29271
  return createErrorResult({ message: "Workspace identity is only available inside a running Replicas workspace." });
@@ -29311,7 +29320,7 @@ async function identityExecCommand(command, options) {
29311
29320
  process.exit(result.status ?? 1);
29312
29321
  }
29313
29322
  async function identityAwsCredentialsCommand(options) {
29314
- const key = (0, import_node_crypto.createHash)("sha256").update(JSON.stringify([options.roleArn, options.audience, options.region, options.sessionName])).digest("hex");
29323
+ const key = (0, import_node_crypto.createHash)("sha256").update(JSON.stringify([options.roleArn, options.audience, options.region, options.sessionName, options.sourceIdentity])).digest("hex");
29315
29324
  const cacheFile = (0, import_node_path.join)((0, import_node_os.homedir)(), ".replicas", "aws-credentials", `${key}.json`);
29316
29325
  try {
29317
29326
  const cached2 = (0, import_node_fs.readFileSync)(cacheFile, "utf8");
@@ -29321,7 +29330,7 @@ async function identityAwsCredentialsCommand(options) {
29321
29330
  }
29322
29331
  } catch {
29323
29332
  }
29324
- const result = await fetchToken(options.audience);
29333
+ const result = await fetchToken(options.audience, options.sourceIdentity);
29325
29334
  if (!result.ok) {
29326
29335
  console.error(import_chalk5.default.red(result.error.message));
29327
29336
  process.exitCode = 1;
@@ -33899,7 +33908,7 @@ if (isEnvironmentSetupWorkspace()) {
33899
33908
  if (isAgentMode()) {
33900
33909
  const identity = program.command("identity").description("Obtain short-lived workspace identity tokens (OIDC) for the services this workspace calls");
33901
33910
  identity.command("token").description("Print a workspace token for an audience (defaults to replicas:workspace)").option("-a, --audience <audience>", "Audience (`aud`) the receiving service verifies, for example its URL").addOption(new import_commander.Option("--format <format>", "Output a raw token or Google executable credentials").choices(["text", "gcp"]).default("text")).action(identityTokenCommand);
33902
- identity.command("aws-credentials").description("Exchange the current token for temporary AWS credentials (for AWS credential_process)").requiredOption("--role-arn <arn>", "IAM role that trusts the Replicas issuer").option("-a, --audience <audience>", "Audience configured on the IAM OIDC provider", "sts.amazonaws.com").option("--region <region>", "AWS region for the STS exchange").option("--session-name <name>", "AWS role session name", "replicas").action(identityAwsCredentialsCommand);
33911
+ identity.command("aws-credentials").description("Exchange the current token for temporary AWS credentials (for AWS credential_process)").requiredOption("--role-arn <arn>", "IAM role that trusts the Replicas issuer").option("-a, --audience <audience>", "Audience configured on the IAM OIDC provider", "sts.amazonaws.com").option("--region <region>", "AWS region for the STS exchange").option("--session-name <name>", "AWS role session name", "replicas").option("--source-identity", "Record the workspace ID as the AWS source identity (role trust policy must allow sts:SetSourceIdentity)").action(identityAwsCredentialsCommand);
33903
33912
  identity.command("exec [command...]").description("Run a command with REPLICAS_WORKSPACE_TOKEN set to a fresh token: replicas identity exec -- curl ...").option("-a, --audience <audience>", "Audience (`aud`) the receiving service verifies").action((command, options) => identityExecCommand(command, options));
33904
33913
  const service = program.command("service").description("Run long-lived services (dev servers, daemons) detached from the agent session so they survive workspace sleep/wake");
33905
33914
  service.command("start <name> [command...]").description('Start (or restart) a named service as a detached daemon. Quote shell operators: replicas service start web "cd app && bun dev"').option("-d, --cwd <dir>", "Working directory for the service (defaults to the current directory)").action(async (name, commandParts, options) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-cli",
3
- "version": "0.2.757",
3
+ "version": "0.2.760",
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": {