@aiwg/cli 2026.7.21 → 2026.7.24

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.
Files changed (32) hide show
  1. package/README.md +14 -3
  2. package/dist/src/api/index.d.ts +1 -0
  3. package/dist/src/api/index.js +1 -0
  4. package/dist/src/artifacts/browser-export.js +2 -0
  5. package/dist/src/artifacts/index-builder.js +44 -8
  6. package/dist/src/artifacts/query-engine.js +1 -1
  7. package/dist/src/artifacts/types.js +1 -0
  8. package/dist/src/cli/handlers/index.js +5 -1
  9. package/dist/src/cli/handlers/sessions.js +339 -40
  10. package/dist/src/cli/handlers/setup-manifest.js +800 -0
  11. package/dist/src/cli/handlers/use.js +127 -17
  12. package/dist/src/config/aiwg-config.js +18 -2
  13. package/dist/src/config/cli.js +16 -3
  14. package/dist/src/extensions/commands/definitions.js +99 -0
  15. package/dist/src/security/threat-assessment-config.js +296 -0
  16. package/dist/src/serve/sandbox-registry.js +34 -0
  17. package/dist/src/sessions/adapters/claude.js +37 -9
  18. package/dist/src/sessions/adapters/codex.js +38 -11
  19. package/dist/src/sessions/adapters/cursor.js +166 -10
  20. package/dist/src/sessions/adapters/factory.js +50 -9
  21. package/dist/src/sessions/batch-contracts.js +121 -0
  22. package/dist/src/sessions/batch-import.js +265 -0
  23. package/dist/src/sessions/contracts.js +32 -5
  24. package/dist/src/sessions/import-lease.js +152 -0
  25. package/dist/src/sessions/importer.js +163 -14
  26. package/dist/src/sessions/index.js +6 -0
  27. package/dist/src/sessions/origin.js +117 -0
  28. package/dist/src/sessions/readers.js +1 -1
  29. package/dist/src/sessions/repository.js +354 -13
  30. package/dist/src/sessions/timeline.js +148 -0
  31. package/dist/src/sessions/workspace-discovery.js +319 -0
  32. package/package.json +1 -1
@@ -576,7 +576,7 @@ function printSessionReloadNotice(provider) {
576
576
  *
577
577
  * @implements #609
578
578
  */
579
- async function countDeployedArtifacts(target, paths) {
579
+ async function countDeployedArtifacts(target, paths, provider) {
580
580
  const countMd = async (dir) => {
581
581
  if (!dir)
582
582
  return 0;
@@ -633,13 +633,11 @@ async function countDeployedArtifacts(target, paths) {
633
633
  }
634
634
  };
635
635
  // Kernel skills deploy to the platform-native skills dir (always-loaded
636
- // set) while standard skills sequester under <provider>/.aiwg/skills (the
637
- // index-driven discovery tier). Both contribute to the deployed surface,
638
- // so both must be counted (#1228). Derive the kernel path by stripping
639
- // the `.aiwg/` segment from the standard path.
640
- const kernelSkillsPath = paths.skills
641
- ? paths.skills.replace(/(^|\/)\.aiwg\/skills?$/, '$1skills')
642
- : '';
636
+ // set) while standard skills may sequester under <provider>/.aiwg/skills.
637
+ // Count the provider-declared kernel path directly; deriving it by stripping
638
+ // `.aiwg/` from the standard path produced `.codex/skills` instead of
639
+ // Codex's native `.agents/skills` path (#766).
640
+ const kernelSkillsPath = provider ? getProviderKernelSkillsPath(provider) : '';
643
641
  return {
644
642
  agents: await countMd(paths.agents),
645
643
  commands: await countMd(paths.commands),
@@ -785,6 +783,117 @@ async function countBundleSourceArtifacts(bundlePath) {
785
783
  rules: await countMd('rules'),
786
784
  };
787
785
  }
786
+ async function fileExists(filePath) {
787
+ try {
788
+ await fs.access(filePath);
789
+ return true;
790
+ }
791
+ catch {
792
+ return false;
793
+ }
794
+ }
795
+ function resolveDeployPath(target, deployPath) {
796
+ return path.isAbsolute(deployPath) ? deployPath : path.join(target, deployPath);
797
+ }
798
+ async function listBundleMdStems(bundlePath, subdir) {
799
+ try {
800
+ const entries = await fs.readdir(path.join(bundlePath, subdir));
801
+ return entries
802
+ .filter(entry => entry.endsWith('.md'))
803
+ .map(entry => path.basename(entry, '.md'));
804
+ }
805
+ catch {
806
+ return [];
807
+ }
808
+ }
809
+ async function countDeployedBundleFiles(bundlePath, subdir, target, deployPath, extensions) {
810
+ if (!deployPath)
811
+ return 0;
812
+ const stems = await listBundleMdStems(bundlePath, subdir);
813
+ if (stems.length === 0)
814
+ return 0;
815
+ const destDir = resolveDeployPath(target, deployPath);
816
+ let count = 0;
817
+ for (const stem of stems) {
818
+ for (const ext of extensions) {
819
+ if (await fileExists(path.join(destDir, `${stem}${ext}`))) {
820
+ count++;
821
+ break;
822
+ }
823
+ }
824
+ }
825
+ return count;
826
+ }
827
+ async function listBundleSkillNameCandidates(bundlePath) {
828
+ const skillsRoot = path.join(bundlePath, 'skills');
829
+ try {
830
+ const entries = await fs.readdir(skillsRoot, { withFileTypes: true });
831
+ const candidates = [];
832
+ for (const entry of entries) {
833
+ if (!entry.isDirectory())
834
+ continue;
835
+ const sourceName = entry.name;
836
+ const skillMd = path.join(skillsRoot, sourceName, 'SKILL.md');
837
+ let deployedName = sourceName;
838
+ try {
839
+ const content = await fs.readFile(skillMd, 'utf-8');
840
+ const match = content.match(/^---\s*\n([\s\S]*?)\n---/);
841
+ if (match) {
842
+ const parsed = YAML.parse(match[1]);
843
+ if (typeof parsed?.name === 'string' && parsed.name.trim()) {
844
+ deployedName = parsed.name.trim();
845
+ }
846
+ }
847
+ }
848
+ catch {
849
+ // Missing or invalid frontmatter still leaves the source dir name as
850
+ // the best deployed-name approximation for providers that copy dirs.
851
+ }
852
+ candidates.push([...new Set([deployedName, sourceName])]);
853
+ }
854
+ return candidates;
855
+ }
856
+ catch {
857
+ return [];
858
+ }
859
+ }
860
+ async function countDeployedBundleSkills(bundlePath, target, provider, paths) {
861
+ const skillCandidates = await listBundleSkillNameCandidates(bundlePath);
862
+ if (skillCandidates.length === 0)
863
+ return 0;
864
+ const candidateDirs = [
865
+ paths.skills,
866
+ getProviderKernelSkillsPath(provider),
867
+ ]
868
+ .filter(Boolean)
869
+ .map(dir => resolveDeployPath(target, dir));
870
+ const uniqueCandidateDirs = [...new Set(candidateDirs)];
871
+ let count = 0;
872
+ for (const names of skillCandidates) {
873
+ let found = false;
874
+ for (const dir of uniqueCandidateDirs) {
875
+ for (const name of names) {
876
+ if (!(await fileExists(path.join(dir, name, 'SKILL.md'))))
877
+ continue;
878
+ count++;
879
+ found = true;
880
+ break;
881
+ }
882
+ if (found)
883
+ break;
884
+ }
885
+ }
886
+ return count;
887
+ }
888
+ async function countBundleDeployedArtifacts(bundlePath, target, provider) {
889
+ const paths = getProviderPaths(provider);
890
+ return {
891
+ agents: await countDeployedBundleFiles(bundlePath, 'agents', target, paths.agents, ['.md', '.toml']),
892
+ commands: await countDeployedBundleFiles(bundlePath, 'commands', target, paths.commands, ['.md']),
893
+ skills: await countDeployedBundleSkills(bundlePath, target, provider, paths),
894
+ rules: await countDeployedBundleFiles(bundlePath, 'rules', target, paths.rules, ['.md', '.mdc']),
895
+ };
896
+ }
788
897
  /**
789
898
  * Deploy a single project-local bundle to one provider via deploy-agents.mjs.
790
899
  * Runs the same script and flags used for upstream addons, with the bundle
@@ -794,8 +903,8 @@ async function countBundleSourceArtifacts(bundlePath) {
794
903
  */
795
904
  async function deployOneProjectLocalBundle(opts) {
796
905
  const { bundle, ctx, frameworkRoot, provider, target, dryRun, verbose, quiet, modelArgs } = opts;
797
- const counts = await countBundleSourceArtifacts(bundle.artifactPath);
798
- const artifactTotal = counts.agents + counts.commands + counts.skills + counts.rules;
906
+ const sourceCounts = await countBundleSourceArtifacts(bundle.artifactPath);
907
+ const artifactTotal = sourceCounts.agents + sourceCounts.commands + sourceCounts.skills + sourceCounts.rules;
799
908
  let cliCommandCount = 0;
800
909
  try {
801
910
  const contribution = await loadCliCommandsContribution(bundle.artifactPath);
@@ -803,14 +912,14 @@ async function deployOneProjectLocalBundle(opts) {
803
912
  }
804
913
  catch (error) {
805
914
  ui.warn(`Invalid CLI contribution for project-local '${bundle.id}': ${error.message}`);
806
- return { exitCode: 1, counts };
915
+ return { exitCode: 1, counts: sourceCounts };
807
916
  }
808
917
  if (verbose || dryRun) {
809
- ui.dim(` Artifacts: agents=${counts.agents} commands=${counts.commands} skills=${counts.skills} rules=${counts.rules} cli=${cliCommandCount}`);
918
+ ui.dim(` Artifacts: agents=${sourceCounts.agents} commands=${sourceCounts.commands} skills=${sourceCounts.skills} rules=${sourceCounts.rules} cli=${cliCommandCount}`);
810
919
  }
811
920
  if (artifactTotal === 0 && cliCommandCount === 0) {
812
921
  ui.warn(`Project-local ${bundle.type} '${bundle.id}' has no deployable agents, commands, skills, rules, or CLI commands at ${bundle.artifactPath}`);
813
- return { exitCode: 1, counts };
922
+ return { exitCode: 1, counts: sourceCounts };
814
923
  }
815
924
  let exitCode = 0;
816
925
  if (artifactTotal > 0) {
@@ -868,8 +977,9 @@ async function deployOneProjectLocalBundle(opts) {
868
977
  exitCode = 1;
869
978
  }
870
979
  }
871
- // Approximate counts from the bundle's source dirs (deploy-agents.mjs is
872
- // idempotent and copies file-for-file from these dirs)
980
+ const counts = exitCode === 0 && !dryRun
981
+ ? await countBundleDeployedArtifacts(bundle.artifactPath, target, provider)
982
+ : sourceCounts;
873
983
  void ctx;
874
984
  return { exitCode, counts };
875
985
  }
@@ -1865,7 +1975,7 @@ export class UseHandler {
1865
1975
  provider: providerName,
1866
1976
  cwd: target,
1867
1977
  });
1868
- const counts = await countDeployedArtifacts(target, paths);
1978
+ const counts = await countDeployedArtifacts(target, paths, providerName);
1869
1979
  if (quiet) {
1870
1980
  ui.blank();
1871
1981
  if (counts.agents > 0)
@@ -2548,7 +2658,7 @@ export class UseHandler {
2548
2658
  if (quiet) {
2549
2659
  // Count deployed artifacts
2550
2660
  const paths = getProviderPaths(provider);
2551
- counts = await countDeployedArtifacts(target, paths);
2661
+ counts = await countDeployedArtifacts(target, paths, provider);
2552
2662
  if (counts.agents > 0)
2553
2663
  ui.deployCount('Agents', counts.agents);
2554
2664
  if (counts.commands > 0)
@@ -12,9 +12,10 @@ import { readFile, writeFile, mkdir, access, readdir, rename, unlink } from 'fs/
12
12
  import { createHash, randomBytes } from 'crypto';
13
13
  import { resolve, join, isAbsolute } from 'path';
14
14
  import { normalizeNamedCaptures } from '../artifacts/index-builder.js';
15
- import { getProviderDefinition, PROVIDER_IDS, resolveProviderPathValue, } from '../providers/provider-definitions.js';
15
+ import { getProviderDefinition, getProviderKernelSkillPath, PROVIDER_IDS, resolveProviderPathValue, } from '../providers/provider-definitions.js';
16
16
  import { validateAuthorization, } from '../policy/authorization.js';
17
17
  import { projectAiwgPath, resolveProjectAiwgDir } from './project-artifacts.js';
18
+ import { defaultThreatAssessmentConfig, validateThreatAssessmentConfig, } from '../security/threat-assessment-config.js';
18
19
  const CONFIG_FILENAME = 'aiwg.config';
19
20
  /**
20
21
  * Operations that a workspace may authorize for one member repository.
@@ -633,6 +634,9 @@ export function emptyConfig(providers = ['claude']) {
633
634
  providers,
634
635
  installed: {},
635
636
  scripts: {},
637
+ security: {
638
+ threatAssessment: defaultThreatAssessmentConfig(),
639
+ },
636
640
  delivery: {
637
641
  mode: 'pr-required',
638
642
  default_branch: 'main',
@@ -711,12 +715,20 @@ export async function readAiwgConfig(projectDir) {
711
715
  if (authorizationErrors.length > 0) {
712
716
  throw new Error(`Invalid .aiwg/aiwg.config:\n${authorizationErrors.map(item => item.message).join('\n')}`);
713
717
  }
718
+ const threatAssessmentErrors = validateThreatAssessmentConfig(parsed.security?.threatAssessment);
719
+ if (threatAssessmentErrors.length > 0) {
720
+ throw new Error(`Invalid .aiwg/aiwg.config:\n${threatAssessmentErrors.join('\n')}`);
721
+ }
714
722
  return parsed;
715
723
  }
716
724
  /**
717
725
  * Write aiwg.config, creating the resolved AIWG artifact directory if needed.
718
726
  */
719
727
  export async function writeAiwgConfig(projectDir, config) {
728
+ const threatAssessmentErrors = validateThreatAssessmentConfig(config.security?.threatAssessment);
729
+ if (threatAssessmentErrors.length > 0) {
730
+ throw new Error(`Invalid .aiwg/aiwg.config:\n${threatAssessmentErrors.join('\n')}`);
731
+ }
720
732
  const dir = resolveProjectAiwgDir(projectDir);
721
733
  await mkdir(dir, { recursive: true });
722
734
  const filePath = join(dir, CONFIG_FILENAME);
@@ -823,6 +835,7 @@ function getProviderDeployDirs(provider, projectDir) {
823
835
  return {
824
836
  agents: resolveProviderPathValue(artifacts.agents, projectDir),
825
837
  skills: resolveProviderPathValue(artifacts.skills, projectDir),
838
+ kernelSkills: resolveProviderPathValue(getProviderKernelSkillPath(provider), projectDir),
826
839
  commands: resolveProviderPathValue(artifacts.commands, projectDir),
827
840
  rules: resolveProviderPathValue(artifacts.rules, projectDir),
828
841
  };
@@ -865,7 +878,10 @@ export async function populateDeployedTo(config, projectDir) {
865
878
  const counts = {
866
879
  agents: await countDeployedInDir(projectDir, dirs.agents, 'md'),
867
880
  commands: await countDeployedInDir(projectDir, dirs.commands, 'md'),
868
- skills: await countDeployedInDir(projectDir, dirs.skills, 'dirs'),
881
+ skills: (await countDeployedInDir(projectDir, dirs.skills, 'dirs')) +
882
+ (dirs.kernelSkills && dirs.kernelSkills !== dirs.skills
883
+ ? await countDeployedInDir(projectDir, dirs.kernelSkills, 'dirs')
884
+ : 0),
869
885
  rules: await countDeployedInDir(projectDir, dirs.rules, 'md'),
870
886
  };
871
887
  // Only populate if at least one artifact type is present
@@ -152,6 +152,7 @@ const ENUM_RULES = {
152
152
  'remotes.tracker_actor.via': ['tea', 'gh', 'mcp', 'api'],
153
153
  'remotes.transport.protocol': ['ssh', 'https'],
154
154
  'repo_maintainer.tiers.local': ['collaborator', 'maintainer', 'admin'],
155
+ 'security.threatAssessment.mode': ['off', 'audit', 'enforce'],
155
156
  };
156
157
  const BOOLEAN_FIELDS = new Set([
157
158
  'delivery.delete_branch_on_merge',
@@ -214,15 +215,17 @@ async function projectConfigSet(key, raw, args) {
214
215
  }
215
216
  // Coerce booleans for known boolean fields
216
217
  let value = raw;
217
- if (/^externalLinks\.[^.]+$/.test(key)) {
218
+ if (/^externalLinks\.[^.]+$/.test(key) || key === 'security.threatAssessment') {
218
219
  try {
219
220
  value = JSON.parse(raw);
220
221
  }
221
222
  catch {
222
223
  throw new AiwgError({
223
224
  code: 'ERR_INVALID_VALUE',
224
- message: `${key} must be a JSON object containing label and url`,
225
- hint: `Try: aiwg config set --project ${key} '{"label":"Project docs","url":"https://example.com/docs"}'`,
225
+ message: `${key} must be a valid JSON object`,
226
+ hint: key === 'security.threatAssessment'
227
+ ? `Try: aiwg config set --project ${key} '{"schemaVersion":"1","mode":"audit","defaultProfile":"balanced"}'`
228
+ : `Try: aiwg config set --project ${key} '{"label":"Project docs","url":"https://example.com/docs"}'`,
226
229
  exitCode: EXIT_CODES.USAGE,
227
230
  });
228
231
  }
@@ -285,6 +288,16 @@ async function projectConfigSet(key, raw, args) {
285
288
  exitCode: EXIT_CODES.USAGE,
286
289
  });
287
290
  }
291
+ const { validateThreatAssessmentConfig } = await import('../security/threat-assessment-config.js');
292
+ const threatErrors = validateThreatAssessmentConfig(cfg.security?.threatAssessment);
293
+ if (threatErrors.length > 0) {
294
+ throw new AiwgError({
295
+ code: 'ERR_INVALID_VALUE',
296
+ message: `Invalid threat-assessment configuration: ${threatErrors.join('; ')}`,
297
+ hint: 'Use a built-in profile or correct the referenced profile, rule pack, threshold, or regex.',
298
+ exitCode: EXIT_CODES.USAGE,
299
+ });
300
+ }
288
301
  await writeAiwgConfig(projectDir, cfg);
289
302
  console.log(`Set --project ${key} = ${raw}`);
290
303
  }
@@ -593,6 +593,102 @@ export const setupCommand = {
593
593
  },
594
594
  },
595
595
  };
596
+ export const setupGenerateCommand = {
597
+ id: 'setup-generate',
598
+ type: 'skill',
599
+ name: 'Setup Generate',
600
+ description: 'Generate starter setup.aiwg.io/v1 SetupManifest assets for agentic installer automation',
601
+ version: '1.0.0',
602
+ capabilities: ['cli', 'project', 'setup', 'setup-manifest', 'agentic-installer', 'generation', 'automation'],
603
+ keywords: ['setup-generate', 'setup generate', 'SetupManifest', 'setup.aiwg.io/v1', 'agentic-installer', 'manifest generation', 'starter manifest'],
604
+ category: 'project',
605
+ platforms: {
606
+ claude: 'full',
607
+ generic: 'full',
608
+ },
609
+ deployment: {
610
+ pathTemplate: '.{platform}/commands/{id}.md',
611
+ core: true,
612
+ },
613
+ metadata: {
614
+ type: 'skill',
615
+ triggerPhrases: [
616
+ 'generate setup manifest',
617
+ 'create setup.manifest.yaml',
618
+ 'setup-generate',
619
+ 'scaffold installer manifest',
620
+ ],
621
+ commandHint: {
622
+ template: 'utility',
623
+ argumentHint: '[--output <path>] [--name <name>] [--type user|developer|ci] [--platform <os>] [--force] [--json]',
624
+ allowedTools: ['Read', 'Write', 'Bash'],
625
+ },
626
+ },
627
+ };
628
+ export const setupValidateCommand = {
629
+ id: 'setup-validate',
630
+ type: 'skill',
631
+ name: 'Setup Validate',
632
+ description: 'Validate setup.aiwg.io/v1 SetupManifest files against the canonical schema and installer consistency checks',
633
+ version: '1.0.0',
634
+ capabilities: ['cli', 'project', 'setup', 'setup-manifest', 'agentic-installer', 'validation', 'schema'],
635
+ keywords: ['setup-validate', 'setup validate', 'SetupManifest', 'setup.aiwg.io/v1', 'agentic-installer', 'manifest validation', 'schema validation', 'installer consistency'],
636
+ category: 'project',
637
+ platforms: {
638
+ claude: 'full',
639
+ generic: 'full',
640
+ },
641
+ deployment: {
642
+ pathTemplate: '.{platform}/commands/{id}.md',
643
+ core: true,
644
+ },
645
+ metadata: {
646
+ type: 'skill',
647
+ triggerPhrases: [
648
+ 'validate setup manifest',
649
+ 'check setup.manifest.yaml',
650
+ 'setup-validate',
651
+ 'lint installer manifest',
652
+ ],
653
+ commandHint: {
654
+ template: 'utility',
655
+ argumentHint: '[manifest-path] [--manifest <path>] [--strict] [--fix] [--json]',
656
+ allowedTools: ['Read', 'Bash'],
657
+ },
658
+ },
659
+ };
660
+ export const setupRunCommand = {
661
+ id: 'setup-run',
662
+ type: 'skill',
663
+ name: 'Setup Run',
664
+ description: 'Validate and execute setup.aiwg.io/v1 SetupManifest files with installer safety gates',
665
+ version: '1.0.0',
666
+ capabilities: ['cli', 'project', 'setup', 'setup-manifest', 'agentic-installer', 'installer', 'dry-run'],
667
+ keywords: ['setup-run', 'setup run', 'SetupManifest', 'setup.aiwg.io/v1', 'agentic-installer', 'installer runner', 'dry run', 'recovery', 'params'],
668
+ category: 'project',
669
+ platforms: {
670
+ claude: 'full',
671
+ generic: 'full',
672
+ },
673
+ deployment: {
674
+ pathTemplate: '.{platform}/commands/{id}.md',
675
+ core: true,
676
+ },
677
+ metadata: {
678
+ type: 'skill',
679
+ triggerPhrases: [
680
+ 'run setup manifest',
681
+ 'execute installer manifest',
682
+ 'setup-run',
683
+ 'run dev setup',
684
+ ],
685
+ commandHint: {
686
+ template: 'utility',
687
+ argumentHint: '[manifest-path] [--manifest <path>] [--dry-run] [--platform <os>] [--params-file <path>] [--param KEY=VALUE] [--step <id>] [--skip <ids>] [--yes]',
688
+ allowedTools: ['Read', 'Bash'],
689
+ },
690
+ },
691
+ };
596
692
  export const issueCommand = {
597
693
  id: 'issue',
598
694
  type: 'skill',
@@ -3362,6 +3458,9 @@ export const commandDefinitions = [
3362
3458
  newCommand,
3363
3459
  initCommand,
3364
3460
  setupCommand,
3461
+ setupGenerateCommand,
3462
+ setupValidateCommand,
3463
+ setupRunCommand,
3365
3464
  issueCommand,
3366
3465
  issueAuditCommand,
3367
3466
  addressIssuesCommand,