Skip to content
Open
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ bin/gstack-global-discover*
.cursor/
.openclaw/
.hermes/
.kimi-code/
.gbrain/
.gbrain-source
.context/
Expand Down
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 kimi from './kimi';

/** 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, kimi];

/** 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, kimi };
52 changes: 52 additions & 0 deletions hosts/kimi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import type { HostConfig } from '../scripts/host-config';

const kimi: HostConfig = {
name: 'kimi',
displayName: 'Kimi Code',
cliCommand: 'kimi',
cliAliases: [],

globalRoot: '.kimi-code/skills/gstack',
localSkillRoot: '.kimi-code/skills/gstack',
hostSubdir: '.kimi-code',
usesEnvVars: true,

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

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

pathRewrites: [
{ from: '~/.claude/skills/gstack', to: '~/.kimi-code/skills/gstack' },
{ from: '.claude/skills/gstack', to: '.kimi-code/skills/gstack' },
{ from: '.claude/skills', to: '.kimi-code/skills' },
{ from: '~/.config/opencode/skills/gstack', to: '~/.kimi-code/skills/gstack' },
{ from: '.config/opencode/skills/gstack', to: '.kimi-code/skills/gstack' },
{ from: '~/.agents/skills/gstack', to: '~/.kimi-code/skills/gstack' },
{ from: '.agents/skills/gstack', to: '.kimi-code/skills/gstack' },
],

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 kimi;
117 changes: 114 additions & 3 deletions setup
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ FACTORY_SKILLS="$HOME/.factory/skills"
FACTORY_GSTACK="$FACTORY_SKILLS/gstack"
OPENCODE_SKILLS="$HOME/.config/opencode/skills"
OPENCODE_GSTACK="$OPENCODE_SKILLS/gstack"
KIMI_SKILLS="$HOME/.kimi-code/skills"
KIMI_GSTACK="$KIMI_SKILLS/gstack"

IS_WINDOWS=0
case "$(uname -s)" in
Expand Down Expand Up @@ -101,7 +103,7 @@ while [ $# -gt 0 ]; do
done

case "$HOST" in
claude|codex|kiro|factory|opencode|auto) ;;
claude|codex|kiro|factory|opencode|kimi|auto) ;;
openclaw)
echo ""
echo "OpenClaw integration uses a different model — OpenClaw spawns Claude Code"
Expand Down Expand Up @@ -136,7 +138,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, kimi, openclaw, hermes, gbrain, or auto)" >&2; exit 1 ;;
esac

# ─── Resolve skill prefix preference ─────────────────────────
Expand Down Expand Up @@ -200,14 +202,16 @@ INSTALL_CODEX=0
INSTALL_KIRO=0
INSTALL_FACTORY=0
INSTALL_OPENCODE=0
INSTALL_KIMI=0
if [ "$HOST" = "auto" ]; then
command -v claude >/dev/null 2>&1 && INSTALL_CLAUDE=1
command -v codex >/dev/null 2>&1 && INSTALL_CODEX=1
command -v kiro-cli >/dev/null 2>&1 && INSTALL_KIRO=1
command -v droid >/dev/null 2>&1 && INSTALL_FACTORY=1
command -v opencode >/dev/null 2>&1 && INSTALL_OPENCODE=1
command -v kimi >/dev/null 2>&1 && INSTALL_KIMI=1
# If none found, default to claude
if [ "$INSTALL_CLAUDE" -eq 0 ] && [ "$INSTALL_CODEX" -eq 0 ] && [ "$INSTALL_KIRO" -eq 0 ] && [ "$INSTALL_FACTORY" -eq 0 ] && [ "$INSTALL_OPENCODE" -eq 0 ]; then
if [ "$INSTALL_CLAUDE" -eq 0 ] && [ "$INSTALL_CODEX" -eq 0 ] && [ "$INSTALL_KIRO" -eq 0 ] && [ "$INSTALL_FACTORY" -eq 0 ] && [ "$INSTALL_OPENCODE" -eq 0 ] && [ "$INSTALL_KIMI" -eq 0 ]; then
INSTALL_CLAUDE=1
fi
elif [ "$HOST" = "claude" ]; then
Expand All @@ -220,6 +224,8 @@ elif [ "$HOST" = "factory" ]; then
INSTALL_FACTORY=1
elif [ "$HOST" = "opencode" ]; then
INSTALL_OPENCODE=1
elif [ "$HOST" = "kimi" ]; then
INSTALL_KIMI=1
fi

migrate_direct_codex_install() {
Expand Down Expand Up @@ -475,6 +481,16 @@ if [ "$INSTALL_OPENCODE" -eq 1 ] && [ "$NEEDS_BUILD" -eq 0 ]; then
)
fi

# 1e. Generate .agents/ Kimi Code skill docs
if [ "$INSTALL_KIMI" -eq 1 ] && [ "$NEEDS_BUILD" -eq 0 ]; then
log "Generating .agents/ skill docs..."
(
cd "$SOURCE_GSTACK_DIR"
bun_cmd install --frozen-lockfile 2>/dev/null || bun_cmd install
bun_cmd run gen:skill-docs --host kimi
)
fi

# 2. Ensure Playwright's Chromium is available
if ! ensure_playwright_browser; then
echo "Installing Playwright Chromium..."
Expand Down Expand Up @@ -972,6 +988,91 @@ link_opencode_skill_dirs() {
fi
}

create_kimi_runtime_root() {
local gstack_dir="$1"
local kimi_gstack="$2"
local kimi_dir="$gstack_dir/.kimi-code/skills"

if [ -L "$kimi_gstack" ]; then
rm -f "$kimi_gstack"
elif [ -d "$kimi_gstack" ] && [ "$kimi_gstack" != "$gstack_dir" ]; then
rm -rf "$kimi_gstack"
fi

mkdir -p "$kimi_gstack" "$kimi_gstack/browse" "$kimi_gstack/design" "$kimi_gstack/gstack-upgrade" "$kimi_gstack/review" "$kimi_gstack/qa" "$kimi_gstack/plan-devex-review"

if [ -f "$kimi_dir/gstack/SKILL.md" ]; then
_link_or_copy "$kimi_dir/gstack/SKILL.md" "$kimi_gstack/SKILL.md"
fi
if [ -d "$gstack_dir/bin" ]; then
_link_or_copy "$gstack_dir/bin" "$kimi_gstack/bin"
fi
if [ -d "$gstack_dir/browse/dist" ]; then
_link_or_copy "$gstack_dir/browse/dist" "$kimi_gstack/browse/dist"
fi
if [ -d "$gstack_dir/browse/bin" ]; then
_link_or_copy "$gstack_dir/browse/bin" "$kimi_gstack/browse/bin"
fi
if [ -d "$gstack_dir/design/dist" ]; then
_link_or_copy "$gstack_dir/design/dist" "$kimi_gstack/design/dist"
fi
if [ -f "$kimi_dir/gstack-upgrade/SKILL.md" ]; then
_link_or_copy "$kimi_dir/gstack-upgrade/SKILL.md" "$kimi_gstack/gstack-upgrade/SKILL.md"
fi
for f in checklist.md design-checklist.md greptile-triage.md TODOS-format.md; do
if [ -f "$gstack_dir/review/$f" ]; then
_link_or_copy "$gstack_dir/review/$f" "$kimi_gstack/review/$f"
fi
done
if [ -d "$gstack_dir/review/specialists" ]; then
_link_or_copy "$gstack_dir/review/specialists" "$kimi_gstack/review/specialists"
fi
if [ -d "$gstack_dir/qa/templates" ]; then
_link_or_copy "$gstack_dir/qa/templates" "$kimi_gstack/qa/templates"
fi
if [ -d "$gstack_dir/qa/references" ]; then
_link_or_copy "$gstack_dir/qa/references" "$kimi_gstack/qa/references"
fi
if [ -f "$gstack_dir/plan-devex-review/dx-hall-of-fame.md" ]; then
_link_or_copy "$gstack_dir/plan-devex-review/dx-hall-of-fame.md" "$kimi_gstack/plan-devex-review/dx-hall-of-fame.md"
fi
if [ -f "$gstack_dir/ETHOS.md" ]; then
_link_or_copy "$gstack_dir/ETHOS.md" "$kimi_gstack/ETHOS.md"
fi
}

link_kimi_skill_dirs() {
local gstack_dir="$1"
local skills_dir="$2"
local kimi_dir="$gstack_dir/.kimi-code/skills"
local linked=()

if [ ! -d "$kimi_dir" ]; then
echo " Generating .agents/ skill docs..."
( cd "$gstack_dir" && bun run gen:skill-docs --host kimi )
fi

if [ ! -d "$kimi_dir" ]; then
echo " warning: .agents/skills/ generation failed — run 'bun run gen:skill-docs --host kimi' manually" >&2
return 1
fi

for skill_dir in "$kimi_dir"/gstack*/; do
if [ -f "$skill_dir/SKILL.md" ]; then
skill_name="$(basename "$skill_dir")"
[ "$skill_name" = "gstack" ] && continue
target="$skills_dir/$skill_name"
if [ -L "$target" ] || [ ! -e "$target" ]; then
_link_or_copy "$skill_dir" "$target"
linked+=("$skill_name")
fi
fi
done
if [ ${#linked[@]} -gt 0 ]; then
echo " linked skills: ${linked[*]}"
fi
}

# 4. Install for Claude (default)
SKILLS_BASENAME="$(basename "$INSTALL_SKILLS_DIR")"
SKILLS_PARENT_BASENAME="$(basename "$(dirname "$INSTALL_SKILLS_DIR")")"
Expand Down Expand Up @@ -1193,6 +1294,16 @@ if [ "$INSTALL_OPENCODE" -eq 1 ]; then
echo " opencode skills: $OPENCODE_SKILLS"
fi

# 6d. Install for Kimi Code
if [ "$INSTALL_KIMI" -eq 1 ]; then
mkdir -p "$KIMI_SKILLS"
create_kimi_runtime_root "$SOURCE_GSTACK_DIR" "$KIMI_GSTACK"
link_kimi_skill_dirs "$SOURCE_GSTACK_DIR" "$KIMI_SKILLS"
echo "gstack ready (kimi)."
echo " browse: $BROWSE_BIN"
echo " kimi skills: $KIMI_SKILLS"
fi

# 7. Create .agents/ sidecar symlinks for the real Codex skill target.
# The root Codex skill ends up pointing at $SOURCE_GSTACK_DIR/.agents/skills/gstack,
# so the runtime assets must live there for both global and repo-local installs.
Expand Down
4 changes: 2 additions & 2 deletions test/host-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ const RESOLVER_NAMES = new Set(Object.keys(RESOLVERS));
// ─── 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 Down