ugly-app 0.1.744 → 0.1.745

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,2 +1,2 @@
1
- export declare const CLI_VERSION = "0.1.744";
1
+ export declare const CLI_VERSION = "0.1.745";
2
2
  //# sourceMappingURL=version.d.ts.map
@@ -1,3 +1,3 @@
1
1
  // Auto-generated by prebuild — do not edit manually
2
- export const CLI_VERSION = "0.1.744";
2
+ export const CLI_VERSION = "0.1.745";
3
3
  //# sourceMappingURL=version.js.map
@@ -12,5 +12,4 @@ export { CapabilityGate, type GateConfig, PermissionDenied } from './permissions
12
12
  export { GrantStore, type GrantSnapshot } from './permissions/grants.js';
13
13
  export { isWithinScope, originScopeDir, resolveScopedPath, ScopeViolation } from './permissions/scope.js';
14
14
  export { type CapabilityRequest, EMPTY_GRANT, type OriginGrant, type PermissionDecision, } from './permissions/types.js';
15
- export { type BinaryProvisionConfig, ensureBundledTool, findBundledTool, resolveBundledBinary, } from './binaries.js';
16
15
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/native/server/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,QAAQ,EAAE,KAAK,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,KAAK,UAAU,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC1F,OAAO,EAAE,UAAU,EAAE,KAAK,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACzE,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC1G,OAAO,EACL,KAAK,iBAAiB,EACtB,WAAW,EACX,KAAK,WAAW,EAChB,KAAK,kBAAkB,GACxB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EACL,KAAK,qBAAqB,EAC1B,iBAAiB,EACjB,eAAe,EACf,oBAAoB,GACrB,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/native/server/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,QAAQ,EAAE,KAAK,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,KAAK,UAAU,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC1F,OAAO,EAAE,UAAU,EAAE,KAAK,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACzE,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC1G,OAAO,EACL,KAAK,iBAAiB,EACtB,WAAW,EACX,KAAK,WAAW,EAChB,KAAK,kBAAkB,GACxB,MAAM,wBAAwB,CAAC"}
@@ -12,5 +12,4 @@ export { CapabilityGate, PermissionDenied } from './permissions/gate.js';
12
12
  export { GrantStore } from './permissions/grants.js';
13
13
  export { isWithinScope, originScopeDir, resolveScopedPath, ScopeViolation } from './permissions/scope.js';
14
14
  export { EMPTY_GRANT, } from './permissions/types.js';
15
- export { ensureBundledTool, findBundledTool, resolveBundledBinary, } from './binaries.js';
16
15
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/native/server/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,QAAQ,EAAmB,MAAM,wBAAwB,CAAC;AACnE,OAAO,EAAE,cAAc,EAAmB,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC1F,OAAO,EAAE,UAAU,EAAsB,MAAM,yBAAyB,CAAC;AACzE,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC1G,OAAO,EAEL,WAAW,GAGZ,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAEL,iBAAiB,EACjB,eAAe,EACf,oBAAoB,GACrB,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/native/server/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,QAAQ,EAAmB,MAAM,wBAAwB,CAAC;AACnE,OAAO,EAAE,cAAc,EAAmB,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC1F,OAAO,EAAE,UAAU,EAAsB,MAAM,yBAAyB,CAAC;AACzE,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC1G,OAAO,EAEL,WAAW,GAGZ,MAAM,wBAAwB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ugly-app",
3
- "version": "0.1.744",
3
+ "version": "0.1.745",
4
4
  "type": "module",
5
5
  "comment:files": "Allowlist what ships to npm. dist = runtime; src = sourcemap targets (dist/*.js.map reference ../../src/); templates = CLI scaffold. Everything else at repo root (.pgdata local Postgres, coverage, assets/icons sources, test/, test-results/) is excluded by omission. The !negations strip the scaffold's installed deps + cruft (templates/node_modules is 200MB+ and must never ship). package.json/README/LICENSE ship automatically.",
6
6
  "files": [
@@ -1,2 +1,2 @@
1
1
  // Auto-generated by prebuild — do not edit manually
2
- export const CLI_VERSION = "0.1.744";
2
+ export const CLI_VERSION = "0.1.745";
@@ -18,10 +18,3 @@ export {
18
18
  type OriginGrant,
19
19
  type PermissionDecision,
20
20
  } from './permissions/types.js';
21
-
22
- export {
23
- type BinaryProvisionConfig,
24
- ensureBundledTool,
25
- findBundledTool,
26
- resolveBundledBinary,
27
- } from './binaries.js';
@@ -1,23 +0,0 @@
1
- export interface BinaryProvisionConfig {
2
- /** Where bundled tools are installed, e.g. `~/.ugly-studio/binaries`. */
3
- root: string;
4
- /** Base URL hosting `binaries-manifest.json` (the R2 mirror). */
5
- manifestBase: string;
6
- }
7
- /** The installed executable for `tool` under `root`, version-agnostic, or null. */
8
- export declare function findBundledTool(root: string, tool: string): string | null;
9
- /**
10
- * Resolve a tool name to a path: a downloaded binary under `cfg.root` when
11
- * present, else the bare name so the daemon's spawn resolves it on the system
12
- * PATH. `downloadable` lists tools the host actually mirrors (others always
13
- * fall through to PATH without a directory probe).
14
- */
15
- export declare function resolveBundledBinary(name: string, cfg: Pick<BinaryProvisionConfig, 'root'>, downloadable?: readonly string[]): string;
16
- /**
17
- * Best-effort: download `tool` from the mirror into `cfg.root` on first use.
18
- * Never fetches from the upstream project at runtime (the binary is mirrored to
19
- * R2 by the host's publish step). Never throws (falls back to a system tool);
20
- * idempotent + single-flight per tool.
21
- */
22
- export declare function ensureBundledTool(tool: string, cfg: BinaryProvisionConfig): Promise<void>;
23
- //# sourceMappingURL=binaries.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"binaries.d.ts","sourceRoot":"","sources":["../../../src/native/server/binaries.ts"],"names":[],"mappings":"AAqBA,MAAM,WAAW,qBAAqB;IACpC,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,iEAAiE;IACjE,YAAY,EAAE,MAAM,CAAC;CACtB;AAgBD,mFAAmF;AACnF,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAWzE;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,IAAI,CAAC,qBAAqB,EAAE,MAAM,CAAC,EACxC,YAAY,GAAE,SAAS,MAAM,EAAW,GACvC,MAAM,CAMR;AAsBD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsDzF"}
@@ -1,142 +0,0 @@
1
- /**
2
- * Bundled-binary provisioning for the native daemon (lifted from Ugly Studio so
3
- * the daemon AND the test harness share one implementation). A host (Ugly
4
- * Studio) bundles a small set of tools by downloading them from its own R2
5
- * mirror on first launch; everything else resolves on the system PATH.
6
- *
7
- * Pure fs/child_process/fetch (no Electron) and fully parameterized — the
8
- * install root + manifest mirror are injected via `BinaryProvisionConfig`, so
9
- * nothing here hardcodes a particular app's paths or hostnames.
10
- *
11
- * The mirror exposes `<manifestBase>/binaries-manifest.json`:
12
- * { "<tool>": { "version": "x", "archives": { "<platform>-<arch>": { "url" } } } }
13
- * One `tar -xf` covers every platform's asset (bsdtar on mac/win unpacks zip).
14
- */
15
- import { basename, join } from 'node:path';
16
- import { chmodSync, existsSync, mkdirSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
17
- import { execFile } from 'node:child_process';
18
- import { promisify } from 'node:util';
19
- const execFileP = promisify(execFile);
20
- const exeName = (tool) => (process.platform === 'win32' ? `${tool}.exe` : tool);
21
- function safeReaddir(dir) {
22
- try {
23
- return readdirSync(dir);
24
- }
25
- catch {
26
- return [];
27
- }
28
- }
29
- /** The installed executable for `tool` under `root`, version-agnostic, or null. */
30
- export function findBundledTool(root, tool) {
31
- const toolRoot = join(root, tool);
32
- if (!existsSync(toolRoot))
33
- return null;
34
- for (const ver of safeReaddir(toolRoot)) {
35
- const verDir = join(toolRoot, ver);
36
- for (const sub of safeReaddir(verDir)) {
37
- const p = join(verDir, sub, 'bin', exeName(tool));
38
- if (existsSync(p))
39
- return p;
40
- }
41
- }
42
- return null;
43
- }
44
- /**
45
- * Resolve a tool name to a path: a downloaded binary under `cfg.root` when
46
- * present, else the bare name so the daemon's spawn resolves it on the system
47
- * PATH. `downloadable` lists tools the host actually mirrors (others always
48
- * fall through to PATH without a directory probe).
49
- */
50
- export function resolveBundledBinary(name, cfg, downloadable = ['gh']) {
51
- if (downloadable.includes(name)) {
52
- const found = findBundledTool(cfg.root, name);
53
- if (found)
54
- return found;
55
- }
56
- return name;
57
- }
58
- /** Look up a tool's archive for this platform in the mirror's manifest. */
59
- async function toolFromManifest(cfg, tool) {
60
- try {
61
- const res = await fetch(`${cfg.manifestBase}/binaries-manifest.json`, { cache: 'no-store' });
62
- if (!res.ok)
63
- return null;
64
- const m = (await res.json());
65
- const entry = m[tool];
66
- const archive = entry?.archives[`${process.platform}-${process.arch}`];
67
- if (!entry || !archive?.url)
68
- return null;
69
- return { version: entry.version, url: archive.url };
70
- }
71
- catch {
72
- return null;
73
- }
74
- }
75
- const ensuring = new Map();
76
- /**
77
- * Best-effort: download `tool` from the mirror into `cfg.root` on first use.
78
- * Never fetches from the upstream project at runtime (the binary is mirrored to
79
- * R2 by the host's publish step). Never throws (falls back to a system tool);
80
- * idempotent + single-flight per tool.
81
- */
82
- export function ensureBundledTool(tool, cfg) {
83
- const key = `${cfg.root}:${tool}`;
84
- const inflight = ensuring.get(key);
85
- if (inflight)
86
- return inflight;
87
- const run = (async () => {
88
- // Every failure below leaves the tool UNINSTALLED and is best-effort (spawns
89
- // fall back to the system PATH). But when there's no system fallback either —
90
- // the common case for pnpm/node on a fresh host — the miss surfaces much later
91
- // as a cryptic `command not found` / ENOENT (-2) at spawn time with no trace.
92
- // So log each failure to console.error/warn (the desktop shells ship these to
93
- // studio.ugly.bot's errorLog) with the tool + reason, making bundle-binary
94
- // provisioning issues findable remotely instead of silent.
95
- const plat = `${process.platform}-${process.arch}`;
96
- try {
97
- if (findBundledTool(cfg.root, tool))
98
- return;
99
- const man = await toolFromManifest(cfg, tool);
100
- if (!man) {
101
- console.warn(`[binaries] ${tool}: not in the mirror manifest for ${plat} — falling back to system PATH (spawns will fail if it isn't installed)`);
102
- return;
103
- }
104
- const dir = join(cfg.root, tool, man.version);
105
- mkdirSync(dir, { recursive: true });
106
- const archive = join(dir, basename(new URL(man.url).pathname));
107
- const res = await fetch(man.url);
108
- if (!res.ok) {
109
- console.error(`[binaries] ${tool} ${man.version}: download failed HTTP ${res.status} ${res.statusText} from ${man.url} — tool will be UNAVAILABLE`);
110
- return;
111
- }
112
- writeFileSync(archive, Buffer.from(await res.arrayBuffer()));
113
- await execFileP('tar', ['-xf', archive, '-C', dir]);
114
- try {
115
- rmSync(archive);
116
- }
117
- catch {
118
- /* leave it */
119
- }
120
- const bin = findBundledTool(cfg.root, tool);
121
- if (!bin) {
122
- console.error(`[binaries] ${tool} ${man.version}: archive extracted to ${dir} but no runnable binary was found (corrupt download or unexpected layout) — tool will be UNAVAILABLE`);
123
- return;
124
- }
125
- if (process.platform !== 'win32') {
126
- try {
127
- chmodSync(bin, 0o755);
128
- }
129
- catch (e) {
130
- console.warn(`[binaries] ${tool}: could not chmod +x ${bin} (${e?.message ?? e}) — spawns may fail with EACCES`);
131
- }
132
- }
133
- }
134
- catch (e) {
135
- // Network / tar-extract / disk failure. Previously swallowed entirely.
136
- console.error(`[binaries] ${tool} (${plat}): provisioning failed — ${e?.message ?? String(e)} — tool will be UNAVAILABLE, spawns fall back to system PATH`);
137
- }
138
- })();
139
- ensuring.set(key, run);
140
- return run;
141
- }
142
- //# sourceMappingURL=binaries.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"binaries.js","sourceRoot":"","sources":["../../../src/native/server/binaries.ts"],"names":[],"mappings":"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"}
@@ -1,155 +0,0 @@
1
- /**
2
- * Bundled-binary provisioning for the native daemon (lifted from Ugly Studio so
3
- * the daemon AND the test harness share one implementation). A host (Ugly
4
- * Studio) bundles a small set of tools by downloading them from its own R2
5
- * mirror on first launch; everything else resolves on the system PATH.
6
- *
7
- * Pure fs/child_process/fetch (no Electron) and fully parameterized — the
8
- * install root + manifest mirror are injected via `BinaryProvisionConfig`, so
9
- * nothing here hardcodes a particular app's paths or hostnames.
10
- *
11
- * The mirror exposes `<manifestBase>/binaries-manifest.json`:
12
- * { "<tool>": { "version": "x", "archives": { "<platform>-<arch>": { "url" } } } }
13
- * One `tar -xf` covers every platform's asset (bsdtar on mac/win unpacks zip).
14
- */
15
- import { basename, join } from 'node:path';
16
- import { chmodSync, existsSync, mkdirSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
17
- import { execFile } from 'node:child_process';
18
- import { promisify } from 'node:util';
19
-
20
- const execFileP = promisify(execFile);
21
-
22
- export interface BinaryProvisionConfig {
23
- /** Where bundled tools are installed, e.g. `~/.ugly-studio/binaries`. */
24
- root: string;
25
- /** Base URL hosting `binaries-manifest.json` (the R2 mirror). */
26
- manifestBase: string;
27
- }
28
-
29
- interface BinariesManifest {
30
- [tool: string]: { version: string; archives: Record<string, { url: string }> } | undefined;
31
- }
32
-
33
- const exeName = (tool: string): string => (process.platform === 'win32' ? `${tool}.exe` : tool);
34
-
35
- function safeReaddir(dir: string): string[] {
36
- try {
37
- return readdirSync(dir);
38
- } catch {
39
- return [];
40
- }
41
- }
42
-
43
- /** The installed executable for `tool` under `root`, version-agnostic, or null. */
44
- export function findBundledTool(root: string, tool: string): string | null {
45
- const toolRoot = join(root, tool);
46
- if (!existsSync(toolRoot)) return null;
47
- for (const ver of safeReaddir(toolRoot)) {
48
- const verDir = join(toolRoot, ver);
49
- for (const sub of safeReaddir(verDir)) {
50
- const p = join(verDir, sub, 'bin', exeName(tool));
51
- if (existsSync(p)) return p;
52
- }
53
- }
54
- return null;
55
- }
56
-
57
- /**
58
- * Resolve a tool name to a path: a downloaded binary under `cfg.root` when
59
- * present, else the bare name so the daemon's spawn resolves it on the system
60
- * PATH. `downloadable` lists tools the host actually mirrors (others always
61
- * fall through to PATH without a directory probe).
62
- */
63
- export function resolveBundledBinary(
64
- name: string,
65
- cfg: Pick<BinaryProvisionConfig, 'root'>,
66
- downloadable: readonly string[] = ['gh'],
67
- ): string {
68
- if (downloadable.includes(name)) {
69
- const found = findBundledTool(cfg.root, name);
70
- if (found) return found;
71
- }
72
- return name;
73
- }
74
-
75
- /** Look up a tool's archive for this platform in the mirror's manifest. */
76
- async function toolFromManifest(
77
- cfg: BinaryProvisionConfig,
78
- tool: string,
79
- ): Promise<{ version: string; url: string } | null> {
80
- try {
81
- const res = await fetch(`${cfg.manifestBase}/binaries-manifest.json`, { cache: 'no-store' });
82
- if (!res.ok) return null;
83
- const m = (await res.json()) as BinariesManifest;
84
- const entry = m[tool];
85
- const archive = entry?.archives[`${process.platform}-${process.arch}`];
86
- if (!entry || !archive?.url) return null;
87
- return { version: entry.version, url: archive.url };
88
- } catch {
89
- return null;
90
- }
91
- }
92
-
93
- const ensuring = new Map<string, Promise<void>>();
94
-
95
- /**
96
- * Best-effort: download `tool` from the mirror into `cfg.root` on first use.
97
- * Never fetches from the upstream project at runtime (the binary is mirrored to
98
- * R2 by the host's publish step). Never throws (falls back to a system tool);
99
- * idempotent + single-flight per tool.
100
- */
101
- export function ensureBundledTool(tool: string, cfg: BinaryProvisionConfig): Promise<void> {
102
- const key = `${cfg.root}:${tool}`;
103
- const inflight = ensuring.get(key);
104
- if (inflight) return inflight;
105
- const run = (async () => {
106
- // Every failure below leaves the tool UNINSTALLED and is best-effort (spawns
107
- // fall back to the system PATH). But when there's no system fallback either —
108
- // the common case for pnpm/node on a fresh host — the miss surfaces much later
109
- // as a cryptic `command not found` / ENOENT (-2) at spawn time with no trace.
110
- // So log each failure to console.error/warn (the desktop shells ship these to
111
- // studio.ugly.bot's errorLog) with the tool + reason, making bundle-binary
112
- // provisioning issues findable remotely instead of silent.
113
- const plat = `${process.platform}-${process.arch}`;
114
- try {
115
- if (findBundledTool(cfg.root, tool)) return;
116
- const man = await toolFromManifest(cfg, tool);
117
- if (!man) {
118
- console.warn(`[binaries] ${tool}: not in the mirror manifest for ${plat} — falling back to system PATH (spawns will fail if it isn't installed)`);
119
- return;
120
- }
121
- const dir = join(cfg.root, tool, man.version);
122
- mkdirSync(dir, { recursive: true });
123
- const archive = join(dir, basename(new URL(man.url).pathname));
124
- const res = await fetch(man.url);
125
- if (!res.ok) {
126
- console.error(`[binaries] ${tool} ${man.version}: download failed HTTP ${res.status} ${res.statusText} from ${man.url} — tool will be UNAVAILABLE`);
127
- return;
128
- }
129
- writeFileSync(archive, Buffer.from(await res.arrayBuffer()));
130
- await execFileP('tar', ['-xf', archive, '-C', dir]);
131
- try {
132
- rmSync(archive);
133
- } catch {
134
- /* leave it */
135
- }
136
- const bin = findBundledTool(cfg.root, tool);
137
- if (!bin) {
138
- console.error(`[binaries] ${tool} ${man.version}: archive extracted to ${dir} but no runnable binary was found (corrupt download or unexpected layout) — tool will be UNAVAILABLE`);
139
- return;
140
- }
141
- if (process.platform !== 'win32') {
142
- try {
143
- chmodSync(bin, 0o755);
144
- } catch (e) {
145
- console.warn(`[binaries] ${tool}: could not chmod +x ${bin} (${(e as Error)?.message ?? e}) — spawns may fail with EACCES`);
146
- }
147
- }
148
- } catch (e) {
149
- // Network / tar-extract / disk failure. Previously swallowed entirely.
150
- console.error(`[binaries] ${tool} (${plat}): provisioning failed — ${(e as Error)?.message ?? String(e)} — tool will be UNAVAILABLE, spawns fall back to system PATH`);
151
- }
152
- })();
153
- ensuring.set(key, run);
154
- return run;
155
- }