C1: skillify finds the browse SDK under its own host's install root

The template hardcoded ~/.claude/skills/gstack for the canonical
browse-client.ts, so external hosts read another host's install or nothing.
A new INSTALLED_ROOT resolver (sibling of INSTALLED_BIN_DIR) names the host's
global root, or the per-install root when setup renders for one.
This commit is contained in:
garrytan committed 2026-10-03 21:48:59 +00:00
1 parent ea0b4841ba
commit 424214ace7
3 files changed
+31 -2

No files matched your search

+1
View File
@@ -143,6 +143,7 @@ export const RESOLVERS: Record<string, ResolverFn> = {
// Literal bin dir for skills that skip the shared preamble, where
// $GSTACK_BIN is never set (#2906): the per-install root when rendering for
// one, else the host's default global root.
INSTALLED_ROOT: (ctx) => ctx.installRoot ? ctx.installRoot.replace(/\/+$/, '') : `~/${getHostConfig(ctx.host).globalRoot}`,
INSTALLED_BIN_DIR: (ctx) => ctx.installRoot ? `${ctx.installRoot.replace(/\/+$/, '')}/bin` : `~/${getHostConfig(ctx.host).globalRoot}/bin`,
FOREGROUND_DISPATCH_NOTE: () => FOREGROUND_DISPATCH_NOTE,
GBRAIN_CONTEXT_LOAD: generateGBrainContextLoad,
+2 -2
View File
@@ -220,7 +220,7 @@ Resolve the gstack install dir. Two reliable signals (in order):
`$B skill list` (the `bundled` row). The skill dir is
`<gstack-install>/browser-skills/hackernews-frontpage/`, so the install
dir is two `dirname` calls above its `_lib/browse-client.ts`.
2. The active gstack skills install at `~/.claude/skills/gstack/`. Read
2. The active gstack skills install at `{{INSTALLED_ROOT}}/`. Read
the symlink target if it's a symlink, otherwise use the path directly.
Example (run as Bun, not bash, to avoid shell-redirect parsing issues):
@@ -232,7 +232,7 @@ import * as path from 'path';
function resolveSdkPath(): string {
const candidates = [
path.join(os.homedir(), '.claude', 'skills', 'gstack', 'browse', 'src', 'browse-client.ts'),
'{{INSTALLED_ROOT}}/browse/src/browse-client.ts'.replace(/^~(?=\/)/, os.homedir()),
// Add other install-dir candidates if your environment differs.
];
for (const c of candidates) {
+28
View File
@@ -0,0 +1,28 @@
/**
* /skillify resolved the browse SDK at ~/.claude/skills/gstack on every host,
* so on Codex and the other external hosts it read another host's install (or
* nothing). The install root now comes from the host config (INSTALLED_ROOT).
*/
import { afterAll, beforeAll, describe, expect, test } from 'bun:test';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { ALL_HOST_CONFIGS } from '../hosts';
import { runGeneration } from '../scripts/gen-skill-docs';
const out = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-skillify-paths-'));
beforeAll(async () => { expect((await runGeneration({ host: 'all', outputRoot: out })).exitCode).toBe(0); });
afterAll(() => fs.rmSync(out, { recursive: true, force: true }));
describe('skillify resolves the browse SDK from its own host install', () => {
for (const host of ALL_HOST_CONFIGS) {
test(host.name, () => {
const file = host.name === 'claude' ? path.join(out, 'skillify/SKILL.md') : path.join(out, host.hostSubdir, 'skills/gstack-skillify/SKILL.md');
if (!fs.existsSync(file)) return;
const text = fs.readFileSync(file, 'utf8');
const sdk = text.split('\n').filter(l => l.includes('browse-client.ts') && /homedir|replace/.test(l));
expect(sdk).toEqual([` '~/${host.globalRoot}/browse/src/browse-client.ts'.replace(/^~(?=\\/)/, os.homedir()),`]);
expect(text).toContain(`The active gstack skills install at \`~/${host.globalRoot}/\``);
});
}
});