replicas-cli 0.2.758 → 0.2.761

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 +69 -59
  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
  }
@@ -25404,7 +25406,7 @@ In agent mode the CLI hides commands that don't make sense for in-workspace agen
25404
25406
  | \`replicas whoami\` | Print the workspace + org identity |
25405
25407
  | \`replicas identity token [--audience <aud>]\` | Print the current short-lived workspace identity token for an audience |
25406
25408
  | \`replicas identity token --format gcp --audience <aud>\` | Supply a current token to Google's executable credential source |
25407
- | \`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 |
25408
25410
  | \`replicas identity exec [--audience <aud>] -- <command>\` | Run a command with the current token and audience in its environment |
25409
25411
  | \`replicas init\` | Create a \`replicas.json\` / \`replicas.yaml\` in the current directory |
25410
25412
  | \`replicas connect <name>\` | SSH into another workspace (requires user creds \u2014 usually only useful when scripting locally) |
@@ -27062,6 +27064,51 @@ function parseMcpToolName(name) {
27062
27064
 
27063
27065
  // ../shared/src/display-message/parsers/claude-parser.ts
27064
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
+ }
27065
27112
  function createSubagentMessage(messageIndex, timestamp, fields) {
27066
27113
  const { id = `subagent-${timestamp}-${messageIndex}`, ...message } = fields;
27067
27114
  return { id, type: "subagent", ...message, timestamp };
@@ -27098,6 +27145,7 @@ function parseClaudeEvents(events, parentToolUseId) {
27098
27145
  const workflowAgentMessageMap = /* @__PURE__ */ new Map();
27099
27146
  const ambientTaskIds = /* @__PURE__ */ new Set();
27100
27147
  const partialIndexes = /* @__PURE__ */ new Map();
27148
+ const streamingTools = /* @__PURE__ */ new Map();
27101
27149
  const completedStreamIds = /* @__PURE__ */ new Set();
27102
27150
  const supersededStreamIds = /* @__PURE__ */ new Set();
27103
27151
  let liveStreamId = null;
@@ -27175,6 +27223,7 @@ function parseClaudeEvents(events, parentToolUseId) {
27175
27223
  if (existing.agent !== void 0) messages[existing.agent] = agentMessage;
27176
27224
  else existing.agent = messages.push(agentMessage) - 1;
27177
27225
  }
27226
+ if (payload.tool) streamingTools.set(payload.streamId, { tool: payload.tool, timestamp: event.timestamp });
27178
27227
  partialIndexes.set(payload.streamId, existing);
27179
27228
  return;
27180
27229
  }
@@ -27301,56 +27350,9 @@ function parseClaudeEvents(events, parentToolUseId) {
27301
27350
  status: "in_progress",
27302
27351
  nestedEvents
27303
27352
  }));
27304
- } else if (toolName === "Bash" || toolName === "bash" || toolName === "shell") {
27353
+ } else if (COMMAND_TOOL_NAMES.has(toolName) || FILE_TOOL_NAMES.has(toolName)) {
27305
27354
  const inputObj = typeof toolInput === "string" ? safeJsonParse(toolInput, {}) : toolInput;
27306
- const command = inputObj.command || "";
27307
- const skillNames = skillNamesFromCommand(command);
27308
- messages.push({
27309
- id: `command-${event.timestamp}-${messages.length}`,
27310
- type: "command",
27311
- command,
27312
- ...skillNames.length > 0 ? { skillNames } : {},
27313
- output: "",
27314
- status: "in_progress",
27315
- timestamp: event.timestamp
27316
- });
27317
- } else if (toolName === "Write" || toolName === "Edit") {
27318
- const inputObj = typeof toolInput === "string" ? safeJsonParse(toolInput, {}) : toolInput;
27319
- const filePath = inputObj.file_path || "";
27320
- const action = toolName === "Write" ? "add" : "update";
27321
- let diff;
27322
- if (toolName === "Edit" && inputObj.old_string != null && inputObj.new_string != null) {
27323
- const oldLines = inputObj.old_string.replace(/\n$/, "").split("\n");
27324
- const newLines = inputObj.new_string.replace(/\n$/, "").split("\n");
27325
- const hunk = `@@ -1,${oldLines.length} +1,${newLines.length} @@`;
27326
- diff = [
27327
- `--- a/${filePath}`,
27328
- `+++ b/${filePath}`,
27329
- hunk,
27330
- ...oldLines.map((l) => `-${l}`),
27331
- ...newLines.map((l) => `+${l}`)
27332
- ].join("\n");
27333
- } else if (toolName === "Write" && inputObj.content != null) {
27334
- const newLines = inputObj.content.replace(/\n$/, "").split("\n");
27335
- const hunk = `@@ -0,0 +1,${newLines.length} @@`;
27336
- diff = [
27337
- `--- /dev/null`,
27338
- `+++ b/${filePath}`,
27339
- hunk,
27340
- ...newLines.map((l) => `+${l}`)
27341
- ].join("\n");
27342
- }
27343
- messages.push({
27344
- id: `patch-${event.timestamp}-${messages.length}`,
27345
- type: "patch",
27346
- operations: [{
27347
- action,
27348
- path: filePath,
27349
- diff
27350
- }],
27351
- status: "in_progress",
27352
- timestamp: event.timestamp
27353
- });
27355
+ messages.push(fileOrCommandMessage(toolName, toolUseId, inputObj, event.timestamp));
27354
27356
  } else if (toolName === "TaskCreate" || toolName === "TaskUpdate" || toolName === "TaskList") {
27355
27357
  taskAccumulator.processContentBlock(block);
27356
27358
  const snapshot = taskSnapshot();
@@ -27530,6 +27532,11 @@ function parseClaudeEvents(events, parentToolUseId) {
27530
27532
  };
27531
27533
  }
27532
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
+ }
27533
27540
  filteredEvents.forEach((event) => {
27534
27541
  if (event.type === "claude-user") {
27535
27542
  const content = normalizeContentBlocks(event.payload.message?.content);
@@ -28011,7 +28018,7 @@ var headlessFilesystemAgentResultSchema = external_exports.object({
28011
28018
  });
28012
28019
 
28013
28020
  // ../shared/src/cli-version.ts
28014
- var CLI_VERSION = "0.2.758";
28021
+ var CLI_VERSION = "0.2.761";
28015
28022
 
28016
28023
  // ../shared/src/version.ts
28017
28024
  function compareVersions(v1, v2) {
@@ -28702,6 +28709,7 @@ async function readSseStream(body, onEvent, shouldContinue = () => true) {
28702
28709
  var import_yaml2 = __toESM(require_dist());
28703
28710
 
28704
28711
  // ../shared/src/workspace-identity.ts
28712
+ var AWS_SOURCE_IDENTITY_CLAIM = "https://aws.amazon.com/source_identity";
28705
28713
  var WORKSPACE_TOKEN_TTL_SECONDS = 15 * 60;
28706
28714
  var IDENTITY_PROXY_PORT = 17323;
28707
28715
  var IDENTITY_TOKEN_PATH = "/token";
@@ -28711,7 +28719,8 @@ var WORKSPACE_TOKEN_ENV = {
28711
28719
  };
28712
28720
  var workspaceAudienceSchema = external_exports.string().min(1).max(255).regex(/^[\x21-\x7e]+$/, "Audience must be a single printable token without spaces");
28713
28721
  var workspaceTokenRequestSchema = external_exports.object({
28714
- audience: workspaceAudienceSchema.optional()
28722
+ audience: workspaceAudienceSchema.optional(),
28723
+ aws_source_identity: external_exports.boolean().optional()
28715
28724
  });
28716
28725
  var workspaceTokenClaimsSchema = external_exports.object({
28717
28726
  iss: external_exports.string(),
@@ -28725,7 +28734,8 @@ var workspaceTokenClaimsSchema = external_exports.object({
28725
28734
  workspace_id: external_exports.string().uuid(),
28726
28735
  environment_id: external_exports.string().uuid(),
28727
28736
  creator_id: external_exports.string().uuid().nullable(),
28728
- 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()
28729
28739
  }).passthrough();
28730
28740
  var workspaceTokenResponseSchema = external_exports.object({
28731
28741
  issuer: external_exports.string(),
@@ -29249,13 +29259,13 @@ var import_node_child_process = require("child_process");
29249
29259
  var import_node_os = require("os");
29250
29260
  var import_node_path = require("path");
29251
29261
  var import_chalk5 = __toESM(require("chalk"));
29252
- async function fetchToken(audience) {
29262
+ async function fetchToken(audience, awsSourceIdentity) {
29253
29263
  let response;
29254
29264
  try {
29255
29265
  response = await fetch(identityTokenUrl(), {
29256
29266
  method: "POST",
29257
29267
  headers: { "content-type": "application/json" },
29258
- body: JSON.stringify(audience ? { audience } : {})
29268
+ body: JSON.stringify({ audience: audience || void 0, aws_source_identity: awsSourceIdentity || void 0 })
29259
29269
  });
29260
29270
  } catch {
29261
29271
  return createErrorResult({ message: "Workspace identity is only available inside a running Replicas workspace." });
@@ -29310,7 +29320,7 @@ async function identityExecCommand(command, options) {
29310
29320
  process.exit(result.status ?? 1);
29311
29321
  }
29312
29322
  async function identityAwsCredentialsCommand(options) {
29313
- 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");
29314
29324
  const cacheFile = (0, import_node_path.join)((0, import_node_os.homedir)(), ".replicas", "aws-credentials", `${key}.json`);
29315
29325
  try {
29316
29326
  const cached2 = (0, import_node_fs.readFileSync)(cacheFile, "utf8");
@@ -29320,7 +29330,7 @@ async function identityAwsCredentialsCommand(options) {
29320
29330
  }
29321
29331
  } catch {
29322
29332
  }
29323
- const result = await fetchToken(options.audience);
29333
+ const result = await fetchToken(options.audience, options.sourceIdentity);
29324
29334
  if (!result.ok) {
29325
29335
  console.error(import_chalk5.default.red(result.error.message));
29326
29336
  process.exitCode = 1;
@@ -33898,7 +33908,7 @@ if (isEnvironmentSetupWorkspace()) {
33898
33908
  if (isAgentMode()) {
33899
33909
  const identity = program.command("identity").description("Obtain short-lived workspace identity tokens (OIDC) for the services this workspace calls");
33900
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);
33901
- 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);
33902
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));
33903
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");
33904
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.758",
3
+ "version": "0.2.761",
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": {