@allocator-one/harness-cli 1.15.0 → 1.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ACTIVITY_MAX_LIMIT,
4
- CLI_VERSION,
4
+ BUDGET_MAX_DOLLARS,
5
5
  CONDITION_KINDS,
6
6
  Client,
7
7
  GATE_OWNER_ROLES,
@@ -11,6 +11,8 @@ import {
11
11
  JOB_STATES,
12
12
  JOB_STATUS_ALIASES,
13
13
  MAX_IDEMPOTENCY_KEY_BYTES,
14
+ MCP_SURFACE,
15
+ OUTCOME_MAX_CHARACTERS,
14
16
  PLAN_EXECUTORS,
15
17
  RELATION_CREATE_TYPES,
16
18
  RELATION_UPDATE_TYPES,
@@ -61,6 +63,7 @@ import {
61
63
  planHold,
62
64
  planRelease,
63
65
  planShow,
66
+ prepareRun,
64
67
  procedureRun,
65
68
  procedureSave,
66
69
  procedureShow,
@@ -77,6 +80,7 @@ import {
77
80
  search,
78
81
  show,
79
82
  skillsList,
83
+ startRun,
80
84
  stateAdd,
81
85
  stateDelete,
82
86
  stateOrder,
@@ -92,7 +96,12 @@ import {
92
96
  upload,
93
97
  userList,
94
98
  workflowStates
95
- } from "./chunk-I2S2JVCQ.js";
99
+ } from "./chunk-L2OWOZ4N.js";
100
+ import {
101
+ CLI_VERSION,
102
+ invokedName,
103
+ named
104
+ } from "./chunk-FQWV5MIC.js";
96
105
 
97
106
  // src/mcp/server.ts
98
107
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -102,11 +111,11 @@ var NeedsLoginError = class extends Error {
102
111
  };
103
112
  function context() {
104
113
  const resolved = resolveConfig();
105
- for (const warning of resolved.warnings) process.stderr.write(warning + "\n");
114
+ for (const warning of resolved.warnings) process.stderr.write(named(warning + "\n"));
106
115
  if (resolved.kind === "error") throw new NeedsLoginError(resolved.message);
107
116
  if (resolved.kind === "needs-login") {
108
117
  throw new NeedsLoginError(
109
- `Not signed in to ${resolved.baseUrl}. Run \`harness login\` in a terminal (or set HARNESS_API_TOKEN for headless use) \u2014 the MCP server never opens a browser itself.`
118
+ `Not signed in to ${resolved.baseUrl}. Run \`${invokedName()} login\` in a terminal (or set HARNESS_API_TOKEN for headless use) \u2014 the MCP server never opens a browser itself.`
110
119
  );
111
120
  }
112
121
  return { client: new Client(resolved.config), config: resolved.config };
@@ -122,7 +131,7 @@ async function run(work) {
122
131
  const output = await work(ctx);
123
132
  const content = [{ type: "text", text: JSON.stringify(output.json) }];
124
133
  if (output.notice !== void 0 && output.notice !== "") {
125
- content.push({ type: "text", text: output.notice.trimEnd() });
134
+ content.push({ type: "text", text: named(output.notice.trimEnd()) });
126
135
  }
127
136
  if (output.partial === true) {
128
137
  content.push({ type: "text", text: "partial failure \u2014 inspect the JSON entries for per-item errors" });
@@ -133,10 +142,10 @@ async function run(work) {
133
142
  const error = explainExpertRefusal(caught, token);
134
143
  if (error instanceof NeedsLoginError) return toolError(error.message);
135
144
  if (error instanceof HttpError) {
136
- return toolError(`${error.message} (HTTP ${error.status})${errorDetails(error.body)}`);
145
+ return toolError(named(`${error.message} (HTTP ${error.status})${errorDetails(error.body)}`));
137
146
  }
138
147
  if (error instanceof TransportError) return toolError(`cannot reach the API: ${error.message}`);
139
- if (error instanceof Error) return toolError(error.message);
148
+ if (error instanceof Error) return toolError(named(error.message));
140
149
  throw error;
141
150
  }
142
151
  }
@@ -258,6 +267,40 @@ var OPERATOR_CONTROL_TOOLS = [
258
267
  handler: (ctx, args) => jobPause(ctx, args["id"])
259
268
  }
260
269
  ];
270
+ var OPERATOR_RUN_TOOLS = [
271
+ {
272
+ name: "job_start",
273
+ description: `Start an Operator job from an outcome, as Run as job does in Chat Mode in the app: the request is posted, attributed to the caller, in a new organization chat of theirs, and Operator takes it from there under the caller's own current rights. Returns job_id, state, workbench_url and, when a change set already awaits approval, approval_url. Approval happens in the app: give a person the approval_url; this tool never approves anything, and no tool does. Each call starts another job, so never call it again to "retry": not after a timeout, a dropped connection or a 5xx either. An "Outcome unknown" error means a job may have been started; look for it with jobs_list (mine) before calling again. Only the organization's admins may pass a budget. Follow the job with job_show.`,
274
+ readOnly: false,
275
+ destructive: false,
276
+ idempotent: false,
277
+ shape: {
278
+ outcome: z.string().describe(
279
+ `what the job should achieve, in plain language: 1 to ${OUTCOME_MAX_CHARACTERS} characters once trimmed`
280
+ ),
281
+ budget: z.number().positive().max(BUDGET_MAX_DOLLARS).optional().describe(
282
+ `the job's budget in US dollars, at most two decimals, up to ${BUDGET_MAX_DOLLARS}; only the organization's admins may pass one`
283
+ ),
284
+ skill: z.string().optional().describe("an Operator skill's invocation name, like /summarize-lps (skills_list names them)")
285
+ },
286
+ handler: async (ctx, args) => {
287
+ const request = prepareRun(args["outcome"], {
288
+ budget: args["budget"] === void 0 ? void 0 : String(args["budget"]),
289
+ skill: args["skill"]
290
+ }, MCP_SURFACE);
291
+ const job = await startRun(ctx, request, MCP_SURFACE);
292
+ return {
293
+ json: {
294
+ job_id: job.job_id,
295
+ state: job.state,
296
+ workbench_url: job.workbench_url,
297
+ ...job.approval_url === null ? {} : { approval_url: job.approval_url }
298
+ },
299
+ human: `Started Operator job ${job.job_id}.`
300
+ };
301
+ }
302
+ }
303
+ ];
261
304
  var NOT_IDEMPOTENT = 'Not idempotent: never retry it automatically, not after a timeout or a 5xx either. An "Outcome unknown" error may have landed and says what to check before calling again; only "cannot reach the API" means the request never reached the server, so nothing changed.';
262
305
  var OPERATOR_LIFECYCLE_TOOLS = [
263
306
  {
@@ -499,7 +542,7 @@ var TOOLS = [
499
542
  },
500
543
  {
501
544
  name: "comment_list",
502
- description: "An issue's full comment thread, untruncated.",
545
+ description: "An issue's complete comment thread, oldest first. Follows every older page; pagination failures return an error, not a partial thread.",
503
546
  readOnly: true,
504
547
  shape: { id: identifier },
505
548
  handler: (ctx, args) => comments(ctx, args["id"])
@@ -871,7 +914,7 @@ var TOOLS = [
871
914
  // ── Plans and gates ──
872
915
  {
873
916
  name: "plan_create",
874
- description: "Create a plan: a root issue and up to 99 steps, created in one transaction or not at all, and held until a person releases it (plan_release). Steps refer to each other by key: parent (containment) and blocked_by (dependencies). executor operator files a step in the organization's Operator team, where only the plan driver claims it; a person's step goes to its team (or the plan's). conditions are wait conditions (not_before, gate, event); a gate may carry a key (gate: <key> is shorthand) and an owner, who alone ticks it. An invalid plan writes nothing and the error lists every violation by step key. Reuse idempotency_key when retrying.",
917
+ description: "Create a plan: a root issue and up to 99 steps, created in one transaction or not at all, and held until a person releases it (plan_release). Steps refer to each other by key: parent (containment) and blocked_by (dependencies). executor operator files a step in the organization's Operator team, where only the plan driver claims it; a person's step goes to its team (or the plan's). conditions are wait conditions (not_before, gate, event); a gate may carry a key (gate: <key> is shorthand) and an owner, who alone ticks it. An invalid plan writes nothing and the error lists every violation by step key. Reuse idempotency_key when retrying. Once released, the changes an Operator step proposes need a person's approval in the app, as plan_release says.",
875
918
  readOnly: false,
876
919
  shape: {
877
920
  team: z.string().optional().describe("team key for the root and people's steps; defaults to HARNESS_TEAM"),
@@ -889,7 +932,7 @@ var TOOLS = [
889
932
  },
890
933
  {
891
934
  name: "plan_edit",
892
- description: "Change step executors and keys (each step named by its key or issue identifier) and the plan's budgets. Editing a released step holds it until the plan is released again; a budget edit needs the plan held. Descriptions, parents and edges are edited with issue_update and relation_add/relation_rm; conditions are set at plan_create, and gate_set ticks a gate.",
935
+ description: "Change step executors and keys (each step named by its key or issue identifier) and the plan's budgets. Editing a released step holds it until the plan is released again; a budget edit needs the plan held. Descriptions, parents and edges are edited with issue_update and relation_add/relation_rm; conditions are set at plan_create, and gate_set ticks a gate. Once released, the changes an Operator step proposes need a person's approval in the app, as plan_release says.",
893
936
  readOnly: false,
894
937
  shape: {
895
938
  id: planId,
@@ -904,7 +947,7 @@ var TOOLS = [
904
947
  },
905
948
  {
906
949
  name: "plan_release",
907
- description: "Release a held plan (or one that needs attention) as the caller: freezes its next revision and lets its ready steps run. A plan with Operator steps needs a spend budget and the caller's right to run Operator; collaborators join its plan room.",
950
+ description: "Release a held plan (or one that needs attention) as the caller: freezes its next revision and lets its ready steps run. A plan with Operator steps needs a spend budget and the caller's right to run Operator; collaborators join its plan room. Each Operator step runs as a job (jobs_list names its plan_step), and the changes a job proposes need a person's approval in the app: give them the job's approval_url. No tool approves.",
908
951
  readOnly: false,
909
952
  shape: {
910
953
  id: planId,
@@ -927,7 +970,7 @@ var TOOLS = [
927
970
  },
928
971
  {
929
972
  name: "gate_set",
930
- description: "Satisfy (satisfied true) or un-satisfy (false) a gate wait condition on an issue, found by its key, label or condition id (condition_list and issue_show list conditions). Needs staff standing in the organization or a place in its plan's room; only an owned gate's owner ticks it, and its owner or staff untick it. Anyone else gets a 403 naming the owner. An outside expert's pxc_ credential ticks the gates it owns on a step assigned to it (assignment_list); any other step is 404.",
973
+ description: "Satisfy (satisfied true) or un-satisfy (false) a gate wait condition on an issue, found by its key, label or condition id (condition_list and issue_show list conditions). Needs staff standing in the organization or a place in its plan's room; only an owned gate's owner ticks it, and its owner or staff untick it. Anyone else gets a 403 naming the owner. An outside expert's pxc_ credential ticks the gates it owns on a step assigned to it (assignment_list); any other step is 404. An Operator-only login (operator-cli) is refused here, as on every tool outside Operator's, even for a person in the plan's room.",
931
974
  readOnly: false,
932
975
  shape: {
933
976
  issue: identifier,
@@ -954,6 +997,8 @@ var TOOLS = [
954
997
  ...OPERATOR_JOB_TOOLS,
955
998
  // ── Operator job controls (CLI-46) ──
956
999
  ...OPERATOR_CONTROL_TOOLS,
1000
+ // ── Operator job start (CLI-45) ──
1001
+ ...OPERATOR_RUN_TOOLS,
957
1002
  // ── Operator job lifecycle (CLI-47) ──
958
1003
  ...OPERATOR_LIFECYCLE_TOOLS,
959
1004
  // ── Uploads ──
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allocator-one/harness-cli",
3
- "version": "1.15.0",
3
+ "version": "1.15.1",
4
4
  "description": "CLI and MCP server for Harness, allocator.one's issue tracker",
5
5
  "type": "module",
6
6
  "license": "MIT",