UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

605 lines • 25.8 kB
// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. ========= // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ========= Copyright 2026 @ Strukto.AI All Rights Reserved. ========= import { GitHubAccount } from "../../../../accessor/github.js"; import { GhConfig } from "../../../../core/github/config.js"; import { IOResult } from "../../../../io/types.js"; import { compareCodePoints } from "../../../../utils/sort.js"; import { Argument, CommandSpec, UsageStyle } from "../../../spec/types.js"; import { CLI, CLIHandler } from "../../types.js"; import { findChild, nodeHelp } from "../../walk.js"; import { runListCmd, runRerunCmd, runViewCmd, workflowListCmd, workflowRunCmd, workflowViewCmd, } from "./actions.js"; import { api } from "./api.js"; import { status as authStatus, token as authToken } from "./auth.js"; import { BOOLEAN, HELP_TOPICS, REPO_EDIT_FIELDS } from "./constants.js"; import { closeCmd as issueClose, commentCmd as issueComment, createCmd as issueCreate, editCmd as issueEdit, listCmd as issueList, reopenCmd as issueReopen, viewCmd as issueView, } from "./issue.js"; import { checksCmd as prChecks, closeCmd as prClose, commentCmd as prComment, createCmd as prCreate, diffCmd as prDiff, editCmd as prEdit, listCmd as prList, mergeCmd as prMerge, reopenCmd as prReopen, viewCmd as prView, } from "./pull.js"; import { createCmd as releaseCreate, listCmd as releaseList, viewCmd as releaseView, } from "./release.js"; import { fork, rename, cloneCmd as repoClone, createCmd as repoCreate, deleteCmd as repoDelete, editCmd as repoEdit, listCmd as repoList, view as repoView, } from "./repo.js"; import { searchHandlers, searchSpec } from "./search.js"; import { version } from "./version.js"; const REPO = new Argument(['-R', '--repo'], { help: 'Select another repository, as [HOST/]OWNER/REPO', }); const JSON_FIELDS = new Argument('--json', { help: 'Output selected JSON fields' }); const JQ = new Argument(['-q', '--jq'], { help: 'Filter JSON output' }); const LIMIT_30 = new Argument(['-L', '--limit'], { type: 'int', default: '30' }); const BODY = new Argument(['-b', '--body']); const BODY_FILE = new Argument(['-F', '--body-file'], { type: 'path' }); const TITLE = new Argument(['-t', '--title']); const REOPEN_COMMENT = new Argument(['-c', '--comment'], { help: 'Post this comment before reopening', }); const NUMBER = new Argument('NUMBER'); // gh's boolean flags are pflag's: a bare `--draft` is true, and `--draft=true` // or `--draft=false` spells the value out, which is how a script turns one // off. Their shorts still cluster (`-sd`). Read one with `ghBool`. function flag(init) { return new Argument([...(init.short === undefined ? [] : [init.short]), init.long], { nargs: '?', attachedOnly: true, help: init.description ?? null, shortValue: false, choices: BOOLEAN, }); } // The grammar and request mapping consume the same setting definition. function repoEditOption(field) { return new Argument([...(field.short === undefined ? [] : [field.short]), field.flag], { nargs: field.kind !== 'value' ? '?' : null, attachedOnly: field.kind !== 'value', choices: field.kind === 'value' ? [...(field.choices ?? [])] : BOOLEAN, help: field.description, }); } function issue() { return new CommandSpec({ name: 'issue', description: 'Manage issues', subcommands: [ new CommandSpec({ name: 'list', aliases: ['ls'], description: 'List issues', arguments: [ REPO, JSON_FIELDS, JQ, LIMIT_30, new Argument(['-s', '--state'], { choices: ['open', 'closed', 'all'], default: 'open' }), new Argument(['-a', '--assignee']), new Argument(['-A', '--author']), new Argument(['-l', '--label'], { action: 'append' }), ], }), new CommandSpec({ name: 'view', description: 'View an issue', arguments: [ REPO, JSON_FIELDS, JQ, flag({ short: '-c', long: '--comments', description: 'Show comments' }), NUMBER, ], }), new CommandSpec({ name: 'create', aliases: ['new'], description: 'Create an issue', arguments: [ REPO, TITLE, BODY, BODY_FILE, new Argument(['-a', '--assignee'], { action: 'append' }), new Argument(['-l', '--label'], { action: 'append' }), ], }), new CommandSpec({ name: 'edit', description: 'Edit an issue', arguments: [ REPO, TITLE, BODY, BODY_FILE, new Argument('--add-assignee', { action: 'append' }), new Argument('--remove-assignee', { action: 'append' }), new Argument('--add-label', { action: 'append' }), new Argument('--remove-label', { action: 'append' }), NUMBER, ], }), new CommandSpec({ name: 'close', description: 'Close an issue', arguments: [REPO, NUMBER] }), new CommandSpec({ name: 'reopen', description: 'Reopen an issue', arguments: [REPO, REOPEN_COMMENT, NUMBER], }), new CommandSpec({ name: 'comment', description: 'Add a comment to an issue', arguments: [REPO, BODY, BODY_FILE, NUMBER], }), ], }); } function pr() { return new CommandSpec({ name: 'pr', description: 'Manage pull requests', subcommands: [ new CommandSpec({ name: 'list', aliases: ['ls'], description: 'List pull requests', arguments: [ REPO, JSON_FIELDS, JQ, LIMIT_30, new Argument(['-s', '--state'], { choices: ['open', 'closed', 'merged', 'all'], default: 'open', }), new Argument(['-B', '--base']), new Argument(['-H', '--head']), ], }), new CommandSpec({ name: 'view', description: 'View a pull request', arguments: [ REPO, JSON_FIELDS, JQ, flag({ short: '-c', long: '--comments', description: 'Show comments' }), NUMBER, ], }), new CommandSpec({ name: 'create', aliases: ['new'], description: 'Create a pull request', arguments: [ REPO, TITLE, BODY, BODY_FILE, new Argument(['-H', '--head']), new Argument(['-B', '--base']), flag({ short: '-d', long: '--draft' }), flag({ long: '--no-maintainer-edit' }), ], }), new CommandSpec({ name: 'edit', description: 'Edit a pull request', arguments: [REPO, TITLE, BODY, BODY_FILE, new Argument(['-B', '--base']), NUMBER], }), new CommandSpec({ name: 'merge', description: 'Merge a pull request', arguments: [ REPO, BODY, BODY_FILE, flag({ short: '-m', long: '--merge' }), flag({ short: '-r', long: '--rebase' }), flag({ short: '-s', long: '--squash' }), new Argument(['-t', '--subject']), new Argument('--match-head-commit'), NUMBER, ], }), new CommandSpec({ name: 'close', description: 'Close a pull request', arguments: [REPO, NUMBER], }), new CommandSpec({ name: 'reopen', description: 'Reopen a pull request', arguments: [REPO, REOPEN_COMMENT, NUMBER], }), new CommandSpec({ name: 'comment', description: 'Add a comment to a pull request', arguments: [REPO, BODY, BODY_FILE, NUMBER], }), new CommandSpec({ name: 'diff', description: 'View changes in a pull request', arguments: [ REPO, flag({ long: '--name-only', description: 'Display only names of changed files' }), NUMBER, ], }), new CommandSpec({ name: 'checks', description: 'Show CI checks for a pull request', arguments: [REPO, JSON_FIELDS, JQ, NUMBER], }), ], }); } function repo() { return new CommandSpec({ name: 'repo', description: 'Manage repositories', subcommands: [ new CommandSpec({ name: 'list', aliases: ['ls'], description: 'List repositories', arguments: [JSON_FIELDS, JQ, LIMIT_30, new Argument('OWNER', { nargs: '?' })], }), new CommandSpec({ name: 'clone', description: 'Clone a repository locally', arguments: [ new Argument(['-u', '--upstream-remote-name'], { help: 'Upstream remote name when cloning a fork', }), flag({ long: '--no-upstream', description: 'Do not add an upstream remote when cloning a fork', }), new Argument('REPOSITORY', { nargs: '?' }), new Argument('DIRECTORY', { nargs: '?' }), new Argument('GITFLAGS', { nargs: '*' }), ], }), new CommandSpec({ name: 'view', description: 'View a repository', arguments: [REPO, JSON_FIELDS, JQ, new Argument('REPOSITORY', { nargs: '?' })], }), new CommandSpec({ name: 'create', description: 'Create a repository', arguments: [ flag({ long: '--public' }), flag({ long: '--private' }), new Argument(['-d', '--description']), new Argument(['-h', '--homepage']), flag({ long: '--add-readme' }), new Argument('NAME', { nargs: '?' }), ], }), new CommandSpec({ name: 'fork', description: 'Create a fork of a repository', arguments: [ flag({ long: '--clone', description: 'Clone the fork' }), flag({ long: '--default-branch-only', description: 'Only include the default branch in the fork', }), new Argument('--fork-name', { help: 'Rename the forked repository' }), new Argument('--org', { help: 'Create the fork in an organization' }), flag({ long: '--remote', description: 'Add a git remote for the fork' }), new Argument('--remote-name', { help: 'Specify the name for the new remote' }), new Argument('REPOSITORY', { nargs: '?' }), ], }), new CommandSpec({ name: 'rename', description: 'Rename a repository', arguments: [REPO, new Argument('NEW-NAME')], }), new CommandSpec({ name: 'edit', description: 'Edit repository settings', arguments: [ ...REPO_EDIT_FIELDS.map(repoEditOption), new Argument('--add-topic', { action: 'append', help: 'Add repository topic' }), new Argument('--remove-topic', { action: 'append', help: 'Remove repository topic' }), flag({ long: '--accept-visibility-change-consequences', description: 'Accept the consequences of changing the repository visibility', }), new Argument('REPOSITORY', { nargs: '?' }), ], }), new CommandSpec({ name: 'delete', description: 'Delete a repository', arguments: [ flag({ long: '--yes', description: 'Confirm deletion without prompting' }), flag({ long: '--confirm', description: 'Deprecated: use --yes instead' }), new Argument('REPOSITORY', { nargs: '?' }), ], }), ], }); } function release() { return new CommandSpec({ name: 'release', description: 'Manage releases', subcommands: [ new CommandSpec({ name: 'list', aliases: ['ls'], description: 'List releases', arguments: [REPO, JSON_FIELDS, JQ, LIMIT_30], }), new CommandSpec({ name: 'view', description: 'View a release', arguments: [REPO, JSON_FIELDS, JQ, new Argument('TAG')], }), new CommandSpec({ name: 'create', description: 'Create a release', arguments: [ REPO, new Argument(['-n', '--notes']), new Argument(['-F', '--notes-file'], { type: 'path' }), TITLE, flag({ short: '-d', long: '--draft' }), flag({ short: '-p', long: '--prerelease' }), flag({ long: '--generate-notes' }), new Argument('--target'), new Argument('TAG'), ], }), ], }); } function run() { return new CommandSpec({ name: 'run', description: 'View workflow runs', subcommands: [ new CommandSpec({ name: 'list', aliases: ['ls'], description: 'List workflow runs', arguments: [ REPO, JSON_FIELDS, JQ, new Argument(['-L', '--limit'], { type: 'int', default: '20' }), new Argument(['-b', '--branch']), new Argument(['-c', '--commit']), new Argument('--created'), new Argument(['-e', '--event']), new Argument(['-s', '--status']), new Argument(['-u', '--user']), new Argument(['-w', '--workflow']), ], }), new CommandSpec({ name: 'view', description: 'View a workflow run', arguments: [ REPO, JSON_FIELDS, JQ, flag({ long: '--exit-status' }), flag({ long: '--log', description: 'View full log for either a run or specific job', }), flag({ long: '--log-failed', description: 'View the log for any failed steps in a run or specific job', }), new Argument('RUN-ID'), ], }), new CommandSpec({ name: 'rerun', description: 'Rerun a workflow run', arguments: [ REPO, flag({ short: '-d', long: '--debug' }), flag({ long: '--failed' }), new Argument(['-j', '--job']), new Argument('RUN-ID'), ], }), ], }); } function workflow() { return new CommandSpec({ name: 'workflow', description: 'Manage workflows', subcommands: [ new CommandSpec({ name: 'list', aliases: ['ls'], description: 'List workflows', arguments: [ REPO, JSON_FIELDS, JQ, new Argument(['-L', '--limit'], { type: 'int', default: '50' }), flag({ short: '-a', long: '--all' }), ], }), new CommandSpec({ name: 'view', description: 'View a workflow', arguments: [ REPO, flag({ short: '-y', long: '--yaml', description: 'View the workflow yaml file' }), new Argument(['-r', '--ref'], { help: "The branch or tag name which contains the version of the workflow file you'd like to view", }), new Argument('WORKFLOW'), ], }), new CommandSpec({ name: 'run', description: 'Run a workflow', arguments: [ REPO, new Argument(['-r', '--ref']), new Argument(['-f', '--raw-field'], { action: 'append' }), new Argument(['-F', '--field'], { action: 'append' }), flag({ long: '--json' }), new Argument('WORKFLOW'), ], }), ], }); } const ENC = new TextEncoder(); /** * `gh help [<command>...]`, as cobra answers it: the help of the deepest * command the words name (words past it are ignored), a help topic when the * first word names one, and otherwise gh's unknown-topic answer, which goes to * stderr with the list of commands and still exits 0. */ function helpCmd(_accessor, _paths, texts, _ctx) { let node = GH.spec; const path = []; for (const word of texts) { const child = findChild(node, word); if (child === null) break; node = child; path.push(child.name); } const first = texts[0]; if (first !== undefined && path.length === 0) { const topic = HELP_TOPICS[first]; if (topic !== undefined) return [ENC.encode(topic), new IOResult()]; const names = GH.spec.subcommands .filter((child) => child.name !== 'help') .map((child) => ` ${child.name}\n`) .sort(compareCodePoints); const asked = texts.map((word) => `\`${word}\``).join(' '); const usage = `Usage: gh <command> <subcommand> [flags]\n\nAvailable commands:\n${names.join('')}`; return [null, new IOResult({ stderr: ENC.encode(`Unknown help topic [${asked}]\n${usage}`) })]; } return [ENC.encode(nodeHelp(['gh', ...path].join(' '), node, GH.spec.usageStyle)), new IOResult()]; } export const GH = new CLI({ spec: new CommandSpec({ name: 'gh', description: 'GitHub CLI', usageStyle: UsageStyle.COBRA, subcommands: [ new CommandSpec({ name: 'auth', description: 'Manage authentication', subcommands: [ new CommandSpec({ name: 'status', description: 'Check the configured token' }), new CommandSpec({ name: 'token', description: 'Token display is unavailable in Mirage', arguments: [ new Argument('--hostname', { help: 'The hostname of the GitHub instance authenticated with', }), new Argument(['-u', '--user'], { help: 'The account selector; tokens are never printed', }), ], }), ], }), new CommandSpec({ name: 'help', description: 'Help about any command', arguments: [new Argument('texts', { metavar: '', nargs: '*' })], }), new CommandSpec({ name: 'version', aliases: ['--version'], description: 'Show the Mirage GitHub CLI implementation version', }), new CommandSpec({ name: 'api', description: 'Make an authenticated GitHub API request', arguments: [ new Argument(['-X', '--method']), new Argument(['-f', '--raw-field'], { action: 'append' }), new Argument(['-F', '--field'], { action: 'append' }), new Argument(['-H', '--header'], { action: 'append' }), flag({ short: '-i', long: '--include', description: 'Include HTTP response status line and headers in the output', }), new Argument('--input', { type: 'path' }), JQ, flag({ long: '--paginate' }), flag({ long: '--slurp' }), flag({ long: '--silent' }), new Argument('ENDPOINT'), ], }), issue(), pr(), repo(), release(), run(), workflow(), searchSpec(), ], }), handlers: { ...searchHandlers(), 'auth status': new CLIHandler({ fn: authStatus }), 'auth token': new CLIHandler({ fn: authToken }), help: new CLIHandler({ fn: helpCmd }), version: new CLIHandler({ fn: version }), api: new CLIHandler({ fn: api, write: true }), 'issue list': new CLIHandler({ fn: issueList }), 'issue view': new CLIHandler({ fn: issueView }), 'issue create': new CLIHandler({ fn: issueCreate, write: true }), 'issue edit': new CLIHandler({ fn: issueEdit, write: true }), 'issue close': new CLIHandler({ fn: issueClose, write: true }), 'issue reopen': new CLIHandler({ fn: issueReopen, write: true }), 'issue comment': new CLIHandler({ fn: issueComment, write: true }), 'pr list': new CLIHandler({ fn: prList }), 'pr view': new CLIHandler({ fn: prView }), 'pr create': new CLIHandler({ fn: prCreate, write: true }), 'pr edit': new CLIHandler({ fn: prEdit, write: true }), 'pr merge': new CLIHandler({ fn: prMerge, write: true }), 'pr close': new CLIHandler({ fn: prClose, write: true }), 'pr reopen': new CLIHandler({ fn: prReopen, write: true }), 'pr comment': new CLIHandler({ fn: prComment, write: true }), 'pr diff': new CLIHandler({ fn: prDiff }), 'pr checks': new CLIHandler({ fn: prChecks }), 'repo list': new CLIHandler({ fn: repoList }), 'repo clone': new CLIHandler({ fn: repoClone }), 'repo view': new CLIHandler({ fn: repoView }), 'repo create': new CLIHandler({ fn: repoCreate, write: true }), 'repo fork': new CLIHandler({ fn: fork, write: true }), 'repo rename': new CLIHandler({ fn: rename, write: true }), 'repo edit': new CLIHandler({ fn: repoEdit, write: true }), 'repo delete': new CLIHandler({ fn: repoDelete, write: true }), 'release list': new CLIHandler({ fn: releaseList }), 'release view': new CLIHandler({ fn: releaseView }), 'release create': new CLIHandler({ fn: releaseCreate, write: true }), 'run list': new CLIHandler({ fn: runListCmd }), 'run view': new CLIHandler({ fn: runViewCmd }), 'run rerun': new CLIHandler({ fn: runRerunCmd, write: true }), 'workflow list': new CLIHandler({ fn: workflowListCmd }), 'workflow view': new CLIHandler({ fn: workflowViewCmd }), 'workflow run': new CLIHandler({ fn: workflowRunCmd, write: true }), }, configModel: GhConfig, accessor: (config) => new GitHubAccount(config), }); //# sourceMappingURL=index.js.map