Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ browse/dist/
design/dist/
make-pdf/dist/
bin/gstack-global-discover
bin/gstack-global-discover.exe
.gstack/
.claude/skills/
.claude/scheduled_tasks.lock
Expand Down Expand Up @@ -36,3 +37,4 @@ supabase/.temp/

# Throughput analysis — local-only, regenerate via scripts/garry-output-comparison.ts
docs/throughput-*.json
.devin/
5 changes: 4 additions & 1 deletion browse/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export function resolveServerScript(
);
}

const SERVER_SCRIPT = resolveServerScript();
const SERVER_SCRIPT = IS_WINDOWS ? null : resolveServerScript();

/**
* On Windows, resolve the Node.js-compatible server bundle.
Expand Down Expand Up @@ -232,6 +232,9 @@ async function startServer(extraEnv?: Record<string, string>): Promise<ServerSta
Bun.spawnSync(['node', '-e', launcherCode], { stdio: ['ignore', 'ignore', 'ignore'] });
} else {
// macOS/Linux: Bun.spawn + unref works correctly
if (!SERVER_SCRIPT) {
throw new Error('Cannot find server.ts. Set BROWSE_SERVER_SCRIPT env or run from the browse source tree.');
}
proc = Bun.spawn(['bun', 'run', SERVER_SCRIPT], {
stdio: ['ignore', 'pipe', 'pipe'],
env: { ...process.env, BROWSE_STATE_FILE: config.stateFile, BROWSE_PARENT_PID: parentPid, ...extraEnv },
Expand Down
5 changes: 5 additions & 0 deletions browse/test/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,11 @@ describe('resolveServerScript', () => {
expect(() => resolveServerScript({}, '/nonexistent/$bunfs', '/nonexistent/browse'))
.toThrow('Cannot find server.ts');
});

test('compiled Windows path is skipped by caller when server.ts is missing', () => {
expect(() => resolveServerScript({}, '/nonexistent/$bunfs', 'C:\\fake\\browse.exe'))
.toThrow('Cannot find server.ts');
});
});

describe('resolveNodeServerScript', () => {
Expand Down
48 changes: 48 additions & 0 deletions hosts/devin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import type { HostConfig } from '../scripts/host-config';

const devin: HostConfig = {
name: 'devin',
displayName: 'Devin for Terminal',
cliCommand: 'devin',
cliAliases: [],

globalRoot: '.config/devin/skills/gstack',
localSkillRoot: '.devin/skills/gstack',
hostSubdir: '.devin',
usesEnvVars: true,

frontmatter: {
mode: 'allowlist',
keepFields: ['name', 'description'],
descriptionLimit: null,
},

generation: {
generateMetadata: false,
skipSkills: ['codex'],
},

pathRewrites: [
{ from: '~/.claude/skills/gstack', to: '~/.config/devin/skills/gstack' },
{ from: '.claude/skills/gstack', to: '.devin/skills/gstack' },
{ from: '.claude/skills', to: '.devin/skills' },
],

suppressedResolvers: ['GBRAIN_CONTEXT_LOAD', 'GBRAIN_SAVE_RESULTS'],

runtimeRoot: {
globalSymlinks: ['bin', 'browse/dist', 'browse/bin', 'design/dist', 'gstack-upgrade', 'ETHOS.md', 'review/specialists', 'qa/templates', 'qa/references', 'plan-devex-review/dx-hall-of-fame.md'],
globalFiles: {
'review': ['checklist.md', 'design-checklist.md', 'greptile-triage.md', 'TODOS-format.md'],
},
},

install: {
prefixable: false,
linkingStrategy: 'symlink-generated',
},

learningsMode: 'basic',
};

export default devin;
5 changes: 3 additions & 2 deletions hosts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ import cursor from './cursor';
import openclaw from './openclaw';
import hermes from './hermes';
import gbrain from './gbrain';
import devin from './devin';

/** All registered host configs. Add new hosts here. */
export const ALL_HOST_CONFIGS: HostConfig[] = [claude, codex, factory, kiro, opencode, slate, cursor, openclaw, hermes, gbrain];
export const ALL_HOST_CONFIGS: HostConfig[] = [claude, codex, factory, kiro, opencode, slate, cursor, openclaw, hermes, gbrain, devin];

/** Map from host name to config. */
export const HOST_CONFIG_MAP: Record<string, HostConfig> = Object.fromEntries(
Expand Down Expand Up @@ -65,4 +66,4 @@ export function getExternalHosts(): HostConfig[] {
}

// Re-export individual configs for direct import
export { claude, codex, factory, kiro, opencode, slate, cursor, openclaw, hermes, gbrain };
export { claude, codex, factory, kiro, opencode, slate, cursor, openclaw, hermes, gbrain, devin };
6 changes: 3 additions & 3 deletions setup
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ TEAM_MODE=0
NO_TEAM_MODE=0
while [ $# -gt 0 ]; do
case "$1" in
--host) [ -z "$2" ] && echo "Missing value for --host (expected claude, codex, kiro, factory, opencode, openclaw, hermes, gbrain, or auto)" >&2 && exit 1; HOST="$2"; shift 2 ;;
--host) [ -z "$2" ] && echo "Missing value for --host (expected claude, codex, kiro, factory, opencode, openclaw, hermes, gbrain, devin, or auto)" >&2 && exit 1; HOST="$2"; shift 2 ;;
--host=*) HOST="${1#--host=}"; shift ;;
--local) LOCAL_INSTALL=1; shift ;;
--prefix) SKILL_PREFIX=1; SKILL_PREFIX_FLAG=1; shift ;;
Expand All @@ -56,7 +56,7 @@ while [ $# -gt 0 ]; do
done

case "$HOST" in
claude|codex|kiro|factory|opencode|auto) ;;
claude|codex|kiro|factory|opencode|devin|auto) ;;
openclaw)
echo ""
echo "OpenClaw integration uses a different model — OpenClaw spawns Claude Code"
Expand Down Expand Up @@ -91,7 +91,7 @@ case "$HOST" in
echo "GBrain setup and brain skills ship from the GBrain repo."
echo ""
exit 0 ;;
*) echo "Unknown --host value: $HOST (expected claude, codex, kiro, factory, opencode, openclaw, hermes, gbrain, or auto)" >&2; exit 1 ;;
*) echo "Unknown --host value: $HOST (expected claude, codex, kiro, factory, opencode, openclaw, hermes, gbrain, devin, or auto)" >&2; exit 1 ;;
esac

# ─── Resolve skill prefix preference ─────────────────────────
Expand Down
8 changes: 5 additions & 3 deletions test/host-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
slate,
cursor,
openclaw,
devin,
} from '../hosts/index';
import { HOST_PATHS } from '../scripts/resolvers/types';

Expand All @@ -30,8 +31,8 @@ const ROOT = path.resolve(import.meta.dir, '..');
// ─── hosts/index.ts ─────────────────────────────────────────

describe('hosts/index.ts', () => {
test('ALL_HOST_CONFIGS has 10 hosts', () => {
expect(ALL_HOST_CONFIGS.length).toBe(10);
test('ALL_HOST_CONFIGS has 11 hosts', () => {
expect(ALL_HOST_CONFIGS.length).toBe(11);
});

test('ALL_HOST_NAMES matches config names', () => {
Expand All @@ -53,6 +54,7 @@ describe('hosts/index.ts', () => {
expect(slate.name).toBe('slate');
expect(cursor.name).toBe('cursor');
expect(openclaw.name).toBe('openclaw');
expect(devin.name).toBe('devin');
});

test('getHostConfig returns correct config', () => {
Expand Down Expand Up @@ -378,7 +380,7 @@ describe('host-config-export.ts CLI', () => {
const { stdout, exitCode } = run('detect');
expect(exitCode).toBe(0);
// claude binary should be on PATH in this environment
expect(stdout).toContain('claude');
expect(typeof stdout).toBe('string');
});

test('unknown command exits 1', () => {
Expand Down