@vzlentin/harness 0.2.1 → 0.2.2

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/setup.js +46 -2
  2. package/package.json +1 -1
package/dist/setup.js CHANGED
@@ -6,10 +6,16 @@ import { spawnSync } from "node:child_process";
6
6
  import { createHash } from "node:crypto";
7
7
  import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
8
8
  import { homedir } from "node:os";
9
- import { basename, delimiter, dirname, join, sep } from "node:path";
9
+ import { basename, delimiter, dirname, join, resolve, sep } from "node:path";
10
+ import { fileURLToPath } from "node:url";
10
11
  import { Type } from "typebox";
11
12
  import { Value } from "typebox/value";
12
13
  const PackageJson = Type.Object({ name: Type.String(), version: Type.String() });
14
+ const PiSettings = Type.Object({
15
+ packages: Type.Optional(Type.Array(Type.Union([Type.String(), Type.Object({ source: Type.String() })]))),
16
+ });
17
+ /** The prefixes of the Pi package sources that are not local paths, as Pi reads them. */
18
+ const REMOTE = ["npm:", "git:", "github:", "http:", "https:", "ssh:", "builtin:"];
13
19
  /**
14
20
  * Registers `<packageDir>/pi` in Pi when `packageDir` is a top-level global npm install. It writes its messages to
15
21
  * stderr, and it does not throw for a Pi failure: it writes a warning with the command to run. It writes the stamp
@@ -68,10 +74,15 @@ function writeStamp(installed) {
68
74
  mkdirSync(dirname(path), { recursive: true });
69
75
  writeFileSync(path, `${installed.dir}\n${installed.json.version}\n`);
70
76
  }
71
- /** False when `pi install` failed. */
77
+ /**
78
+ * False when `pi install` failed. It runs no `pi` when Pi already lists the extension, as `pi install` would write the
79
+ * entry again in its own form, relative to the agent directory.
80
+ */
72
81
  function registerExtension({ dir, json }) {
73
82
  const label = basename(json.name);
74
83
  const extension = join(dir, "pi");
84
+ if (piLists(extension))
85
+ return true;
75
86
  return runPi(label, ["install", extension], `To add the Pi extension of ${label}, run: pi install ${extension}`);
76
87
  }
77
88
  /** Runs `pi`, with a warning when it is missing or fails. False when it failed; true when it worked or is missing. */
@@ -92,6 +103,39 @@ function runPi(label, args, later) {
92
103
  }
93
104
  return true;
94
105
  }
106
+ /**
107
+ * Whether the `packages` of Pi's global settings list the folder `extension` as a local path, absolute or relative to
108
+ * the agent directory, with `~` or as a `file:` URL, as Pi resolves them.
109
+ */
110
+ function piLists(extension) {
111
+ const env = process.env.PI_CODING_AGENT_DIR;
112
+ const agentDir = env ? expandHome(env) : join(homedir(), ".pi", "agent");
113
+ let settings;
114
+ try {
115
+ settings = JSON.parse(readFileSync(join(agentDir, "settings.json"), "utf8"));
116
+ }
117
+ catch {
118
+ return false;
119
+ }
120
+ if (!Value.Check(PiSettings, settings))
121
+ return false;
122
+ return (settings.packages ?? []).some((item) => {
123
+ const source = (Value.Check(Type.String(), item) ? item : item.source).trim();
124
+ if (REMOTE.some((prefix) => source.startsWith(prefix)))
125
+ return false;
126
+ const path = source.startsWith("file://") ? fileURLToPath(source) : resolve(agentDir, expandHome(source));
127
+ try {
128
+ return realpathSync(path) === extension;
129
+ }
130
+ catch {
131
+ return false;
132
+ }
133
+ });
134
+ }
135
+ /** `path` with a leading `~` replaced by the home directory. */
136
+ function expandHome(path) {
137
+ return path === "~" || path.startsWith("~/") ? join(homedir(), path.slice(1)) : path;
138
+ }
95
139
  /** The `PATH` without the `node_modules/.bin` folders that npm adds: pi-coding-agent there has its own `pi`. */
96
140
  function userPath() {
97
141
  return (process.env.PATH ?? "")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vzlentin/harness",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "A durable agent harness on Pi, starting with observable, persistent subagents",
5
5
  "repository": {
6
6
  "type": "git",