agent-golf 0.1.0 → 0.1.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.
package/README.md CHANGED
@@ -2,17 +2,17 @@
2
2
 
3
3
  Pi commands for the Agent Golf coding-agent leaderboard. Login, browsing and submission never call a model or compute scores locally.
4
4
 
5
- **Initial release: preview (0.1.0).** Installation, UI, transport and privacy are verified. At initial publication, production API availability and real counter integration were not verified; local website tests used stand-in scores. This is for integration testing, not competition scoring until your organizer confirms a deployed, verified real counter.
5
+ **0.1.1:** uses https://agent-golf.csbasics.in and includes read-only interactive problem views. Local integration with the real counter is verified and the production catalogue is reachable. A fresh solved production submission has not been verified; follow your organizer's readiness announcement before competition use.
6
6
 
7
7
  ## Install and update
8
8
 
9
9
  Requires Node.js 22.19+ and the Pi coding agent (tested with Pi 1.1.0).
10
10
 
11
11
  ```bash
12
- pi install npm:agent-golf@preview
12
+ pi install npm:agent-golf
13
13
  ```
14
14
 
15
- Use an organizer-provided trusted test server via the override below until production is ready. Once the team announces a stable release, install `npm:agent-golf` to follow `latest` instead.
15
+ Unversioned installations follow `latest`. The `preview` channel also receives this URL update; existing preview users can keep that installation source.
16
16
 
17
17
  Start a new Pi process, or run `/reload` in an existing one. To update an unpinned installation:
18
18
 
@@ -20,7 +20,9 @@ Start a new Pi process, or run `/reload` in an existing one. To update an unpinn
20
20
  pi update --extensions
21
21
  ```
22
22
 
23
- Restart Pi or `/reload` afterward. An exact version such as `npm:agent-golf@0.1.0` is pinned; updating does not advance it to a different version. The `npm:agent-golf@preview` installation follows the preview channel; it does not automatically switch to `latest`.
23
+ Restart Pi or `/reload` afterward. An exact version such as `npm:agent-golf@0.1.0` is pinned; updating does not advance it to a different version. Remove the old pinned source (`pi remove npm:agent-golf@0.1.0`), then install `npm:agent-golf@0.1.1` explicitly to replace that pin. The `npm:agent-golf@preview` installation follows the preview channel; it does not automatically switch to `latest`.
24
+
25
+ **Updating from 0.1.0:** the default server origin changed. Unset any old `AGENT_GOLF_API_BASE_URL` override, update/restart Pi, and run `/golf-login` with your student key valid on the new website. Old-origin credentials are preserved but never migrated or sent to the new origin.
24
26
 
25
27
  ## Commands
26
28
 
@@ -28,7 +30,7 @@ Restart Pi or `/reload` afterward. An exact version such as `npm:agent-golf@0.1.
28
30
 
29
31
  Browse public contest rules, challenge URLs, instructions and each problem's model/thinking setup. Rules open first, followed by a problem selector. Close a detail view to return to the list; cancel the list to exit. An empty list still shows the rules.
30
32
 
31
- Views use Pi's temporary plain-text multi-line editor in interactive or supported RPC clients. All edits are discarded; nothing is inserted into your prompt or session. `any` setup is displayed as "any model, one per session" or "any thinking level".
33
+ Interactive Pi uses read-only, scrollable text views: Enter on rules opens the problem list, Esc closes browsing, and Enter/Esc on details returns to the list. Arrow keys, Page Up/Down and Home/End scroll; navigation hints use your configured confirm/cancel keys. RPC clients retain temporary text-editor dialogs with explicit navigation titles; all edits are discarded. Nothing is inserted into your prompt or session. `any` setup is displayed as "any model, one per session" or "any thinking level".
32
34
 
33
35
  Browsing sends no key, cookie or session log, makes no model calls, and never starts/binds an attempt or changes your setup. The catalogue is advisory; server submission checks are authoritative. Replies are bounded to 64 KB and strings to 1,000 Unicode characters, with terminal-safety and exact Hackattic URL checks.
34
36
 
@@ -56,7 +58,7 @@ The server owns scores and status. A null total with `missing_usage` is displaye
56
58
 
57
59
  ## Server configuration
58
60
 
59
- Default API origin: **https://agent-golf.justuju.in**.
61
+ Default API origin: **https://agent-golf.csbasics.in**.
60
62
 
61
63
  For a trusted local server:
62
64
 
@@ -75,7 +77,7 @@ Both authentication and browsing/submission use this origin. Only HTTPS and loop
75
77
  - **No persisted session file:** submit from the saved attempt, not a fresh custom-only session that has not flushed to disk.
76
78
  - **Upload/network error:** the upload may already have reached the server. Check the website before retrying. The plugin never retries automatically.
77
79
 
78
- Removing the preview with `pi remove npm:agent-golf@preview` (use the source you installed) does not remove your credential file. Delete that file separately only if you intend to discard saved logins.
80
+ Removing the package with `pi remove npm:agent-golf` (use the source you installed, e.g. `npm:agent-golf@preview`) does not remove your credential file. Delete that file separately only if you intend to discard saved logins.
79
81
 
80
82
  ## License
81
83
 
package/config.ts CHANGED
@@ -1,4 +1,4 @@
1
- export const DEFAULT_API_ORIGIN = "https://agent-golf.justuju.in";
1
+ export const DEFAULT_API_ORIGIN = "https://agent-golf.csbasics.in";
2
2
 
3
3
  export function getApiOrigin(baseUrl: string | undefined): string {
4
4
  if (!baseUrl?.trim()) throw new Error("missing-origin");
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "agent-golf",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Zero-model-call Pi commands for Agent Golf login, raw session submission and problem browsing",
5
5
  "license": "MIT",
6
- "homepage": "https://agent-golf.justuju.in",
6
+ "homepage": "https://agent-golf.csbasics.in",
7
7
  "keywords": ["pi-package", "agent-golf"],
8
8
  "engines": { "node": ">=22.19.0" },
9
- "files": ["index.ts", "config.ts", "login.ts", "submit.ts", "user-bash.ts", "problems.ts", "README.md", "LICENSE"],
9
+ "files": ["index.ts", "config.ts", "login.ts", "submit.ts", "user-bash.ts", "problems.ts", "problem-view.ts", "README.md", "LICENSE"],
10
10
  "pi": { "extensions": ["./index.ts"], "skills": [], "prompts": [], "themes": [] },
11
11
  "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org/" },
12
12
  "type": "module",
@@ -14,7 +14,8 @@
14
14
  "test": "node --test test/*.test.mjs"
15
15
  },
16
16
  "peerDependencies": {
17
- "@earendil-works/pi-coding-agent": "*"
17
+ "@earendil-works/pi-coding-agent": "*",
18
+ "@earendil-works/pi-tui": "*"
18
19
  },
19
20
  "devDependencies": {}
20
21
  }
@@ -0,0 +1,74 @@
1
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
+
3
+ type TuiModule = typeof import("@earendil-works/pi-tui");
4
+ type Factory = Parameters<ExtensionContext["ui"]["custom"]>[0];
5
+ type Tui = Parameters<Factory>[0];
6
+ type Theme = Parameters<Factory>[1];
7
+ type Bindings = Parameters<Factory>[2];
8
+
9
+ // Dependency injection keeps rendering tests independent of Pi's module loader.
10
+ export function createProblemView(
11
+ lib: TuiModule, tui: Tui, theme: Theme, bindings: Bindings,
12
+ done: (continued: boolean) => void, title: string, body: string, action: string, cancel: string,
13
+ ) {
14
+ const text = new lib.Text(body, 0, 0);
15
+ const scroll = new lib.ScrollView(text, { scrollbar: "hidden", overscroll: "contain" });
16
+ let height = 1;
17
+ let closed = false;
18
+ const finish = (value: boolean) => { if (!closed) { closed = true; done(value); } };
19
+ const keys = (name: "tui.select.confirm" | "tui.select.cancel") => bindings.getKeys(name).join("/");
20
+ return {
21
+ render(width: number) {
22
+ const w = Math.max(1, Math.floor(width));
23
+ const confirmKey = keys("tui.select.confirm"), cancelKey = keys("tui.select.cancel");
24
+ const navigation = action === cancel ? `${confirmKey}/${cancelKey}: ${action}` :
25
+ `${confirmKey}: ${action} · ${cancelKey}: ${cancel}`;
26
+ const navigationLines = new lib.Text(navigation, 0, 0).render(w);
27
+ const available = Math.max(1, tui.terminal.rows - 5);
28
+ // Physically tiny terminals cannot fit body plus controls; ask for resize
29
+ // rather than presenting a clipped action as if it were the full help.
30
+ if (w < 12 || navigationLines.length + 2 > available) {
31
+ return [lib.truncateToWidth("Resize terminal", w),
32
+ ...new lib.Text(`${confirmKey}/${cancelKey}`, 0, 0).render(w)].slice(0, available);
33
+ }
34
+ const showScrollHint = available >= navigationLines.length + 3;
35
+ height = Math.max(1, Math.min(16, available - 1 - navigationLines.length - Number(showScrollHint)));
36
+ const lines = text.render(w);
37
+ scroll.updateLayout(lines.length, height, () => tui.requestRender());
38
+ const visible = lines.slice(scroll.scrollTop, scroll.scrollTop + height);
39
+ const scrollHint = `${w < 45 ? "↑/↓ scroll" : "↑/↓ PgUp/PgDn Home/End: scroll"} · ${scroll.scrollTop + 1}-${Math.min(lines.length, scroll.scrollTop + height)}/${lines.length}`;
40
+ return [
41
+ lib.truncateToWidth(theme.fg("accent", title), w),
42
+ ...visible,
43
+ ...navigationLines.map((line) => theme.fg("muted", line)),
44
+ ...(showScrollHint ? [lib.truncateToWidth(theme.fg("muted", scrollHint), w)] : []),
45
+ ];
46
+ },
47
+ handleInput(data: string) {
48
+ if (closed) return;
49
+ if (bindings.matches(data, "tui.select.cancel") || lib.matchesKey(data, lib.Key.ctrl("c"))) { finish(false); return; }
50
+ if (bindings.matches(data, "tui.select.confirm")) { finish(true); return; }
51
+ if (lib.matchesKey(data, lib.Key.up)) scroll.scrollBy(-1);
52
+ else if (lib.matchesKey(data, lib.Key.down)) scroll.scrollBy(1);
53
+ else if (lib.matchesKey(data, lib.Key.pageUp)) scroll.scrollBy(-height);
54
+ else if (lib.matchesKey(data, lib.Key.pageDown)) scroll.scrollBy(height);
55
+ else if (lib.matchesKey(data, lib.Key.home)) scroll.scrollToStart();
56
+ else if (lib.matchesKey(data, lib.Key.end)) scroll.scrollToEnd();
57
+ // No input/editor exists: typing and paste cannot alter the displayed text.
58
+ },
59
+ invalidate() { text.invalidate(); scroll.invalidate(); },
60
+ };
61
+ }
62
+
63
+ export async function showProblemView(
64
+ ctx: Pick<ExtensionContext, "ui" | "mode">,
65
+ title: string, body: string, action: string, cancel: string,
66
+ ): Promise<boolean> {
67
+ if (ctx.mode !== "tui") {
68
+ return await ctx.ui.editor(`${title} — Enter: ${action}; Esc: ${cancel} (not an upload)`,
69
+ `${body}\n\nViewer only. Enter navigates; it does not upload. Edits are discarded.`) !== undefined;
70
+ }
71
+ const lib = await import("@earendil-works/pi-tui");
72
+ return ctx.ui.custom<boolean>((tui, theme, bindings, done) =>
73
+ createProblemView(lib, tui, theme, bindings, done, title, body, action, cancel));
74
+ }
package/problems.ts CHANGED
@@ -1,10 +1,11 @@
1
1
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
2
  import { resolveApiOrigin } from "./config.ts";
3
+ import { showProblemView } from "./problem-view.ts";
3
4
 
4
5
  export const MAX_PROBLEM_BODY_BYTES = 64 * 1024;
5
6
  const MAX_STRING_CHARACTERS = 1000;
6
7
  const UNAVAILABLE = "Problem list unavailable. Check the configured server and connection, then try /golf-problems again.";
7
- const ADVISORY = "Informational only: browsing does not start or bind an attempt. Upload checks remain authoritative. Any edits in this view are discarded.";
8
+ const ADVISORY = "Informational only: browsing does not start or bind an attempt. Upload checks remain authoritative. Nothing is uploaded by browsing.";
8
9
 
9
10
  interface Problem {
10
11
  slug: string;
@@ -72,7 +73,7 @@ async function readCatalogue(response: Response): Promise<Catalogue | undefined>
72
73
  let browsing = false;
73
74
  export async function browseProblems(
74
75
  args: string,
75
- ctx: Pick<ExtensionContext, "ui" | "hasUI">,
76
+ ctx: Pick<ExtensionContext, "ui" | "hasUI" | "mode">,
76
77
  options: ProblemOptions = {},
77
78
  ): Promise<Result> {
78
79
  if (args.trim()) return { message: "Usage: /golf-problems", type: "info" };
@@ -94,22 +95,21 @@ export async function browseProblems(
94
95
  if (!data) return { message: UNAVAILABLE, type: "error" };
95
96
  const rules = [`Rules version: ${data.rules_version}`, "", ...data.rules, "", ADVISORY];
96
97
  if (!data.problems.length) rules.push("", "No problems are currently listed.");
97
- // A temporary plain-text editor is supported in TUI and RPC and can scroll
98
- // long guidance. Never paste its result into the main editor or session.
99
- await ctx.ui.editor("Agent Golf rules — edits discarded", rules.join("\n"));
100
- if (data.problems.length) {
98
+ const continued = await showProblemView(ctx, "Agent Golf rules", rules.join("\n"),
99
+ data.problems.length ? "view problems" : "close", "close");
100
+ if (continued && data.problems.length) {
101
101
  const labels = data.problems.map((p) => `${p.title.replace(/[\n\t]/g, " ")} (${p.slug})`);
102
102
  while (true) {
103
- const selected = await ctx.ui.select("Agent Golf problems — advisory; Esc to close", labels);
103
+ const selected = await ctx.ui.select("Agent Golf problems — choose to view details", labels);
104
104
  const index = selected === undefined ? -1 : labels.indexOf(selected);
105
105
  if (index < 0) break;
106
106
  const problem = data.problems[index];
107
- await ctx.ui.editor("Problem details — edits discarded", [
107
+ await showProblemView(ctx, "Problem details", [
108
108
  problem.title, `Problem: ${problem.slug}`, problem.challenge_url,
109
109
  `Model: ${problem.model === "any" ? "any model, one per session" : problem.model}`,
110
110
  `Thinking level: ${problem.thinking_level === "any" ? "any thinking level" : problem.thinking_level}`,
111
111
  "", ...problem.instructions, "", ADVISORY,
112
- ].join("\n"));
112
+ ].join("\n"), "back to problems", "back to problems");
113
113
  }
114
114
  }
115
115
  return { message: "Problem browsing closed. No attempt or setup was changed.", type: "info" };