@timurproko/a1 0.1.8-dev.130 → 0.1.8-dev.134
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 +14 -0
- package/bin/module-identity.js +1 -1
- package/bin/pi-tui.d.ts +1 -1
- package/bin/pi-tui.js +8 -4
- package/bin/sync-pi-tui-proxy.js +97 -0
- package/dist/cli/version-stats.d.ts +6 -0
- package/dist/cli/version-stats.js +41 -7
- package/dist/native/darwin-arm64/manifest.json +1 -1
- package/dist/native/linux-x64/manifest.json +3 -3
- package/dist/native/linux-x64/process-guardian +0 -0
- package/dist/native/win32-x64/manifest.json +2 -2
- package/dist/native/win32-x64/process-guardian.exe +0 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -6,6 +6,20 @@
|
|
|
6
6
|
npm install --global @timurproko/a1@latest
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
+
Any channel can also be installed or updated directly with npm, without the
|
|
10
|
+
`a1 update` commands:
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
# dev channel (next tag)
|
|
14
|
+
npm install -g @timurproko/a1@next
|
|
15
|
+
|
|
16
|
+
# stable release
|
|
17
|
+
npm install -g @timurproko/a1@latest
|
|
18
|
+
|
|
19
|
+
# exact version
|
|
20
|
+
npm install -g @timurproko/a1@0.1.8-dev.107
|
|
21
|
+
```
|
|
22
|
+
|
|
9
23
|
## Use
|
|
10
24
|
|
|
11
25
|
```sh
|
package/bin/module-identity.js
CHANGED
|
@@ -87,7 +87,7 @@ function followProxyReExport(resolvedPath) {
|
|
|
87
87
|
* no CommonJS condition, so an ordinary require cannot name it. From there a
|
|
88
88
|
* require resolves pi-tui, which publishes no `exports` map at all.
|
|
89
89
|
*/
|
|
90
|
-
function resolvePinnedPiTui(packageRoot) {
|
|
90
|
+
export function resolvePinnedPiTui(packageRoot) {
|
|
91
91
|
let directory = packageRoot;
|
|
92
92
|
while (true) {
|
|
93
93
|
const candidate = join(directory, "node_modules", "@earendil-works", "pi-coding-agent");
|
package/bin/pi-tui.d.ts
CHANGED
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
* file, so TypeScript reads this declaration and follows it to pinned Pi's
|
|
4
4
|
* nested pi-tui copy — the same module the proxy re-exports at runtime.
|
|
5
5
|
*/
|
|
6
|
-
export * from "../node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-tui/dist/index.
|
|
6
|
+
export * from "../node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-tui/dist/index.d.ts";
|
package/bin/pi-tui.js
CHANGED
|
@@ -12,10 +12,14 @@
|
|
|
12
12
|
* plain import specifier in a module carries no such restriction, so the hop
|
|
13
13
|
* through this file is what makes the nested copy nameable from the manifest.
|
|
14
14
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
15
|
+
* npm does not materialize one layout: `npm ci` keeps pinned Pi's shrinkwrapped
|
|
16
|
+
* nested copy, while a global install hoists pi-tui to the root and produces no
|
|
17
|
+
* nested copy. bin/sync-pi-tui-proxy.js therefore rewrites this file's one
|
|
18
|
+
* re-export on postinstall to whatever pinned Pi resolves in the tree npm
|
|
19
|
+
* actually built; the path below is the dev-checkout (`npm ci`) shape. When the
|
|
20
|
+
* target is missing anyway, this import fails loudly at launch instead of
|
|
21
|
+
* silently rendering without extension chrome, and bin/module-identity.js
|
|
22
|
+
* reports the same condition before the composition loads.
|
|
19
23
|
*
|
|
20
24
|
* This lives in bin/ (shipped, plain JS) because it names a path inside
|
|
21
25
|
* node_modules, which the Pi API boundary policy rightly forbids ordinary
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Point the `#pi-tui` proxy at the copy pinned Pi actually resolves.
|
|
3
|
+
*
|
|
4
|
+
* The proxy (bin/pi-tui.js) re-exports one static path, but npm does not
|
|
5
|
+
* materialize one layout: `npm ci` against A1's lockfile keeps pinned Pi's
|
|
6
|
+
* shrinkwrapped nested pi-tui copy, while a global `npm install -g` of the
|
|
7
|
+
* published tarball hoists pi-tui to A1's node_modules root and materializes
|
|
8
|
+
* no nested copy at all. A static path can only name one of those layouts —
|
|
9
|
+
* whichever the other layout lacks, the proxy's import fails at launch and
|
|
10
|
+
* extension UI never renders.
|
|
11
|
+
*
|
|
12
|
+
* This script runs on postinstall, asks Node what pinned Pi resolves
|
|
13
|
+
* `@earendil-works/pi-tui` to — the copy Pi's extension loader hands to
|
|
14
|
+
* extensions — and rewrites the proxy's single re-export (and its declaration
|
|
15
|
+
* twin) to that file. Whatever tree npm built, the proxy names the module Pi
|
|
16
|
+
* uses, so A1's renderer and Pi's extensions share one class identity.
|
|
17
|
+
*
|
|
18
|
+
* It never fails an install: a tree in which pinned Pi or its pi-tui cannot
|
|
19
|
+
* be resolved is reported on stderr and left untouched, and launch's
|
|
20
|
+
* module-identity check names the same condition to the user.
|
|
21
|
+
*
|
|
22
|
+
* This lives in bin/ (shipped, plain JS) because it inspects and names
|
|
23
|
+
* dependency resolution, which the Pi API boundary policy rightly forbids
|
|
24
|
+
* ordinary production code from touching.
|
|
25
|
+
*/
|
|
26
|
+
import { readFileSync, realpathSync, writeFileSync } from "node:fs";
|
|
27
|
+
import { dirname, join, relative } from "node:path";
|
|
28
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
29
|
+
import { resolvePinnedPiTui } from "./module-identity.js";
|
|
30
|
+
|
|
31
|
+
const RE_EXPORT_LINE = /^export \* from "[^"]+";$/m;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Rewrite the proxy pair under packageRoot/bin to re-export the pi-tui module
|
|
35
|
+
* pinned Pi resolves. Returns a discriminated outcome; never throws.
|
|
36
|
+
*/
|
|
37
|
+
export function syncPiTuiProxy(packageRoot) {
|
|
38
|
+
// resolvePinnedPiTui answers with a canonical real path, so the directory the
|
|
39
|
+
// relative specifier is computed from must be canonical too (Windows short
|
|
40
|
+
// names, symlinked installs).
|
|
41
|
+
const canonicalRoot = canonical(packageRoot);
|
|
42
|
+
let target;
|
|
43
|
+
try {
|
|
44
|
+
target = resolvePinnedPiTui(canonicalRoot);
|
|
45
|
+
} catch (error) {
|
|
46
|
+
return { kind: "unresolved", message: error instanceof Error ? error.message : String(error) };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const binDirectory = join(canonicalRoot, "bin");
|
|
50
|
+
const runtimeSpecifier = relativeSpecifier(binDirectory, target);
|
|
51
|
+
const declarationSpecifier = runtimeSpecifier.replace(/\.js$/, ".d.ts").replace(/\.d\.d\.ts$/, ".d.ts");
|
|
52
|
+
const changed = [];
|
|
53
|
+
for (const [file, specifier] of [
|
|
54
|
+
["pi-tui.js", runtimeSpecifier],
|
|
55
|
+
["pi-tui.d.ts", declarationSpecifier],
|
|
56
|
+
]) {
|
|
57
|
+
if (rewriteReExport(join(binDirectory, file), specifier)) changed.push(file);
|
|
58
|
+
}
|
|
59
|
+
return { kind: "synced", target, changed };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function canonical(path) {
|
|
63
|
+
try {
|
|
64
|
+
return realpathSync.native(path);
|
|
65
|
+
} catch {
|
|
66
|
+
return path;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function relativeSpecifier(fromDirectory, toFile) {
|
|
71
|
+
const specifier = relative(fromDirectory, toFile).split("\\").join("/");
|
|
72
|
+
return specifier.startsWith(".") ? specifier : `./${specifier}`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Replace the file's single re-export line; returns whether the file changed. */
|
|
76
|
+
function rewriteReExport(file, specifier) {
|
|
77
|
+
let source;
|
|
78
|
+
try {
|
|
79
|
+
source = readFileSync(file, "utf8");
|
|
80
|
+
} catch {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
const line = `export * from "${specifier}";`;
|
|
84
|
+
if (source.includes(line) || !RE_EXPORT_LINE.test(source)) return false;
|
|
85
|
+
writeFileSync(file, source.replace(RE_EXPORT_LINE, line));
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const invokedDirectly = process.argv[1] !== undefined
|
|
90
|
+
&& import.meta.url === pathToFileURL(process.argv[1]).href;
|
|
91
|
+
if (invokedDirectly) {
|
|
92
|
+
const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
93
|
+
const outcome = syncPiTuiProxy(packageRoot);
|
|
94
|
+
if (outcome.kind === "unresolved") {
|
|
95
|
+
process.stderr.write(`a1: could not point #pi-tui at pinned Pi's copy (${outcome.message}); extension UI may not render.\n`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -3,6 +3,11 @@ interface VersionProcessResult {
|
|
|
3
3
|
readonly stdout: string;
|
|
4
4
|
}
|
|
5
5
|
type VersionProcessRunner = (command: string, arguments_: readonly string[]) => Promise<VersionProcessResult>;
|
|
6
|
+
type RegistryFetcher = (url: string) => Promise<{
|
|
7
|
+
readonly ok: boolean;
|
|
8
|
+
readonly status: number;
|
|
9
|
+
text(): Promise<string>;
|
|
10
|
+
}>;
|
|
6
11
|
interface VersionOutput {
|
|
7
12
|
stdout(message: string): void;
|
|
8
13
|
stderr(message: string): void;
|
|
@@ -11,6 +16,7 @@ export interface VersionStatsOptions {
|
|
|
11
16
|
readonly packageRoot: string;
|
|
12
17
|
readonly output?: VersionOutput;
|
|
13
18
|
readonly runner?: VersionProcessRunner;
|
|
19
|
+
readonly fetcher?: RegistryFetcher;
|
|
14
20
|
}
|
|
15
21
|
export declare function runVersionStats(options: VersionStatsOptions): Promise<number>;
|
|
16
22
|
export {};
|
|
@@ -20,13 +20,24 @@ export async function runVersionStats(options) {
|
|
|
20
20
|
output.stderr(`${PRODUCT_TEXT.diagnostic(`could not read its installed version: ${message(error)}`)}\n`);
|
|
21
21
|
return 1;
|
|
22
22
|
}
|
|
23
|
-
const remote = await queryDistTags(runner);
|
|
23
|
+
const remote = await queryDistTags(runner, options.fetcher ?? defaultRegistryFetcher);
|
|
24
24
|
output.stdout(`Current: ${installed}\nDevelop: ${remote.develop ?? "unavailable"}\nRelease: ${remote.release ?? "unavailable"}\n`);
|
|
25
25
|
if (remote.error)
|
|
26
26
|
output.stderr(`${PRODUCT_TEXT.diagnostic(`could not resolve npm dist-tags: ${remote.error}`)}\n`);
|
|
27
27
|
return 0;
|
|
28
28
|
}
|
|
29
|
-
|
|
29
|
+
// npm view respects the user's .npmrc registry and proxy, so it stays primary; the direct
|
|
30
|
+
// registry fetch below covers npm CLI versions whose --json output shape we cannot parse.
|
|
31
|
+
async function queryDistTags(runner, fetcher) {
|
|
32
|
+
const fromNpm = await queryDistTagsViaNpm(runner);
|
|
33
|
+
if (fromNpm.error === null)
|
|
34
|
+
return fromNpm;
|
|
35
|
+
const fromRegistry = await queryDistTagsViaRegistry(fetcher);
|
|
36
|
+
if (fromRegistry.error === null)
|
|
37
|
+
return fromRegistry;
|
|
38
|
+
return unavailable(`${fromNpm.error}; registry fallback failed: ${fromRegistry.error}`);
|
|
39
|
+
}
|
|
40
|
+
async function queryDistTagsViaNpm(runner) {
|
|
30
41
|
let result;
|
|
31
42
|
try {
|
|
32
43
|
result = await runner("npm", ["view", PRODUCT_TEXT.packageName, "dist-tags", "--json"]);
|
|
@@ -37,12 +48,34 @@ async function queryDistTags(runner) {
|
|
|
37
48
|
if (result.code !== 0)
|
|
38
49
|
return unavailable(`npm exited with status ${result.code ?? "unknown"}`);
|
|
39
50
|
try {
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
51
|
+
const parsed = JSON.parse(result.stdout);
|
|
52
|
+
// npm 12 wraps `npm view <pkg> dist-tags --json` output in a one-element array; older npm returns the bare object.
|
|
53
|
+
const metadata = Array.isArray(parsed) && parsed.length === 1 ? parsed[0] : parsed;
|
|
54
|
+
return parseDistTags(metadata, "npm");
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
return unavailable(message(error));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async function queryDistTagsViaRegistry(fetcher) {
|
|
61
|
+
try {
|
|
62
|
+
const response = await fetcher(`https://registry.npmjs.org/-/package/${encodeURIComponent(PRODUCT_TEXT.packageName)}/dist-tags`);
|
|
63
|
+
if (!response.ok)
|
|
64
|
+
return unavailable(`registry responded with status ${response.status}`);
|
|
65
|
+
return parseDistTags(JSON.parse(await response.text()), "registry");
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
return unavailable(message(error));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function parseDistTags(metadata, source) {
|
|
72
|
+
if (typeof metadata !== "object" || metadata === null || Array.isArray(metadata)) {
|
|
73
|
+
return unavailable(`${source} returned a non-object dist-tags value`);
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
43
76
|
const tags = metadata;
|
|
44
|
-
const release = parseVersion(tags.latest,
|
|
45
|
-
const develop = tags.next === undefined ? null : parseVersion(tags.next,
|
|
77
|
+
const release = parseVersion(tags.latest, `${source} latest`);
|
|
78
|
+
const develop = tags.next === undefined ? null : parseVersion(tags.next, `${source} development channel`);
|
|
46
79
|
return { release, develop, error: null };
|
|
47
80
|
}
|
|
48
81
|
catch (error) {
|
|
@@ -52,6 +85,7 @@ async function queryDistTags(runner) {
|
|
|
52
85
|
function unavailable(error) {
|
|
53
86
|
return { release: null, develop: null, error };
|
|
54
87
|
}
|
|
88
|
+
const defaultRegistryFetcher = async (url) => await fetch(url, { headers: { accept: "application/json" }, signal: AbortSignal.timeout(10_000) });
|
|
55
89
|
function createVersionProcessRunner() {
|
|
56
90
|
return async (command, arguments_) => await new Promise((resolvePromise, rejectPromise) => {
|
|
57
91
|
const child = process.platform === "win32"
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"platform": "darwin",
|
|
6
6
|
"architecture": "arm64",
|
|
7
7
|
"capability": "unsupported",
|
|
8
|
-
"builtAt": "2026-08-
|
|
8
|
+
"builtAt": "2026-08-25T18:22:19.065Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian",
|
|
11
11
|
"sha256": "7524bf568992517f50ed53196c861c5edeba0d7275633bb4735ab45bd5963a28",
|
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
"platform": "linux",
|
|
6
6
|
"architecture": "x64",
|
|
7
7
|
"capability": "supported",
|
|
8
|
-
"builtAt": "2026-08-
|
|
8
|
+
"builtAt": "2026-08-25T18:22:33.863Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian",
|
|
11
|
-
"sha256": "
|
|
12
|
-
"size":
|
|
11
|
+
"sha256": "ee8a00eaaf79c707459bbbfb52518e9739314967049fbe5fa625f36ce33db9ee",
|
|
12
|
+
"size": 414568
|
|
13
13
|
},
|
|
14
14
|
"provenance": {
|
|
15
15
|
"language": "Rust",
|
|
Binary file
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
"platform": "win32",
|
|
6
6
|
"architecture": "x64",
|
|
7
7
|
"capability": "supported",
|
|
8
|
-
"builtAt": "2026-08-
|
|
8
|
+
"builtAt": "2026-08-25T18:23:18.046Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian.exe",
|
|
11
|
-
"sha256": "
|
|
11
|
+
"sha256": "2f13a4c73ed082a13cf9947be6f363c3c800eaf40e22ea9ae4674299d7f51cdd",
|
|
12
12
|
"size": 172544
|
|
13
13
|
},
|
|
14
14
|
"provenance": {
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@timurproko/a1",
|
|
3
|
-
"version": "0.1.8-dev.
|
|
3
|
+
"version": "0.1.8-dev.134",
|
|
4
4
|
"description": "Standalone terminal workspace for supervised native and managed agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "npm@11.13.0",
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"node": ">=22.19.0 <25"
|
|
22
22
|
},
|
|
23
23
|
"scripts": {
|
|
24
|
+
"postinstall": "node bin/sync-pi-tui-proxy.js",
|
|
24
25
|
"clean": "node scripts/clean.mjs",
|
|
25
26
|
"build": "npm run clean && tsc -p tsconfig.build.json && node scripts/development/build-process-guardian.mjs",
|
|
26
27
|
"build:process-guardian": "node scripts/development/build-process-guardian.mjs",
|