diff --git a/src/__tests__/wiki-toggle-env.test.ts b/src/__tests__/wiki-toggle-env.test.ts new file mode 100644 index 0000000..2e37b47 --- /dev/null +++ b/src/__tests__/wiki-toggle-env.test.ts @@ -0,0 +1,50 @@ +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import { isWikiEnabled } from '../types.js'; + +describe('isWikiEnabled (env var approach)', () => { + const originalEnv = process.env; + + beforeEach(() => { + process.env = { ...originalEnv }; + }); + + afterEach(() => { + process.env = originalEnv; + }); + + it('returns true by default', () => { + delete process.env.TEAMAI_WIKI_DISABLED; + delete process.env.TEAMAI_WIKI_ENABLED; + expect(isWikiEnabled()).toBe(true); + }); + + it('returns false when TEAMAI_WIKI_DISABLED=1', () => { + process.env.TEAMAI_WIKI_DISABLED = '1'; + expect(isWikiEnabled()).toBe(false); + }); + + it('returns false when TEAMAI_WIKI_DISABLED=true', () => { + process.env.TEAMAI_WIKI_DISABLED = 'true'; + expect(isWikiEnabled()).toBe(false); + }); + + it('returns false when TEAMAI_WIKI_ENABLED=false', () => { + process.env.TEAMAI_WIKI_ENABLED = 'false'; + expect(isWikiEnabled()).toBe(false); + }); + + it('returns false when TEAMAI_WIKI_ENABLED=0', () => { + process.env.TEAMAI_WIKI_ENABLED = '0'; + expect(isWikiEnabled()).toBe(false); + }); + + it('returns true when TEAMAI_WIKI_DISABLED is not 1 or true', () => { + process.env.TEAMAI_WIKI_DISABLED = '0'; + expect(isWikiEnabled()).toBe(true); + }); + + it('returns true when TEAMAI_WIKI_ENABLED=1', () => { + process.env.TEAMAI_WIKI_ENABLED = '1'; + expect(isWikiEnabled()).toBe(true); + }); +}); diff --git a/src/builtin-skills.ts b/src/builtin-skills.ts index 2b85a8e..d0fa214 100644 --- a/src/builtin-skills.ts +++ b/src/builtin-skills.ts @@ -50,7 +50,7 @@ export const BUILTIN_SKILL_NAMES = new Set(['teamai-share-learnings', 'teamai-wi * - Built-in skills directory doesn't exist (dev environment without build) * - A tool's skills directory is not configured */ -export async function deployBuiltinSkills(teamConfig: TeamaiConfig, localConfig?: LocalConfig): Promise { +export async function deployBuiltinSkills(teamConfig: TeamaiConfig, localConfig?: LocalConfig, options?: { skipWiki?: boolean }): Promise { const builtinDir = getBuiltinSkillsDir(); if (!await pathExists(builtinDir)) { @@ -76,6 +76,12 @@ export async function deployBuiltinSkills(teamConfig: TeamaiConfig, localConfig? if (skillNames.length === 0) return 0; + // Skip teamai-wiki deployment when wiki feature is disabled + const filteredSkills = options?.skipWiki + ? skillNames.filter(name => name !== 'teamai-wiki') + : skillNames; + if (filteredSkills.length === 0) return 0; + const baseDir = localConfig ? resolveBaseDir(localConfig) : (process.env.HOME ?? ''); let deployed = 0; @@ -90,7 +96,7 @@ export async function deployBuiltinSkills(teamConfig: TeamaiConfig, localConfig? const targetSkillsDir = path.join(baseDir, toolPath.skills); - for (const skillName of skillNames) { + for (const skillName of filteredSkills) { const srcDir = path.join(builtinDir, skillName); const destDir = path.join(targetSkillsDir, skillName); diff --git a/src/pull.ts b/src/pull.ts index 7ad562a..5a60d80 100644 --- a/src/pull.ts +++ b/src/pull.ts @@ -19,6 +19,7 @@ import { TEAMAI_CLAUDEMD_END, CultureFrontmatterSchema, resolveBaseDir, + isWikiEnabled, getTeamaiHome, } from './types.js'; import type { CultureFrontmatter } from './types.js'; @@ -260,7 +261,10 @@ async function pullForScope( const subscribedTags = localConfig.subscribedTags; // Step 2: Sync each resource type - const resourceTypes: ResourceType[] = ['skills', 'rules', 'docs', 'env', 'wiki']; + const wikiEnabled = isWikiEnabled(); + const resourceTypes: ResourceType[] = wikiEnabled + ? ['skills', 'rules', 'docs', 'env', 'wiki'] + : ['skills', 'rules', 'docs', 'env']; let totalSynced = 0; let desiredSkillNames: Set | null = null; let knownRepoSkillNames: Set | null = null; @@ -555,7 +559,7 @@ async function pullForScope( if (!options.dryRun) { try { const { deployBuiltinSkills } = await import('./builtin-skills.js'); - const deployed = await deployBuiltinSkills(freshConfig, localConfig); + const deployed = await deployBuiltinSkills(freshConfig, localConfig, { skipWiki: !wikiEnabled }); if (deployed > 0) { log.debug(`[${scopeLabel}] Deployed ${deployed} built-in skill(s)`); } diff --git a/src/push.ts b/src/push.ts index a7f63f7..6058b1c 100644 --- a/src/push.ts +++ b/src/push.ts @@ -7,6 +7,7 @@ import { log, spinner } from './utils/logger.js'; import { getHandler } from './resources/index.js'; import { scanTeamRepoNamespaces } from './resources/skills.js'; import type { GlobalOptions, ResourceItem, ResourceType } from './types.js'; +import { isWikiEnabled } from './types.js'; import { loadRolesManifest, resolveRoleResourceNamespaces } from './roles.js'; import { askQuestion, askSelection } from './utils/prompt.js'; import { pathExists } from './utils/fs.js'; @@ -137,7 +138,9 @@ export async function push(options: GlobalOptions & { all?: boolean; role?: stri // Scan for pushable resources first, then resolve namespace for new skills only. // Modified skills already carry their namespace from scanLocalForPush. - const pushableTypes: ResourceType[] = ['skills', 'rules', 'env', 'wiki']; + const pushableTypes: ResourceType[] = isWikiEnabled() + ? ['skills', 'rules', 'env', 'wiki'] + : ['skills', 'rules', 'env']; const allItems: ResourceItem[] = []; for (const type of pushableTypes) { diff --git a/src/status.ts b/src/status.ts index 27092d9..3097325 100644 --- a/src/status.ts +++ b/src/status.ts @@ -16,6 +16,7 @@ import { type AgentSkillsView, } from './agent-skills.js'; import type { GlobalOptions, ResourceType } from './types.js'; +import { isWikiEnabled } from './types.js'; export interface ListOptions extends GlobalOptions { /** Where to look for resources: 'repo' (default for backwards compat), @@ -100,6 +101,12 @@ export async function status(options: GlobalOptions): Promise { console.log(` ${type}: ${count}`); } + // Wiki feature status + if (!isWikiEnabled()) { + console.log(''); + log.info('ℹ Wiki: disabled (TEAMAI_WIKI_DISABLED=1) — wiki routing handled by external plugin'); + } + // Local pushable items console.log(''); log.info('Local resources not yet pushed:'); diff --git a/src/types.ts b/src/types.ts index 38b3319..8ce0572 100644 --- a/src/types.ts +++ b/src/types.ts @@ -534,3 +534,14 @@ export function getStatePath(scope: Scope, projectRoot?: string): string { export function getPushignorePath(): string { return path.join(process.env.HOME ?? '', '.teamai', 'pushignore'); } + +/** + * Check if wiki feature is enabled. + * Disable by setting TEAMAI_WIKI_DISABLED=1 or TEAMAI_WIKI_ENABLED=false. + * Defaults to enabled for backward compatibility. + */ +export function isWikiEnabled(): boolean { + if (process.env.TEAMAI_WIKI_DISABLED === '1' || process.env.TEAMAI_WIKI_DISABLED === 'true') return false; + if (process.env.TEAMAI_WIKI_ENABLED === '0' || process.env.TEAMAI_WIKI_ENABLED === 'false') return false; + return true; +}