aios-core 4.2.3 → 4.2.5
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.
- package/.aios-core/core/registry/service-registry.json +6466 -6586
- package/.aios-core/core-config.yaml +10 -5
- package/.aios-core/data/aios-kb.md +19 -25
- package/.aios-core/data/entity-registry.yaml +311 -204
- package/.aios-core/data/registry-update-log.jsonl +14 -0
- package/.aios-core/development/tasks/db-squad-integration.md +3 -3
- package/.aios-core/development/tasks/dev-develop-story.md +1 -1
- package/.aios-core/development/tasks/integrate-squad.md +1 -1
- package/.aios-core/development/tasks/pr-automation.md +3 -3
- package/.aios-core/development/tasks/squad-creator-migrate.md +1 -1
- package/.aios-core/development/tasks/squad-creator-sync-ide-command.md +0 -2
- package/.aios-core/development/tasks/update-aios.md +2 -2
- package/.aios-core/development/tasks/validate-next-story.md +2 -99
- package/.aios-core/development/workflows/README.md +0 -4
- package/.aios-core/docs/standards/AIOS-COLOR-PALETTE-V2.1.md +0 -1
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1-COMPLETE.md +3 -3
- package/.aios-core/docs/standards/QUALITY-GATES-SPECIFICATION.md +1 -1
- package/.aios-core/docs/standards/STANDARDS-INDEX.md +4 -4
- package/.aios-core/docs/standards/STORY-TEMPLATE-V2-SPECIFICATION.md +2 -2
- package/.aios-core/framework-config.yaml +8 -4
- package/.aios-core/infrastructure/scripts/ide-sync/README.md +29 -5
- package/.aios-core/infrastructure/scripts/ide-sync/gemini-commands.js +205 -0
- package/.aios-core/infrastructure/scripts/ide-sync/index.js +48 -11
- package/.aios-core/infrastructure/scripts/ide-sync/redirect-generator.js +1 -1
- package/.aios-core/infrastructure/scripts/test-utilities.js +1 -1
- package/.aios-core/infrastructure/scripts/tool-resolver.js +2 -2
- package/.aios-core/infrastructure/scripts/validate-claude-integration.js +101 -0
- package/.aios-core/infrastructure/scripts/validate-codex-integration.js +141 -0
- package/.aios-core/infrastructure/scripts/validate-gemini-integration.js +151 -0
- package/.aios-core/infrastructure/scripts/validate-parity.js +119 -0
- package/.aios-core/infrastructure/templates/aios-sync.yaml.template +0 -11
- package/.aios-core/install-manifest.yaml +83 -71
- package/.aios-core/local-config.yaml.template +2 -1
- package/.aios-core/presets/README.md +0 -1
- package/.aios-core/product/data/integration-patterns.md +1 -1
- package/.aios-core/product/templates/ide-rules/gemini-rules.md +87 -233
- package/.aios-core/product/templates/statusline/statusline-script.js +188 -0
- package/.aios-core/product/templates/statusline/track-agent.sh +68 -0
- package/.aios-core/user-guide.md +14 -19
- package/LICENSE +0 -27
- package/README.md +42 -6
- package/bin/aios-init.js +98 -54
- package/bin/modules/env-config.js +0 -1
- package/package.json +19 -5
- package/packages/gemini-aios-extension/README.md +13 -8
- package/packages/gemini-aios-extension/commands/aios-agent.js +7 -0
- package/packages/gemini-aios-extension/commands/aios-agents.js +2 -1
- package/packages/gemini-aios-extension/commands/aios-analyst.js +6 -0
- package/packages/gemini-aios-extension/commands/aios-architect.js +6 -0
- package/packages/gemini-aios-extension/commands/aios-data-engineer.js +6 -0
- package/packages/gemini-aios-extension/commands/aios-dev.js +6 -0
- package/packages/gemini-aios-extension/commands/aios-devops.js +6 -0
- package/packages/gemini-aios-extension/commands/aios-master.js +6 -0
- package/packages/gemini-aios-extension/commands/aios-menu.js +6 -0
- package/packages/gemini-aios-extension/commands/aios-pm.js +6 -0
- package/packages/gemini-aios-extension/commands/aios-po.js +6 -0
- package/packages/gemini-aios-extension/commands/aios-qa.js +6 -0
- package/packages/gemini-aios-extension/commands/aios-sm.js +6 -0
- package/packages/gemini-aios-extension/commands/aios-squad-creator.js +6 -0
- package/packages/gemini-aios-extension/commands/aios-ux-design-expert.js +6 -0
- package/packages/gemini-aios-extension/commands/lib/agent-launcher.js +138 -0
- package/packages/gemini-aios-extension/extension.json +70 -0
- package/packages/gemini-aios-extension/gemini-extension.json +147 -0
- package/packages/gemini-aios-extension/hooks/hooks.json +67 -65
- package/packages/installer/src/config/ide-configs.js +10 -10
- package/packages/installer/src/config/templates/core-config-template.js +6 -3
- package/packages/installer/src/wizard/ide-config-generator.js +373 -2
- package/packages/installer/src/wizard/ide-selector.js +1 -1
- package/pro/README.md +66 -0
- package/pro/license/degradation.js +220 -0
- package/pro/license/errors.js +450 -0
- package/pro/license/feature-gate.js +354 -0
- package/pro/license/index.js +181 -0
- package/pro/license/license-api.js +617 -0
- package/pro/license/license-cache.js +523 -0
- package/pro/license/license-crypto.js +303 -0
- package/scripts/code-intel-health-check.js +125 -125
- package/scripts/ensure-manifest.js +58 -0
- package/.aios-core/infrastructure/scripts/ide-sync/transformers/windsurf.js +0 -106
- package/.aios-core/product/templates/ide-rules/cline-rules.md +0 -84
- package/.aios-core/product/templates/ide-rules/roo-rules.md +0 -86
- package/.aios-core/product/templates/ide-rules/windsurf-rules.md +0 -80
|
@@ -12,6 +12,7 @@ const path = require('path');
|
|
|
12
12
|
const yaml = require('js-yaml');
|
|
13
13
|
const inquirer = require('inquirer');
|
|
14
14
|
const ora = require('ora');
|
|
15
|
+
const { spawnSync } = require('child_process');
|
|
15
16
|
const { getIDEConfig } = require('../config/ide-configs');
|
|
16
17
|
const { validateProjectName } = require('./validators');
|
|
17
18
|
const { getMergeStrategy, hasMergeStrategy } = require('../merger/index.js');
|
|
@@ -386,8 +387,8 @@ async function createAntiGravityConfigJson(projectRoot, ideConfig) {
|
|
|
386
387
|
* @returns {Promise<{success: boolean, files: string[], errors: Array}>}
|
|
387
388
|
*
|
|
388
389
|
* @example
|
|
389
|
-
* const result = await generateIDEConfigs(['cursor', '
|
|
390
|
-
* console.log(result.files); // ['.cursorrules', '.
|
|
390
|
+
* const result = await generateIDEConfigs(['cursor', 'github-copilot'], wizardState);
|
|
391
|
+
* console.log(result.files); // ['.cursorrules', '.github/copilot-instructions.md']
|
|
391
392
|
*/
|
|
392
393
|
async function generateIDEConfigs(selectedIDEs, wizardState, options = {}) {
|
|
393
394
|
const projectRoot = options.projectRoot || process.cwd();
|
|
@@ -538,6 +539,44 @@ async function generateIDEConfigs(selectedIDEs, wizardState, options = {}) {
|
|
|
538
539
|
} else {
|
|
539
540
|
spinner.info('Skipped settings.local.json (no hooks to register)');
|
|
540
541
|
}
|
|
542
|
+
|
|
543
|
+
// Silent statusline setup (graceful skip if user already has one)
|
|
544
|
+
const statuslineResult = await setupGlobalStatusline();
|
|
545
|
+
if (statuslineResult.installed) {
|
|
546
|
+
createdFiles.push(...statuslineResult.files);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// Gemini parity with Claude Code: copy hooks and configure settings
|
|
551
|
+
if (ideKey === 'gemini') {
|
|
552
|
+
spinner.start('Copying Gemini CLI hooks...');
|
|
553
|
+
const hookFiles = await copyGeminiHooksFolder(projectRoot);
|
|
554
|
+
createdFiles.push(...hookFiles);
|
|
555
|
+
if (hookFiles.length > 0) {
|
|
556
|
+
createdFolders.push(path.join(projectRoot, '.gemini', 'hooks'));
|
|
557
|
+
spinner.succeed(`Copied ${hookFiles.length} hook file(s) to .gemini/hooks`);
|
|
558
|
+
} else {
|
|
559
|
+
spinner.info('No Gemini hook files to copy');
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
spinner.start('Configuring Gemini CLI settings...');
|
|
563
|
+
const settingsFile = await createGeminiSettings(projectRoot);
|
|
564
|
+
if (settingsFile) {
|
|
565
|
+
createdFiles.push(settingsFile);
|
|
566
|
+
spinner.succeed('Created .gemini/settings.json with AIOS hooks');
|
|
567
|
+
} else {
|
|
568
|
+
spinner.info('Skipped .gemini/settings.json (no hooks to register)');
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
spinner.start('Linking Gemini AIOS extension...');
|
|
572
|
+
const extensionResult = await linkGeminiExtension(projectRoot);
|
|
573
|
+
if (extensionResult.status === 'linked') {
|
|
574
|
+
spinner.succeed('Gemini extension "aios" linked and enabled');
|
|
575
|
+
} else if (extensionResult.status === 'already-linked') {
|
|
576
|
+
spinner.succeed('Gemini extension "aios" already linked');
|
|
577
|
+
} else {
|
|
578
|
+
spinner.info(`Skipped Gemini extension linking (${extensionResult.reason})`);
|
|
579
|
+
}
|
|
541
580
|
}
|
|
542
581
|
|
|
543
582
|
} catch (error) {
|
|
@@ -724,6 +763,334 @@ async function createClaudeSettingsLocal(projectRoot) {
|
|
|
724
763
|
return settingsPath;
|
|
725
764
|
}
|
|
726
765
|
|
|
766
|
+
/**
|
|
767
|
+
<<<<<<< HEAD
|
|
768
|
+
* Copy .aios-core/hooks/gemini folder into .gemini/hooks during installation
|
|
769
|
+
* @param {string} projectRoot - Project root directory
|
|
770
|
+
* @returns {Promise<string[]>} List of copied files
|
|
771
|
+
*/
|
|
772
|
+
async function copyGeminiHooksFolder(projectRoot) {
|
|
773
|
+
const sourceDir = path.join(__dirname, '..', '..', '..', '..', '.aios-core', 'hooks', 'gemini');
|
|
774
|
+
const targetDir = path.join(projectRoot, '.gemini', 'hooks');
|
|
775
|
+
const copiedFiles = [];
|
|
776
|
+
|
|
777
|
+
if (!await fs.pathExists(sourceDir)) {
|
|
778
|
+
return copiedFiles;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
if (path.resolve(sourceDir) === path.resolve(targetDir)) {
|
|
782
|
+
return copiedFiles;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
await fs.ensureDir(targetDir);
|
|
786
|
+
|
|
787
|
+
const files = await fs.readdir(sourceDir);
|
|
788
|
+
for (const file of files) {
|
|
789
|
+
if (!file.endsWith('.js')) continue;
|
|
790
|
+
|
|
791
|
+
const sourcePath = path.join(sourceDir, file);
|
|
792
|
+
const targetPath = path.join(targetDir, file);
|
|
793
|
+
const stat = await fs.stat(sourcePath);
|
|
794
|
+
if (stat.isFile()) {
|
|
795
|
+
await fs.copy(sourcePath, targetPath);
|
|
796
|
+
copiedFiles.push(targetPath);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
return copiedFiles;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
/**
|
|
804
|
+
* Create/merge .gemini/settings.json and register AIOS hooks as enabled.
|
|
805
|
+
* @param {string} projectRoot - Project root directory
|
|
806
|
+
* @returns {Promise<string|null>} Path to settings file or null if skipped
|
|
807
|
+
*/
|
|
808
|
+
async function createGeminiSettings(projectRoot) {
|
|
809
|
+
const settingsPath = path.join(projectRoot, '.gemini', 'settings.json');
|
|
810
|
+
const hooksDir = path.join(projectRoot, '.gemini', 'hooks');
|
|
811
|
+
|
|
812
|
+
if (!await fs.pathExists(hooksDir)) {
|
|
813
|
+
return null;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
const hookEntries = [
|
|
817
|
+
{
|
|
818
|
+
event: 'SessionStart',
|
|
819
|
+
matcher: '*',
|
|
820
|
+
hook: {
|
|
821
|
+
name: 'aios-session-init',
|
|
822
|
+
type: 'command',
|
|
823
|
+
command: 'node ".gemini/hooks/session-start.js"',
|
|
824
|
+
timeout: 5000,
|
|
825
|
+
enabled: true,
|
|
826
|
+
},
|
|
827
|
+
},
|
|
828
|
+
{
|
|
829
|
+
event: 'BeforeAgent',
|
|
830
|
+
matcher: '*',
|
|
831
|
+
hook: {
|
|
832
|
+
name: 'aios-context-inject',
|
|
833
|
+
type: 'command',
|
|
834
|
+
command: 'node ".gemini/hooks/before-agent.js"',
|
|
835
|
+
timeout: 3000,
|
|
836
|
+
enabled: true,
|
|
837
|
+
},
|
|
838
|
+
},
|
|
839
|
+
{
|
|
840
|
+
event: 'BeforeTool',
|
|
841
|
+
matcher: 'write_file|replace|shell|bash|execute',
|
|
842
|
+
hook: {
|
|
843
|
+
name: 'aios-security-check',
|
|
844
|
+
type: 'command',
|
|
845
|
+
command: 'node ".gemini/hooks/before-tool.js"',
|
|
846
|
+
timeout: 2000,
|
|
847
|
+
enabled: true,
|
|
848
|
+
},
|
|
849
|
+
},
|
|
850
|
+
{
|
|
851
|
+
event: 'AfterTool',
|
|
852
|
+
matcher: '*',
|
|
853
|
+
hook: {
|
|
854
|
+
name: 'aios-audit-log',
|
|
855
|
+
type: 'command',
|
|
856
|
+
command: 'node ".gemini/hooks/after-tool.js"',
|
|
857
|
+
timeout: 2000,
|
|
858
|
+
enabled: true,
|
|
859
|
+
},
|
|
860
|
+
},
|
|
861
|
+
{
|
|
862
|
+
event: 'SessionEnd',
|
|
863
|
+
matcher: '*',
|
|
864
|
+
hook: {
|
|
865
|
+
name: 'aios-session-persist',
|
|
866
|
+
type: 'command',
|
|
867
|
+
command: 'node ".gemini/hooks/session-end.js"',
|
|
868
|
+
timeout: 5000,
|
|
869
|
+
enabled: true,
|
|
870
|
+
},
|
|
871
|
+
},
|
|
872
|
+
];
|
|
873
|
+
|
|
874
|
+
let settings = {};
|
|
875
|
+
if (await fs.pathExists(settingsPath)) {
|
|
876
|
+
try {
|
|
877
|
+
settings = JSON.parse(await fs.readFile(settingsPath, 'utf8'));
|
|
878
|
+
} catch (error) {
|
|
879
|
+
console.error(` ⚠️ Could not parse ${settingsPath}: ${error.message}`);
|
|
880
|
+
settings = {};
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
settings.previewFeatures = true;
|
|
885
|
+
settings.folderTrust = settings.folderTrust || { enabled: true };
|
|
886
|
+
settings.hooks = settings.hooks || {};
|
|
887
|
+
|
|
888
|
+
for (const entry of hookEntries) {
|
|
889
|
+
if (!Array.isArray(settings.hooks[entry.event])) {
|
|
890
|
+
settings.hooks[entry.event] = [];
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
const alreadyRegistered = settings.hooks[entry.event].some((wrapper) => {
|
|
894
|
+
if (wrapper && Array.isArray(wrapper.hooks)) {
|
|
895
|
+
return wrapper.hooks.some((h) => h && h.name === entry.hook.name);
|
|
896
|
+
}
|
|
897
|
+
return false;
|
|
898
|
+
});
|
|
899
|
+
|
|
900
|
+
if (!alreadyRegistered) {
|
|
901
|
+
settings.hooks[entry.event].push({
|
|
902
|
+
matcher: entry.matcher,
|
|
903
|
+
hooks: [entry.hook],
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
await fs.ensureDir(path.dirname(settingsPath));
|
|
909
|
+
await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
|
|
910
|
+
return settingsPath;
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
/**
|
|
914
|
+
* Best-effort Gemini extension linking for AIOS project.
|
|
915
|
+
* Does not fail installation when auth/CLI is unavailable.
|
|
916
|
+
* @param {string} projectRoot
|
|
917
|
+
* @returns {Promise<{status: 'linked'|'already-linked'|'skipped', reason?: string}>}
|
|
918
|
+
*/
|
|
919
|
+
async function linkGeminiExtension(projectRoot) {
|
|
920
|
+
const extensionDir = path.join(projectRoot, 'packages', 'gemini-aios-extension');
|
|
921
|
+
const manifestPath = path.join(extensionDir, 'gemini-extension.json');
|
|
922
|
+
const legacyManifestPath = path.join(extensionDir, 'extension.json');
|
|
923
|
+
|
|
924
|
+
if (!await fs.pathExists(extensionDir)) {
|
|
925
|
+
return { status: 'skipped', reason: 'extension-dir-not-found' };
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
// Gemini CLI >=0.28 expects gemini-extension.json
|
|
929
|
+
if (!await fs.pathExists(manifestPath) && await fs.pathExists(legacyManifestPath)) {
|
|
930
|
+
await fs.copy(legacyManifestPath, manifestPath);
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
if (!await fs.pathExists(manifestPath)) {
|
|
934
|
+
return { status: 'skipped', reason: 'manifest-not-found' };
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
const versionCheck = spawnSync('gemini', ['--version'], { encoding: 'utf8' });
|
|
938
|
+
if (versionCheck.status !== 0) {
|
|
939
|
+
return { status: 'skipped', reason: 'gemini-cli-not-available' };
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
let linkResult = spawnSync('gemini', ['extensions', 'link', extensionDir, '--consent'], {
|
|
943
|
+
cwd: projectRoot,
|
|
944
|
+
encoding: 'utf8',
|
|
945
|
+
timeout: 30000,
|
|
946
|
+
});
|
|
947
|
+
|
|
948
|
+
if (linkResult.status === 0) {
|
|
949
|
+
return { status: 'linked' };
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
const output = `${linkResult.stdout || ''}\n${linkResult.stderr || ''}`;
|
|
953
|
+
|
|
954
|
+
// When already installed, perform idempotent relink.
|
|
955
|
+
if (output.includes('already installed')) {
|
|
956
|
+
const uninstall = spawnSync('gemini', ['extensions', 'uninstall', 'aios'], {
|
|
957
|
+
cwd: projectRoot,
|
|
958
|
+
encoding: 'utf8',
|
|
959
|
+
timeout: 30000,
|
|
960
|
+
});
|
|
961
|
+
|
|
962
|
+
if (uninstall.status !== 0) {
|
|
963
|
+
return { status: 'skipped', reason: 'uninstall-failed' };
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
linkResult = spawnSync('gemini', ['extensions', 'link', extensionDir, '--consent'], {
|
|
967
|
+
cwd: projectRoot,
|
|
968
|
+
encoding: 'utf8',
|
|
969
|
+
timeout: 30000,
|
|
970
|
+
});
|
|
971
|
+
|
|
972
|
+
if (linkResult.status === 0) {
|
|
973
|
+
return { status: 'linked' };
|
|
974
|
+
}
|
|
975
|
+
return { status: 'skipped', reason: 'relink-failed' };
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
if (output.toLowerCase().includes('authentication')) {
|
|
979
|
+
return { status: 'skipped', reason: 'authentication-required' };
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
return { status: 'skipped', reason: 'link-failed' };
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
/**
|
|
986
|
+
* Setup global statusline for Claude Code
|
|
987
|
+
*
|
|
988
|
+
* Copies statusline-script.js and track-agent.sh to ~/.claude/
|
|
989
|
+
* and configures ~/.claude/settings.json with statusLine + hook entries.
|
|
990
|
+
*
|
|
991
|
+
* GRACEFUL SKIP: If user already has a statusLine configured, this function
|
|
992
|
+
* returns silently without any output — the user never knows it was checked.
|
|
993
|
+
*
|
|
994
|
+
* @returns {Promise<{installed: boolean, files: string[]}>}
|
|
995
|
+
*/
|
|
996
|
+
async function setupGlobalStatusline() {
|
|
997
|
+
const homeDir = require('os').homedir();
|
|
998
|
+
const globalSettingsPath = path.join(homeDir, '.claude', 'settings.json');
|
|
999
|
+
const result = { installed: false, files: [] };
|
|
1000
|
+
|
|
1001
|
+
// Read existing global settings
|
|
1002
|
+
let settings = {};
|
|
1003
|
+
try {
|
|
1004
|
+
if (await fs.pathExists(globalSettingsPath)) {
|
|
1005
|
+
const content = await fs.readFile(globalSettingsPath, 'utf8');
|
|
1006
|
+
settings = JSON.parse(content);
|
|
1007
|
+
}
|
|
1008
|
+
} catch {
|
|
1009
|
+
// Corrupted or unreadable — treat as empty
|
|
1010
|
+
settings = {};
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
// GRACEFUL SKIP: User already has a statusLine configured
|
|
1014
|
+
if (settings.statusLine) {
|
|
1015
|
+
return result;
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
// Source templates
|
|
1019
|
+
const templatesDir = path.join(__dirname, '..', '..', '..', '..', '.aios-core', 'product', 'templates', 'statusline');
|
|
1020
|
+
|
|
1021
|
+
const scriptSource = path.join(templatesDir, 'statusline-script.js');
|
|
1022
|
+
const hookSource = path.join(templatesDir, 'track-agent.sh');
|
|
1023
|
+
|
|
1024
|
+
// Verify templates exist
|
|
1025
|
+
if (!await fs.pathExists(scriptSource) || !await fs.pathExists(hookSource)) {
|
|
1026
|
+
return result;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
// Target paths
|
|
1030
|
+
const scriptTarget = path.join(homeDir, '.claude', 'statusline-script.js');
|
|
1031
|
+
const hookTarget = path.join(homeDir, '.claude', 'hooks', 'track-agent.sh');
|
|
1032
|
+
const cacheDir = path.join(homeDir, '.claude', 'session-cache');
|
|
1033
|
+
|
|
1034
|
+
// Copy files
|
|
1035
|
+
try {
|
|
1036
|
+
await fs.ensureDir(path.join(homeDir, '.claude', 'hooks'));
|
|
1037
|
+
await fs.ensureDir(cacheDir);
|
|
1038
|
+
await fs.copy(scriptSource, scriptTarget);
|
|
1039
|
+
await fs.copy(hookSource, hookTarget);
|
|
1040
|
+
result.files.push(scriptTarget, hookTarget);
|
|
1041
|
+
} catch {
|
|
1042
|
+
return result;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
// Build the statusLine command with platform-appropriate path
|
|
1046
|
+
const scriptPathEscaped = scriptTarget.replace(/\\/g, '\\\\');
|
|
1047
|
+
|
|
1048
|
+
// Add statusLine to settings
|
|
1049
|
+
settings.statusLine = {
|
|
1050
|
+
type: 'command',
|
|
1051
|
+
command: `node "${scriptPathEscaped}"`,
|
|
1052
|
+
};
|
|
1053
|
+
|
|
1054
|
+
// Add track-agent hook to UserPromptSubmit (if not already present)
|
|
1055
|
+
if (!settings.hooks) {
|
|
1056
|
+
settings.hooks = {};
|
|
1057
|
+
}
|
|
1058
|
+
if (!Array.isArray(settings.hooks.UserPromptSubmit)) {
|
|
1059
|
+
settings.hooks.UserPromptSubmit = [];
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
const hookPathEscaped = hookTarget.replace(/\\/g, '\\\\');
|
|
1063
|
+
const alreadyHasTrackAgent = settings.hooks.UserPromptSubmit.some(entry => {
|
|
1064
|
+
if (Array.isArray(entry.hooks)) {
|
|
1065
|
+
return entry.hooks.some(h => h.command && h.command.includes('track-agent'));
|
|
1066
|
+
}
|
|
1067
|
+
return entry.command && entry.command.includes('track-agent');
|
|
1068
|
+
});
|
|
1069
|
+
|
|
1070
|
+
if (!alreadyHasTrackAgent) {
|
|
1071
|
+
settings.hooks.UserPromptSubmit.push({
|
|
1072
|
+
matcher: '',
|
|
1073
|
+
hooks: [
|
|
1074
|
+
{
|
|
1075
|
+
type: 'command',
|
|
1076
|
+
command: `bash "${hookPathEscaped}"`,
|
|
1077
|
+
},
|
|
1078
|
+
],
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
// Write settings back
|
|
1083
|
+
try {
|
|
1084
|
+
await fs.ensureDir(path.dirname(globalSettingsPath));
|
|
1085
|
+
await fs.writeFile(globalSettingsPath, JSON.stringify(settings, null, 2), 'utf8');
|
|
1086
|
+
result.installed = true;
|
|
1087
|
+
} catch {
|
|
1088
|
+
return result;
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
return result;
|
|
1092
|
+
}
|
|
1093
|
+
|
|
727
1094
|
module.exports = {
|
|
728
1095
|
generateIDEConfigs,
|
|
729
1096
|
showSuccessSummary,
|
|
@@ -734,4 +1101,8 @@ module.exports = {
|
|
|
734
1101
|
generateTemplateVariables,
|
|
735
1102
|
copyClaudeHooksFolder,
|
|
736
1103
|
createClaudeSettingsLocal,
|
|
1104
|
+
copyGeminiHooksFolder,
|
|
1105
|
+
createGeminiSettings,
|
|
1106
|
+
linkGeminiExtension,
|
|
1107
|
+
setupGlobalStatusline,
|
|
737
1108
|
};
|
|
@@ -46,7 +46,7 @@ function validateIDESelection(selectedIDEs) {
|
|
|
46
46
|
*
|
|
47
47
|
* @example
|
|
48
48
|
* const selectedIDEs = await selectIDEs();
|
|
49
|
-
* console.log(selectedIDEs); // ['cursor', '
|
|
49
|
+
* console.log(selectedIDEs); // ['cursor', 'github-copilot']
|
|
50
50
|
*/
|
|
51
51
|
async function selectIDEs() {
|
|
52
52
|
const { selectedIDEs } = await inquirer.prompt([
|
package/pro/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# AIOS Pro
|
|
2
|
+
|
|
3
|
+
Premium features for [Synkra AIOS](https://github.com/SynkraAI/aios-core).
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
AIOS Pro extends the open-source AIOS framework with premium capabilities:
|
|
8
|
+
|
|
9
|
+
- **Premium Squads** - Pre-built squads for SaaS, e-commerce, and fintech
|
|
10
|
+
- **Persistent Memory** - Cross-session context and memory analytics
|
|
11
|
+
- **Usage Metrics** - Dashboards, team analytics, and cost tracking
|
|
12
|
+
- **Enterprise Integrations** - ClickUp, Google Drive, GitLab, Jira, Azure DevOps
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
AIOS Pro requires an active license and is distributed via npm.
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# Install
|
|
20
|
+
npm install @aios-fullstack/pro
|
|
21
|
+
|
|
22
|
+
# Activate license
|
|
23
|
+
aios pro activate --key PRO-XXXX-XXXX-XXXX-XXXX
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Usage with aios-core
|
|
27
|
+
|
|
28
|
+
AIOS Pro is designed to work as a git submodule of `aios-core`:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# Clone aios-core with pro submodule
|
|
32
|
+
git clone --recurse-submodules https://github.com/SynkraAI/aios-core.git
|
|
33
|
+
|
|
34
|
+
# Or add to existing clone
|
|
35
|
+
cd aios-core
|
|
36
|
+
git submodule update --init pro
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Activate License
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
aios pro activate --key PRO-XXXX-XXXX-XXXX-XXXX
|
|
43
|
+
aios pro status
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Structure
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
aios-pro/
|
|
50
|
+
├── squads/ # Premium squad definitions
|
|
51
|
+
├── memory/ # Persistent memory modules
|
|
52
|
+
├── metrics/ # Usage dashboards and analytics
|
|
53
|
+
├── integrations/ # Enterprise integration adapters
|
|
54
|
+
├── license/ # License infrastructure
|
|
55
|
+
└── pro-config.yaml # Pro extension configuration
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Requirements
|
|
59
|
+
|
|
60
|
+
- Node.js >= 18
|
|
61
|
+
- aios-core >= 3.12.0
|
|
62
|
+
- Active AIOS Pro license
|
|
63
|
+
|
|
64
|
+
## License
|
|
65
|
+
|
|
66
|
+
Proprietary - Copyright (c) 2026 SynkraAI. All rights reserved.
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graceful Degradation Module
|
|
3
|
+
*
|
|
4
|
+
* Utilities for handling pro feature unavailability with user-friendly
|
|
5
|
+
* messages that preserve data and provide clear next steps.
|
|
6
|
+
*
|
|
7
|
+
* Per ADR-PRO-003 and AC-8:
|
|
8
|
+
* - Never show upgrade prompts on core features
|
|
9
|
+
* - Never delete or corrupt user data
|
|
10
|
+
* - Always provide actionable next steps
|
|
11
|
+
* - Keep messages non-intrusive
|
|
12
|
+
*
|
|
13
|
+
* @module pro/license/degradation
|
|
14
|
+
* @see ADR-PRO-003 - Feature Gating & Licensing
|
|
15
|
+
* @see Story PRO-6 - License Key & Feature Gating System
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
'use strict';
|
|
19
|
+
|
|
20
|
+
const { featureGate } = require('./feature-gate');
|
|
21
|
+
const { ProFeatureError } = require('./errors');
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Default degradation message template.
|
|
25
|
+
* @private Reserved for future customization support.
|
|
26
|
+
*/
|
|
27
|
+
const _DEFAULT_MESSAGE_TEMPLATE = `
|
|
28
|
+
{featureName} requires an active AIOS Pro license.
|
|
29
|
+
|
|
30
|
+
Your data and configurations are preserved.
|
|
31
|
+
|
|
32
|
+
Activate: aios pro activate --key <KEY>
|
|
33
|
+
Purchase: https://synkra.ai/pro
|
|
34
|
+
`;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Handle pro feature gracefully - return fallback instead of throwing.
|
|
38
|
+
*
|
|
39
|
+
* Use this when you want to provide a degraded experience instead
|
|
40
|
+
* of completely blocking the feature.
|
|
41
|
+
*
|
|
42
|
+
* @param {string} featureId - Feature ID to check
|
|
43
|
+
* @param {Function} proAction - Function to execute if feature is available
|
|
44
|
+
* @param {Function} [fallbackAction] - Function to execute if not available
|
|
45
|
+
* @param {object} [options] - Options
|
|
46
|
+
* @param {boolean} [options.silent=false] - Don't log degradation message
|
|
47
|
+
* @returns {*} Result of proAction or fallbackAction
|
|
48
|
+
*/
|
|
49
|
+
function withGracefulDegradation(featureId, proAction, fallbackAction, options = {}) {
|
|
50
|
+
if (featureGate.isAvailable(featureId)) {
|
|
51
|
+
return proAction();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!options.silent) {
|
|
55
|
+
const featureName = getFeatureFriendlyName(featureId);
|
|
56
|
+
logDegradationMessage(featureName, featureId);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (typeof fallbackAction === 'function') {
|
|
60
|
+
return fallbackAction();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Execute action only if pro feature is available.
|
|
68
|
+
*
|
|
69
|
+
* Unlike featureGate.require(), this doesn't throw - it just
|
|
70
|
+
* returns undefined if the feature isn't available.
|
|
71
|
+
*
|
|
72
|
+
* @param {string} featureId - Feature ID to check
|
|
73
|
+
* @param {Function} action - Action to execute if available
|
|
74
|
+
* @returns {*|undefined} Result of action or undefined
|
|
75
|
+
*/
|
|
76
|
+
function ifProAvailable(featureId, action) {
|
|
77
|
+
if (featureGate.isAvailable(featureId)) {
|
|
78
|
+
return action();
|
|
79
|
+
}
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get a user-friendly name for a feature.
|
|
85
|
+
*
|
|
86
|
+
* @param {string} featureId - Feature ID
|
|
87
|
+
* @returns {string} Friendly name
|
|
88
|
+
*/
|
|
89
|
+
function getFeatureFriendlyName(featureId) {
|
|
90
|
+
const all = featureGate.listAll();
|
|
91
|
+
const feature = all.find((f) => f.id === featureId);
|
|
92
|
+
return feature ? feature.name : featureId;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Log a degradation message to the console.
|
|
97
|
+
*
|
|
98
|
+
* This is used internally when a pro feature is accessed without
|
|
99
|
+
* a license. Messages are non-intrusive and informative.
|
|
100
|
+
*
|
|
101
|
+
* @param {string} featureName - Human-friendly feature name
|
|
102
|
+
* @param {string} _featureId - Feature ID for reference (reserved for logging)
|
|
103
|
+
*/
|
|
104
|
+
function logDegradationMessage(featureName, _featureId) {
|
|
105
|
+
console.log('');
|
|
106
|
+
console.log(` ${featureName} requires an active AIOS Pro license.`);
|
|
107
|
+
console.log('');
|
|
108
|
+
console.log(' Your data and configurations are preserved.');
|
|
109
|
+
console.log('');
|
|
110
|
+
console.log(' Activate: aios pro activate --key <KEY>');
|
|
111
|
+
console.log(' Purchase: https://synkra.ai/pro');
|
|
112
|
+
console.log('');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Create a degradation-aware wrapper for a pro module.
|
|
117
|
+
*
|
|
118
|
+
* This creates a proxy that catches ProFeatureError and provides
|
|
119
|
+
* a graceful degradation experience.
|
|
120
|
+
*
|
|
121
|
+
* @param {object} proModule - Pro module with methods
|
|
122
|
+
* @param {object} fallbacks - Map of method names to fallback functions
|
|
123
|
+
* @returns {Proxy} Wrapped module
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* const safePremiumSquads = createDegradationWrapper(
|
|
127
|
+
* PremiumSquads,
|
|
128
|
+
* {
|
|
129
|
+
* listTemplates: () => ['basic'], // Fallback to basic templates
|
|
130
|
+
* exportSquad: () => null, // No export without license
|
|
131
|
+
* }
|
|
132
|
+
* );
|
|
133
|
+
*/
|
|
134
|
+
function createDegradationWrapper(proModule, fallbacks = {}) {
|
|
135
|
+
return new Proxy(proModule, {
|
|
136
|
+
get(target, prop) {
|
|
137
|
+
const original = target[prop];
|
|
138
|
+
|
|
139
|
+
if (typeof original !== 'function') {
|
|
140
|
+
return original;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return function (...args) {
|
|
144
|
+
try {
|
|
145
|
+
return original.apply(target, args);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
if (error instanceof ProFeatureError) {
|
|
148
|
+
if (fallbacks[prop]) {
|
|
149
|
+
logDegradationMessage(error.friendlyName, error.featureId);
|
|
150
|
+
return fallbacks[prop](...args);
|
|
151
|
+
}
|
|
152
|
+
// Re-throw if no fallback
|
|
153
|
+
throw error;
|
|
154
|
+
}
|
|
155
|
+
// Re-throw non-license errors
|
|
156
|
+
throw error;
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Check if the system is in degraded mode (expired/no license).
|
|
165
|
+
*
|
|
166
|
+
* @returns {boolean} true if pro features are unavailable
|
|
167
|
+
*/
|
|
168
|
+
function isInDegradedMode() {
|
|
169
|
+
const state = featureGate.getLicenseState();
|
|
170
|
+
return state === 'Expired' || state === 'Not Activated';
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Get degradation status summary.
|
|
175
|
+
*
|
|
176
|
+
* @returns {{ degraded: boolean, reason: string, action: string }}
|
|
177
|
+
*/
|
|
178
|
+
function getDegradationStatus() {
|
|
179
|
+
const state = featureGate.getLicenseState();
|
|
180
|
+
|
|
181
|
+
if (state === 'Active') {
|
|
182
|
+
return {
|
|
183
|
+
degraded: false,
|
|
184
|
+
reason: 'License is active',
|
|
185
|
+
action: null,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (state === 'Grace') {
|
|
190
|
+
return {
|
|
191
|
+
degraded: false,
|
|
192
|
+
reason: 'License in grace period - revalidate soon',
|
|
193
|
+
action: 'aios pro validate',
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (state === 'Expired') {
|
|
198
|
+
return {
|
|
199
|
+
degraded: true,
|
|
200
|
+
reason: 'License has expired',
|
|
201
|
+
action: 'aios pro activate --key <KEY>',
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
degraded: true,
|
|
207
|
+
reason: 'No license activated',
|
|
208
|
+
action: 'aios pro activate --key <KEY>',
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
module.exports = {
|
|
213
|
+
withGracefulDegradation,
|
|
214
|
+
ifProAvailable,
|
|
215
|
+
getFeatureFriendlyName,
|
|
216
|
+
logDegradationMessage,
|
|
217
|
+
createDegradationWrapper,
|
|
218
|
+
isInDegradedMode,
|
|
219
|
+
getDegradationStatus,
|
|
220
|
+
};
|