Skip to content
Merged
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
76 changes: 70 additions & 6 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,76 @@ Thanks for helping improve AutoCLI.
bun install
```

3. Run the core checks before opening a pull request:
3. Run the local CLI directly while developing:

```bash
bun run typecheck
bun test
bun run build
bun run dev --help
bun run dev status
```

4. For local CLI testing:
4. Link the CLI globally if you want to test the installed command shape:

```bash
bun run link:global
autocli --help
```

## Useful Scripts

Use these scripts instead of updating generated files by hand:

```bash
bun run generate:platform-registry
bun run generate:readme
bun run generate:skill-providers
bun run sync:skills
bun run sync:docs
```

- `bun run generate:platform-registry` rebuilds generated platform metadata and runtime registry files.
- `bun run generate:readme` refreshes the generated sections inside `README.md`.
- `bun run generate:skill-providers` rebuilds provider reference docs under `skills/autocli/references/providers`.
- `bun run sync:skills` regenerates skill provider references and syncs the installed Codex skill.
- `bun run sync:docs` refreshes the README, regenerates skill provider references, and syncs the installed Codex skill.

## Testing and Verification

Pick the smallest useful check for the change you made, then run the full set before a release-sensitive change or broad refactor.

### Fast checks

For docs or generated-reference changes:

```bash
bun run sync:docs
```

For code changes:

```bash
bun run typecheck
bun run build
```

For a focused test while iterating:

```bash
bun test src/path/to/test-file.test.ts
```

### Pre-pull-request checks

Before opening a pull request, run the core checks:

```bash
bun run sync:docs
bun run typecheck
bun test
bun run build
```

`prepublishOnly` runs this same high-level flow automatically for release builds.

## Project Structure

- `src/platforms/<category>/<provider>` contains provider logic.
Expand All @@ -36,11 +91,19 @@ autocli --help

- Keep command surfaces consistent with the rest of the CLI.
- Add or update tests for new behavior.
- Update `README.md` when command surfaces or auth requirements change.
- Regenerate docs when command surfaces, provider counts, auth requirements, or skill references change.
- Prefer clear, structured `--json` output for anything an agent might consume.
- Mark partial or experimental support honestly in manifests and docs.
- List results should use the stable `data.items` alias to work with `--filter` and `--select` global flags.
- When adding new output functions, pass the optional `context?: Partial<CommandContext>` parameter so filtering can be applied transparently.
- Prefer updating the source manifest/metadata and then running the generators instead of editing generated files manually.

## Docs and Generated Files

- `README.md` contains generated marker-based sections. Use `bun run generate:readme` or `bun run sync:docs` after provider or metadata changes.
- `skills/autocli/references/providers` is generated. Use `bun run generate:skill-providers` or `bun run sync:skills`.
- Platform registry files under `src/platforms/generated-*` are generated from provider manifests. Use `bun run generate:platform-registry`.
- If a generated file changes unexpectedly, review the source manifest or shared runtime metadata first.

## Auth, Sessions, and Test Fixtures

Expand All @@ -53,6 +116,7 @@ autocli --help
- Keep pull requests focused.
- Include the user-facing command examples for new providers or commands.
- Mention any new system dependencies such as `ffmpeg`, `qpdf`, or `tesseract`.
- Mention which verification steps you ran, especially if you only ran targeted tests instead of the full suite.

## Questions and Ideas

Expand Down
417 changes: 247 additions & 170 deletions README.md

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
},
"scripts": {
"generate:platform-registry": "bun run scripts/generate-platform-registry.ts",
"pregenerate:readme": "bun run generate:platform-registry",
"pregenerate:skill-providers": "bun run generate:platform-registry",
"predev": "bun run generate:platform-registry",
"dev": "bun run src/index.ts",
Expand All @@ -47,9 +48,11 @@
"start": "bun run src/index.ts",
"prebuild": "bun run generate:platform-registry",
"prepare": "bun run build",
"prepublishOnly": "bun run sync:skills && bun run typecheck && bun test && bun run build",
"prepublishOnly": "bun run sync:docs && bun run typecheck && bun test && bun run build",
"link:global": "bun run scripts/link-global.ts",
"generate:readme": "bun run scripts/generate-readme.ts",
"generate:skill-providers": "bun run scripts/generate-skill-provider-references.ts",
"sync:docs": "bun run generate:readme && bun run sync:skills",
"sync:skills": "bun run scripts/sync-skills.ts",
"pretypecheck": "bun run generate:platform-registry",
"typecheck": "tsc --noEmit",
Expand Down
252 changes: 252 additions & 0 deletions scripts/generate-readme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
#!/usr/bin/env bun

import { readFile, writeFile } from "node:fs/promises";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";

import { resolvePlatformCapabilityMetadata } from "../src/core/runtime/platform-capability-metadata.js";
import { getPlatformCategories, getPlatformDefinitions, getPlatformDefinitionsByCategory } from "../src/platforms/index.js";
import { buildPlatformCommandPrefix } from "../src/core/runtime/platform-command-prefix.js";

import type { AuthStrategyKind } from "../src/core/auth/auth-types.js";
import type { PlatformCategory, PlatformDefinition } from "../src/core/runtime/platform-definition.js";

const __dirname = dirname(fileURLToPath(import.meta.url));
const repoRoot = dirname(__dirname);
const readmePath = resolve(repoRoot, "README.md");

const GENERATED_BLOCKS = {
badges: "badges",
whyItMattersCount: "why-it-matters-count",
atAGlance: "at-a-glance",
commandModelCategories: "command-model-categories",
categoryOverview: "category-overview",
providerMatrix: "provider-matrix",
} as const;

const AUTH_LABELS: Record<AuthStrategyKind, string> = {
cookies: "cookies",
oauth2: "oauth2",
apiKey: "api token",
botToken: "bot token",
session: "session",
none: "none",
};

const CATEGORY_SUMMARIES: Record<PlatformCategory, string> = {
llm: "Prompting, chat, image, and generation workflows.",
editor: "Local file, media, and document transformations.",
finance: "Market, forex, and crypto lookups.",
data: "Structured data cleanup, conversion, filtering, and extraction.",
google: "Google Workspace APIs and account-backed productivity flows.",
maps: "Geocoding, routing, elevation, and geometry helpers.",
movie: "Title lookup, recommendations, and streaming availability.",
news: "Headline discovery, source search, and feed aggregation.",
music: "Music discovery, playback, and library-style workflows.",
social: "Posting, profile lookup, messaging, and public social reads.",
careers: "Job search and hiring discovery workflows.",
shopping: "Product discovery plus cart and order surfaces where supported.",
developer: "Code hosting, issues, docs, and workspace automation.",
devops: "Infrastructure, deployments, DNS, and uptime automation.",
bot: "Bot-token messaging and chat ops.",
tools: "Public utilities, temp mail, downloads, transcripts, and web helpers.",
forum: "Forum-style communities, threads, and discussions.",
};

const CATEGORY_HEADINGS: Partial<Record<PlatformCategory, string>> = {
llm: "LLM",
devops: "DevOps",
};

interface ReadmeContext {
categories: readonly PlatformCategory[];
providerCount: number;
categoryCount: number;
}

export async function renderReadme(): Promise<string> {
const template = await readFile(readmePath, "utf8");
return renderReadmeFromTemplate(template);
}

export function renderReadmeFromTemplate(template: string): string {
const normalizedTemplate = template.replace(/\r\n/g, "\n");
const definitions = [...getPlatformDefinitions()];
const categories = [...getPlatformCategories()];
const context: ReadmeContext = {
categories,
providerCount: definitions.length,
categoryCount: categories.length,
};

let output = normalizedTemplate;
output = replaceGeneratedBlock(output, GENERATED_BLOCKS.badges, renderBadges(context));
output = replaceGeneratedBlock(output, GENERATED_BLOCKS.whyItMattersCount, renderWhyItMattersCount(context));
output = replaceGeneratedBlock(output, GENERATED_BLOCKS.atAGlance, renderAtAGlance(context));
output = replaceGeneratedBlock(output, GENERATED_BLOCKS.commandModelCategories, renderCommandModelCategories(context));
output = replaceGeneratedBlock(output, GENERATED_BLOCKS.categoryOverview, renderCategoryOverview(context));
output = replaceGeneratedBlock(output, GENERATED_BLOCKS.providerMatrix, renderProviderMatrix(context));

return output.trimEnd() + "\n";
}

export async function generateReadme(): Promise<void> {
const readme = await renderReadme();
await writeFile(readmePath, readme, "utf8");
}

function renderBadges(context: ReadmeContext): string {
return [
`[![npm version](https://img.shields.io/npm/v/%40vk007%2Fautocli)](https://www.npmjs.com/package/@vk007/autocli)`,
`[![license](https://img.shields.io/github/license/vkop007/autocli)](./LICENSE)`,
`[![providers](https://img.shields.io/badge/providers-${context.providerCount}-blue)](#category-overview)`,
`[![categories](https://img.shields.io/badge/categories-${context.categoryCount}-6f42c1)](#category-overview)`,
].join("\n");
}

function renderWhyItMattersCount(context: ReadmeContext): string {
return `- One command surface across \`${context.providerCount}\` providers.`;
}

function renderAtAGlance(context: ReadmeContext): string {
return [
"| Item | Value |",
"| --- | --- |",
"| Package | `@vk007/autocli` |",
"| CLI command | `autocli` |",
`| Providers | \`${context.providerCount}\` |`,
`| Categories | \`${context.categoryCount}\` |`,
"| npm install | `npm install -g @vk007/autocli` |",
"| bun install | `bun install -g @vk007/autocli` |",
"| Local setup | `bun install` |",
"| Docs sync | `bun run sync:docs` |",
].join("\n");
}

function renderCommandModelCategories(context: ReadmeContext): string {
return context.categories.map((category) => `- \`autocli ${category} ...\``).join("\n");
}

function renderCategoryOverview(context: ReadmeContext): string {
const lines = [
"This inventory is generated from the live platform registry.",
"",
"| Category | Representative providers | Count | Auth modes | Use it for | Route |",
"| --- | --- | ---: | --- | --- | --- |",
];

for (const category of context.categories) {
const definitions = [...getPlatformDefinitionsByCategory(category)].sort((left, right) => left.id.localeCompare(right.id));
const providers = formatRepresentativeProviders(definitions);
const authModes = formatCategoryAuthModes(definitions);
const route = `\`autocli ${category} ...\``;
lines.push(
`| \`${category}\` | ${providers} | ${definitions.length} | ${authModes} | ${CATEGORY_SUMMARIES[category]} | ${route} |`,
);
}

lines.push("");
lines.push(`AutoCLI currently exposes \`${context.providerCount}\` providers across \`${context.categoryCount}\` active command groups.`);
return lines.join("\n");
}

function renderProviderMatrix(context: ReadmeContext): string {
const sections = [
"The tables below are generated from provider manifests and runtime capability metadata, so they stay aligned with `autocli <category> <provider> capabilities --json`.",
"",
];

for (const category of context.categories) {
const definitions = [...getPlatformDefinitionsByCategory(category)].sort((left, right) => left.displayName.localeCompare(right.displayName));
sections.push(`### ${CATEGORY_HEADINGS[category] ?? toTitleCase(category)}`);
sections.push("");
sections.push("| Provider | Stability | Auth | Read | Write | Browser login | Async jobs | Command |");
sections.push("| --- | --- | --- | --- | --- | --- | --- | --- |");

for (const definition of definitions) {
const metadata = resolvePlatformCapabilityMetadata(definition);
sections.push(
`| ${definition.displayName} | \`${metadata.stability}\` | ${formatAuthStrategies(definition.authStrategies)} | \`${metadata.discovery}\` | \`${metadata.mutation}\` | \`${metadata.browserLogin}\` | \`${metadata.asyncJobs}\` | \`${buildPlatformCommandPrefix(definition)}\` |`,
);
}

const notes = collectProviderNotes(definitions);
if (notes.length > 0) {
sections.push("");
sections.push("Notes:");
for (const note of notes) {
sections.push(`- ${note}`);
}
}

sections.push("");
}

return sections.join("\n").trimEnd();
}

function collectProviderNotes(definitions: readonly PlatformDefinition[]): string[] {
const notes: string[] = [];

for (const definition of definitions) {
const metadata = resolvePlatformCapabilityMetadata(definition);
for (const note of metadata.notes ?? []) {
notes.push(`\`${definition.id}\`: ${note}`);
}
}

return notes;
}

function formatRepresentativeProviders(definitions: readonly PlatformDefinition[]): string {
const names = definitions.map((definition) => `\`${definition.id}\``);
if (names.length <= 5) {
return names.join(", ");
}

const visible = names.slice(0, 5).join(", ");
return `${visible}, +${names.length - 5} more`;
}

function formatCategoryAuthModes(definitions: readonly PlatformDefinition[]): string {
const values = new Set<string>();

for (const definition of definitions) {
for (const strategy of definition.authStrategies) {
values.add(AUTH_LABELS[strategy]);
}
}

return Array.from(values).sort((left, right) => left.localeCompare(right)).map((value) => `\`${value}\``).join(", ");
}

function formatAuthStrategies(strategies: readonly AuthStrategyKind[]): string {
return strategies.map((strategy) => `\`${AUTH_LABELS[strategy]}\``).join(", ");
}

function replaceGeneratedBlock(readme: string, name: string, content: string): string {
const start = `<!-- GENERATED:${name}:start -->`;
const end = `<!-- GENERATED:${name}:end -->`;
const pattern = new RegExp(`${escapeForRegExp(start)}[\\s\\S]*?${escapeForRegExp(end)}`);

if (!pattern.test(readme)) {
throw new Error(`README is missing generated block markers for "${name}".`);
}

return readme.replace(pattern, `${start}\n${content.trimEnd()}\n${end}`);
}

function escapeForRegExp(value: string): string {
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

function toTitleCase(value: string): string {
return value
.split("-")
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
.join(" ");
}

if (import.meta.main) {
await generateReadme();
}
6 changes: 3 additions & 3 deletions skills/autocli/references/providers/indeed.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ Generated from the real AutoCLI provider definition and command tree.
- Command prefix: `autocli careers indeed`
- Aliases: none
- Auth: `none`
- Stability: `unknown`
- Discovery: `unknown`
- Mutation: `unknown`
- Stability: `stable`
- Discovery: `supported`
- Mutation: `unsupported`
- Browser login: `unsupported`
- Browser fallback: `unsupported`
- Async jobs: `unsupported`
Expand Down
Loading
Loading