@invarn/cli 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/sdk.mjs CHANGED
@@ -7091,17 +7091,19 @@ async function getAgentBuildResult(sessionToken, buildId, { wait } = {}) {
7091
7091
  }
7092
7092
  return res.json();
7093
7093
  }
7094
- async function listSecrets({ pipelineId, kind } = {}) {
7094
+ async function listSecrets({ pipelineId, agentId, kind } = {}) {
7095
7095
  const url = new URL("/secrets", "http://placeholder");
7096
7096
  if (pipelineId) url.searchParams.set("pipeline", pipelineId);
7097
+ if (agentId) url.searchParams.set("agent", agentId);
7097
7098
  if (kind) url.searchParams.set("kind", kind);
7098
7099
  const res = await request(`${url.pathname}${url.search}`);
7099
7100
  const body = await res.json();
7100
7101
  return body.entries || [];
7101
7102
  }
7102
- async function createSecret({ scope, pipelineId, kind, key, value }) {
7103
+ async function createSecret({ scope, pipelineId, agentId, kind, key, value }) {
7103
7104
  const body = { scope, kind, key, value };
7104
7105
  if (pipelineId) body.pipelineId = pipelineId;
7106
+ if (agentId) body.agentId = agentId;
7105
7107
  const res = await request("/secrets", {
7106
7108
  method: "POST",
7107
7109
  body: JSON.stringify(body)
@@ -8865,6 +8867,22 @@ var REGISTRATION_GUIDANCE = [
8865
8867
  " If it does, read it \u2014 the build steps are already defined there. Base your agent's commands on them.",
8866
8868
  " Do NOT explore the project structure if a pipeline file already tells you the build commands.",
8867
8869
  "",
8870
+ "STEP SPLITTING \u2014 one concept per command:",
8871
+ " When you have multiple natural phases (setup, compile, test, package, sign, deploy), SPLIT them into separate commands.",
8872
+ " Rule of thumb: if your single `run` field contains `&&` joining distinct concepts, that's a signal to split.",
8873
+ " Example of a BAD single command (distinct phases chained together):",
8874
+ ` run: "printf '...' > local.properties && xcodebuild -scheme iosApp build"`,
8875
+ " Better as two commands:",
8876
+ ` [{name:"config", run:"printf '...' > local.properties"},`,
8877
+ ' {name:"build", run:"xcodebuild -scheme iosApp build"}]',
8878
+ " Why it matters:",
8879
+ " \u2022 Each command is its own step in the PR check run \u2014 reviewers see exactly which phase broke.",
8880
+ ' \u2022 Failed step name appears in the check summary (e.g. "Build failed at step `build`" is much more useful than a generic "build failed").',
8881
+ " \u2022 Per-step duration is tracked \u2014 spot slow phases.",
8882
+ " \u2022 cibuild auto-injects cache steps between commands; one mega-step defeats that.",
8883
+ " \u2022 Individual steps can be individually retried in future (phase 4 of the roadmap).",
8884
+ " Shell flow inside ONE command (pipes, variable assignment, short guards) is fine. Splitting is for DISTINCT PHASES, not every line.",
8885
+ "",
8868
8886
  "DESCRIPTION: Write 3 sentences for other AI agents to read.",
8869
8887
  " 1. What this agent does (include specific tools: Gradle, Xcode, pytest, etc.)",
8870
8888
  " 2. When to use it (the trigger condition)",
@@ -8971,12 +8989,19 @@ ${lines.join("\n")}`;
8971
8989
  }
8972
8990
  async function handleSecretsList(args) {
8973
8991
  try {
8992
+ if (args.pipeline_id && args.agent_id) {
8993
+ return {
8994
+ content: [{ type: "text", text: "Error: pass either pipeline_id or agent_id, not both." }],
8995
+ isError: true
8996
+ };
8997
+ }
8974
8998
  const entries = await listSecrets({
8975
8999
  pipelineId: args.pipeline_id || void 0,
9000
+ agentId: args.agent_id || void 0,
8976
9001
  kind: args.kind
8977
9002
  });
8978
9003
  if (entries.length === 0) {
8979
- const scope = args.pipeline_id ? "pipeline" : "org";
9004
+ const scope = args.pipeline_id || args.agent_id ? "pipeline" : "org";
8980
9005
  return {
8981
9006
  content: [
8982
9007
  { type: "text", text: `No ${args.kind?.toLowerCase() ?? "entries"} at ${scope} scope.` }
@@ -8996,12 +9021,19 @@ ${lines.join("\n")}` }] };
8996
9021
  }
8997
9022
  async function handleSecretsSet(args) {
8998
9023
  try {
9024
+ if (args.pipeline_id && args.agent_id) {
9025
+ return {
9026
+ content: [{ type: "text", text: "Error: pass either pipeline_id or agent_id, not both." }],
9027
+ isError: true
9028
+ };
9029
+ }
8999
9030
  const kind = args.kind === "VARIABLE" ? "VARIABLE" : "SECRET";
9000
- const scope = args.pipeline_id ? "PIPELINE" : "ORG";
9031
+ const scope = args.pipeline_id || args.agent_id ? "PIPELINE" : "ORG";
9001
9032
  try {
9002
9033
  await createSecret({
9003
9034
  scope,
9004
9035
  pipelineId: args.pipeline_id,
9036
+ agentId: args.agent_id,
9005
9037
  kind,
9006
9038
  key: args.key,
9007
9039
  value: args.value
@@ -9016,7 +9048,11 @@ async function handleSecretsSet(args) {
9016
9048
  };
9017
9049
  } catch (err) {
9018
9050
  if (err.statusCode !== 409) throw err;
9019
- const existing = (await listSecrets({ pipelineId: args.pipeline_id, kind })).find((e) => e.key === args.key);
9051
+ const existing = (await listSecrets({
9052
+ pipelineId: args.pipeline_id,
9053
+ agentId: args.agent_id,
9054
+ kind
9055
+ })).find((e) => e.key === args.key);
9020
9056
  if (!existing) throw err;
9021
9057
  await updateSecretValue(existing.id, args.value);
9022
9058
  return {
@@ -9143,9 +9179,11 @@ var TOOL_SPECS = [
9143
9179
  description: z2.string().describe("What this agent does, when to use it, and what it does not do"),
9144
9180
  repository: z2.string().optional().describe('Repository to bind to (e.g. "owner/repo-name"). Required if the org has multiple repos connected. Omit if only one repo.'),
9145
9181
  commands: z2.array(z2.object({
9146
- name: z2.string().describe('Command name (e.g. "compile")'),
9182
+ name: z2.string().describe('Command name \u2014 short, lowercase, hyphenated, matches the concept (e.g. "config", "compile", "test", "package")'),
9147
9183
  run: z2.string().describe('Shell command to execute (e.g. "./gradlew assembleDebug")')
9148
- })).describe("List of commands this agent can run"),
9184
+ })).describe(
9185
+ 'Ordered list of commands. SPLIT NATURAL PHASES INTO SEPARATE COMMANDS \u2014 one concept per command. If you find yourself chaining distinct phases with `&&` (e.g. write a config file THEN build, or compile THEN archive THEN sign), those belong in separate commands. Each command becomes its own step in the check-run output, gets its own duration, and fails the build with a clear "Failed at step X" summary instead of "build step failed" for an 8-minute merged operation. Example: for "write local.properties from secrets, then xcodebuild", use two commands: [{name:"config", run:"printf ... > local.properties"}, {name:"build", run:"xcodebuild ..."}] \u2014 NOT one command that chains both with `&&`.'
9186
+ ),
9149
9187
  overwrite: z2.boolean().optional().describe("Set true to replace a pipeline file that already exists at .ci/pipelines/agent-<name>.yml in the repo. Use this when re-registering an agent \u2014 e.g. the same repo is connected under another org, or a prior registration was deleted without cleaning up the committed file. Do NOT set this when the existing file is hand-authored YAML unrelated to a prior agent."),
9150
9188
  triggers: z2.array(z2.object({
9151
9189
  push_branch: z2.string().optional().describe('Match push events where the pushed branch matches this pattern (glob). Use "*" for any branch.'),
@@ -9191,16 +9229,17 @@ var TOOL_SPECS = [
9191
9229
  },
9192
9230
  {
9193
9231
  name: "invarn_secrets_list",
9194
- description: "List secret and variable metadata for the organization or a specific pipeline. Returns keys, kinds, scopes, and timestamps \u2014 NEVER values. Use this to check whether a secret exists before calling invarn_agent_run.",
9232
+ description: "List secret and variable metadata for the organization or a specific pipeline. Returns keys, kinds, scopes, and timestamps \u2014 NEVER values. Use this to check whether a secret exists before calling invarn_agent_run. To scope to an agent's pipeline, pass agent_id \u2014 simpler than looking up pipeline_id.",
9195
9233
  inputSchema: {
9196
- pipeline_id: z2.string().optional().describe("Pipeline ID. Omit for org-scoped entries."),
9234
+ pipeline_id: z2.string().optional().describe("Pipeline ID. Omit for org-scoped entries. Mutually exclusive with agent_id."),
9235
+ agent_id: z2.string().optional().describe("Agent ID \u2014 resolves to the agent's owned pipeline server-side. Mutually exclusive with pipeline_id."),
9197
9236
  kind: z2.enum(["SECRET", "VARIABLE"]).optional().describe("Filter to a specific kind.")
9198
9237
  },
9199
9238
  handler: async (args) => handleSecretsList(args ?? {})
9200
9239
  },
9201
9240
  {
9202
9241
  name: "invarn_secrets_set",
9203
- description: "Create or update a secret or variable. Idempotent \u2014 if the key already exists at this scope, the value is replaced. Requires a human (inv_human_\u2026) token with OWNER or ADMIN role in the org; agent tokens receive 403. There is no read tool \u2014 values cannot be retrieved via MCP.",
9242
+ description: "Create or update a secret or variable. Idempotent \u2014 if the key already exists at this scope, the value is replaced. Requires a human (inv_human_\u2026) token with OWNER or ADMIN role in the org; agent tokens receive 403. There is no read tool \u2014 values cannot be retrieved via MCP. To target an agent's pipeline, pass agent_id instead of pipeline_id.",
9204
9243
  inputSchema: {
9205
9244
  key: z2.string().describe(
9206
9245
  "Uppercase env-var-safe key (/^[A-Z_][A-Z0-9_]{0,63}$/, not starting with INVARN_)."
@@ -9209,7 +9248,8 @@ var TOOL_SPECS = [
9209
9248
  kind: z2.enum(["SECRET", "VARIABLE"]).describe(
9210
9249
  "SECRET: encrypted at rest, redacted from build logs. VARIABLE: plaintext, visible in dashboard and logs when echoed."
9211
9250
  ),
9212
- pipeline_id: z2.string().optional().describe("Pipeline ID for a pipeline-scoped entry. Omit for org scope.")
9251
+ pipeline_id: z2.string().optional().describe("Pipeline ID for a pipeline-scoped entry. Omit for org scope. Mutually exclusive with agent_id."),
9252
+ agent_id: z2.string().optional().describe("Agent ID \u2014 resolves to the agent's owned pipeline server-side. Mutually exclusive with pipeline_id. Omit both for org scope.")
9213
9253
  },
9214
9254
  handler: async (args) => handleSecretsSet(args)
9215
9255
  }
@@ -9319,19 +9359,27 @@ function buildHandlers(ctx, getSession) {
9319
9359
  return result;
9320
9360
  },
9321
9361
  invarn_secrets_list: async (args) => {
9362
+ if (args.pipeline_id && args.agent_id) {
9363
+ throw new Error("pass either pipeline_id or agent_id, not both");
9364
+ }
9322
9365
  const entries = await listSecrets({
9323
9366
  pipelineId: args.pipeline_id || void 0,
9367
+ agentId: args.agent_id || void 0,
9324
9368
  kind: args.kind
9325
9369
  });
9326
9370
  return { entries };
9327
9371
  },
9328
9372
  invarn_secrets_set: async (args) => {
9373
+ if (args.pipeline_id && args.agent_id) {
9374
+ throw new Error("pass either pipeline_id or agent_id, not both");
9375
+ }
9329
9376
  const kind = args.kind === "VARIABLE" ? "VARIABLE" : "SECRET";
9330
- const scope = args.pipeline_id ? "PIPELINE" : "ORG";
9377
+ const scope = args.pipeline_id || args.agent_id ? "PIPELINE" : "ORG";
9331
9378
  try {
9332
9379
  await createSecret({
9333
9380
  scope,
9334
9381
  pipelineId: args.pipeline_id,
9382
+ agentId: args.agent_id,
9335
9383
  kind,
9336
9384
  key: args.key,
9337
9385
  value: args.value
@@ -9339,7 +9387,11 @@ function buildHandlers(ctx, getSession) {
9339
9387
  return { action: "created", key: args.key, kind, scope: scope.toLowerCase() };
9340
9388
  } catch (err) {
9341
9389
  if (err.statusCode !== 409) throw err;
9342
- const existing = (await listSecrets({ pipelineId: args.pipeline_id, kind })).find((e) => e.key === args.key);
9390
+ const existing = (await listSecrets({
9391
+ pipelineId: args.pipeline_id,
9392
+ agentId: args.agent_id,
9393
+ kind
9394
+ })).find((e) => e.key === args.key);
9343
9395
  if (!existing) throw err;
9344
9396
  await updateSecretValue(existing.id, args.value);
9345
9397
  return { action: "updated", key: args.key, kind, scope: scope.toLowerCase() };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@invarn/cli",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "Invarn CLI — run builds, check artifacts, and ship from your terminal",
5
5
  "type": "module",
6
6
  "main": "dist/invarn.cjs",