@proagentstore/cli 0.4.57 → 0.4.58

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.
@@ -2,7 +2,7 @@ import { homedir } from "node:os";
2
2
  import { join, resolve } from "node:path";
3
3
  import { RunnerInputError } from "../errors.js";
4
4
  import { defaultStatePath, HeadlessSession } from "./headless.js";
5
- import { InspectError, readGitRemoteOrigin, readRepoFile, repoSearch, repoTree, runRepoGit } from "./inspect.js";
5
+ import { InspectError, readGitRemoteOrigin, readRepoFile, repoSearch, repoSync, repoTree, runRepoGit } from "./inspect.js";
6
6
  import { switchRepoBranch } from "./repo-write.js";
7
7
  import { checkWorkdir, ensureRepo, sanitizeSessionName } from "./repo.js";
8
8
  import { asTurnAuthor } from "./turn-author.js";
@@ -54,7 +54,15 @@ export class CodingRuntime {
54
54
  }
55
55
  /** Run a whitelisted read-only git command in the session's repo. */
56
56
  git(input) {
57
- return runRepoGit(this.resolveWorkDir(input), input.cmd, { path: input.path, n: input.n });
57
+ return runRepoGit(this.resolveWorkDir(input), input.cmd, { path: input.path, n: input.n, ref: input.ref });
58
+ }
59
+ /**
60
+ * Where the checkout stands against its upstream — fetch (cached), then count ahead/behind
61
+ * (#785). Never pulls. The one endpoint here that reaches the NETWORK, which is why it carries
62
+ * its own cache and its own no-prompt environment (`inspect.ts`).
63
+ */
64
+ sync(input) {
65
+ return repoSync(this.resolveWorkDir(input), { branch: input.branch, forceFetch: input.forceFetch });
58
66
  }
59
67
  /**
60
68
  * The ONE write the platform may make in a checkout by itself (#322) — put it back on a branch
@@ -158,6 +166,8 @@ export class CodingRuntime {
158
166
  // exactly the question asked about a session that just stopped.
159
167
  authResolved: session.authResolved,
160
168
  engineRuntime: session.engineRuntime,
169
+ engineMode: session.engineMode,
170
+ engineModeWarning: session.engineModeWarning,
161
171
  // Reported on EVERY capture, including one where the session is not alive: "how did the
162
172
  // last turn end" is exactly the question asked about a session that just stopped, and
163
173
  // the omitted-when-null shape keeps "not measured" distinguishable from a verdict.
@@ -252,6 +262,8 @@ export class CodingRuntime {
252
262
  takeover: this.takeovers.has(sessionId),
253
263
  authResolved: s.authResolved,
254
264
  engineRuntime: s.engineRuntime,
265
+ engineMode: s.engineMode,
266
+ engineModeWarning: s.engineModeWarning,
255
267
  ghGuard: s.ghGuard,
256
268
  }));
257
269
  }
@@ -1,6 +1,8 @@
1
1
  import { createServer, } from "node:http";
2
2
  import { URL } from "node:url";
3
3
  import { LocalRunner, RunnerInputError } from "./runner.js";
4
+ import { probeGitSshIdentity } from "./coding/repo.js";
5
+ import { listGithubOrgs, listGithubRepos, searchGithubRepos, getGithubRepoDetail, getGithubCredentialScope } from "./coding/github-browse.js";
4
6
  export function createRunnerServer(runner) {
5
7
  return createServer(async (req, res) => {
6
8
  try {
@@ -176,6 +178,95 @@ async function route(runner, req, res) {
176
178
  // use tmux, have their own /tmux/* endpoints and are unaffected.
177
179
  return json(res, 200, { tracked: runner.coding.diagnostics() });
178
180
  }
181
+ if ((req.method === "GET" || req.method === "POST") && path === "/coding/git-identity") {
182
+ // Report the SSH identity this machine presents to github.com (#684). Called by
183
+ // `coding_diagnostics` to surface deploy-key vs user-account mismatches without a
184
+ // failed clone. Never throws on the runner side — a network hiccup returns identity:null.
185
+ const b = req.method === "POST" ? await readJson(req).catch(() => ({})) : {};
186
+ const host = String(b.host || "github.com");
187
+ return json(res, 200, probeGitSshIdentity(host));
188
+ }
189
+ // ── GitHub credential scope (#688) ────────────────────────────────────────
190
+ // Read-only: reports the authenticated gh login + org memberships. Surfaces
191
+ // which account and organisations the runner's credentials can reach, before
192
+ // any browse operation begins. Never writes, never mutates anything on GitHub.
193
+ if ((req.method === "GET" || req.method === "POST") && path === "/coding/github-credentials") {
194
+ return json(res, 200, getGithubCredentialScope());
195
+ }
196
+ // ── GitHub org + repo enumeration (#685) ──────────────────────────────────
197
+ // Read-only: lists orgs and repos reachable by the machine's `gh` credentials.
198
+ // Never writes, never mutates anything on GitHub.
199
+ if ((req.method === "GET" || req.method === "POST") && path === "/coding/github-orgs") {
200
+ return json(res, 200, listGithubOrgs());
201
+ }
202
+ if ((req.method === "GET" || req.method === "POST") && path === "/coding/github-repos") {
203
+ const b = req.method === "POST"
204
+ ? await readJson(req).catch(() => ({}))
205
+ : {};
206
+ // Query params override body for GET callers (more REST-idiomatic).
207
+ const qOwner = url.searchParams.get("owner");
208
+ const qLimit = url.searchParams.get("limit");
209
+ const qSince = url.searchParams.get("since");
210
+ const qVis = url.searchParams.get("visibility");
211
+ const input = {
212
+ owner: qOwner ?? b.owner,
213
+ limit: qLimit ? Number(qLimit) : b.limit,
214
+ since: qSince ?? b.since,
215
+ visibility: (qVis === "public" || qVis === "private" || qVis === "all")
216
+ ? qVis
217
+ : b.visibility,
218
+ };
219
+ return json(res, 200, listGithubRepos(input));
220
+ }
221
+ // ── GitHub repository search (#686) ───────────────────────────────────────
222
+ // Read-only: searches repos reachable by the machine's `gh` credentials via
223
+ // GitHub's own search API. One API call per query (no per-repo fan-out).
224
+ // Results are cached in-process for 5 minutes to guard the 30 req/min quota.
225
+ if ((req.method === "GET" || req.method === "POST") && path === "/coding/github-search") {
226
+ const b = req.method === "POST"
227
+ ? await readJson(req).catch(() => ({}))
228
+ : {};
229
+ const qQuery = url.searchParams.get("query");
230
+ const qOwner = url.searchParams.get("owner");
231
+ const qLang = url.searchParams.get("language");
232
+ const qTopic = url.searchParams.get("topic");
233
+ const qPushedAfter = url.searchParams.get("pushedAfter");
234
+ const qOpenPrs = url.searchParams.get("openPrs");
235
+ const qLimit = url.searchParams.get("limit");
236
+ const qSort = url.searchParams.get("sort");
237
+ const bTyped = b;
238
+ const input = {
239
+ query: qQuery ?? bTyped.query,
240
+ owner: qOwner ?? bTyped.owner,
241
+ language: qLang ?? bTyped.language,
242
+ topic: qTopic ?? bTyped.topic,
243
+ pushedAfter: qPushedAfter ?? bTyped.pushedAfter,
244
+ openPrs: qOpenPrs !== null ? qOpenPrs === "true" : bTyped.openPrs,
245
+ limit: qLimit ? Number(qLimit) : bTyped.limit,
246
+ sort: (qSort === "stars" || qSort === "forks" || qSort === "updated")
247
+ ? qSort
248
+ : bTyped.sort,
249
+ };
250
+ return json(res, 200, searchGithubRepos(input));
251
+ }
252
+ // ── GitHub repository detail (#687) ──────────────────────────────────────
253
+ // Read-only: fetches issues, PRs, and branches for a given owner/repo slug
254
+ // via `gh api`. Results are cached in-process for 2 minutes.
255
+ if ((req.method === "GET" || req.method === "POST") && path === "/coding/github-repo-detail") {
256
+ const b = req.method === "POST"
257
+ ? await readJson(req).catch(() => ({}))
258
+ : {};
259
+ const bTyped = b;
260
+ const qRepo = url.searchParams.get("repo");
261
+ const qLimit = url.searchParams.get("limit");
262
+ const qState = url.searchParams.get("state");
263
+ const input = {
264
+ repo: qRepo ?? bTyped.repo ?? "",
265
+ limit: qLimit ? Number(qLimit) : bTyped.limit,
266
+ state: (qState === "all" || qState === "open") ? qState : bTyped.state,
267
+ };
268
+ return json(res, 200, getGithubRepoDetail(input));
269
+ }
179
270
  if (req.method === "POST" && path === "/coding/browse") {
180
271
  const { readdirSync, statSync } = await import("node:fs");
181
272
  const { resolve } = await import("node:path");
@@ -240,6 +331,18 @@ async function route(runner, req, res) {
240
331
  return json(res, 400, { error: e instanceof Error ? e.message : String(e) });
241
332
  }
242
333
  }
334
+ // Where the checkout stands against its upstream (#785): fetch (cached a minute), then count
335
+ // ahead/behind. Never pulls. A SEPARATE endpoint for the reason `/coding/search` is one: an
336
+ // older runner 404s it, which the cloud reads as "unverified" — never as "in sync".
337
+ if (req.method === "POST" && path === "/coding/sync") {
338
+ const b = await readJson(req);
339
+ try {
340
+ return json(res, 200, runner.coding.sync(b));
341
+ }
342
+ catch (e) {
343
+ return json(res, 400, { error: e instanceof Error ? e.message : String(e) });
344
+ }
345
+ }
243
346
  // The ONE write surface (#322). A standing policy may put a checkout back on the branch it
244
347
  // declared; it may not commit, discard, or touch a remote. An older runner 404s this, which the
245
348
  // cloud reports as "asked, not confirmed" rather than as done.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proagentstore/cli",
3
- "version": "0.4.57",
3
+ "version": "0.4.58",
4
4
  "description": "CLI for creating, publishing, and running ProAgentStore agents",
5
5
  "license": "MIT",
6
6
  "type": "module",