Skip to content

Commit f5279f7

Browse files
feat: Add github-cli resource (#33)
* feat: Add github-cli resource (auto-generated from issue #32) * fix: settings not being quoted when called * feat: improve deploy for beta * feat: added interactive login for github * chore: bump version * chore: move all homebrew calls to the utils.installviapackagemgr * feat: updated homebrew resource to support the new tap trust system * fix: asdf on (we switched to nodeJS), let's use deno instead. Fixed github cli already being installed * feat: collpase all jetbrains tests to a single test * fix: for homebrew still prompting * fix: auto-fix test failures from Test all (MacOS) (#66) Co-authored-by: kevinwang5658 <20214115+kevinwang5658@users.noreply.github.com> * fix: add guard around uninstall github * fix: auto-fix test failures from Test all (Linux) (#67) Co-authored-by: kevinwang5658 <20214115+kevinwang5658@users.noreply.github.com> * feat: change cron for tests to run every day * chore; bump plugin-lib version --------- Co-authored-by: kevinwang5658 <20214115+kevinwang5658@users.noreply.github.com> Co-authored-by: kevinwang <kevinwang5658@gmail.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent f9ce4cb commit f5279f7

40 files changed

Lines changed: 1223 additions & 100 deletions

.github/workflows/run-all-tests-linux.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ on:
77
# push:
88
workflow_dispatch:
99
schedule:
10-
- cron: '0 0 * * 1,3,6' # Mon/Wed/Sat at midnight UTC
10+
- cron: '0 0 * * *' # Every day at midnight UTC
1111

1212
jobs:
1313
build-and-test:

.github/workflows/run-all-tests-macos.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ on:
77
# push:
88
workflow_dispatch:
99
schedule:
10-
- cron: '0 0 * * 1,3,6' # Mon/Wed/Sat at midnight UTC
10+
- cron: '0 0 * * *' # Every day at midnight UTC
1111

1212

1313
jobs:

CLAUDE.md

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,19 @@ const result = await $.spawnSafe('my-tool --version')
258258
await $.spawn('my-tool configure', { interactive: true })
259259
```
260260

261+
**`interactive: true` vs `stdin: true`** — these are distinct options:
262+
263+
- `interactive: true` — passes `-i` to the shell so it sources the user's RC file (`.zshrc`, `.bashrc`). Use this when the command needs PATH entries or shell aliases from the RC. It does **not** allow the user to type input.
264+
- `stdin: true` — connects the user's terminal stdin directly to the spawned process. Use this when the command requires real user input (e.g. browser-based OAuth prompts, interactive wizards, password entry). Without this flag, interactive prompts will hang.
265+
266+
```typescript
267+
// Command needs PATH from shell RC but no user input
268+
await $.spawn('my-tool configure', { interactive: true })
269+
270+
// Command requires the user to interact with it directly (e.g. browser login flow)
271+
await $.spawn('gh auth login --web', { interactive: true, stdin: true })
272+
```
273+
261274
**Never use `sudo` inside `$.spawn` or `$.spawnSafe`.** Use `{ requiresRoot: true }` in the options instead. The framework handles privilege escalation through the parent process.
262275

263276
```typescript
@@ -287,16 +300,34 @@ Utils.isWindows()
287300

288301
**Package Installation:**
289302

290-
Always use `Utils.installViaPkgMgr(pkg)` from `@codifycli/plugin-core` to install system packages. This is platform-agnostic and automatically dispatches to the correct package manager (Homebrew on macOS, apt on Debian/Ubuntu, etc.). Never hardcode package manager calls like `brew install`, `apt-get install -y`, or `sudo apt install` in resource code.
303+
Always use `Utils.installViaPkgMgr(pkg)` from `@codifycli/plugin-core` to install system packages. This is platform-agnostic and automatically dispatches to the correct package manager (Homebrew on macOS, apt on Debian/Ubuntu, etc.). Never hardcode package manager calls like `brew install`, `apt-get install -y`, or `sudo apt install` in resource code — not even inside macOS-only branches.
304+
305+
The function accepts an optional `PkgMgrOptionsMap` (second arg) for per-PM flags, and an optional `forcePackageManager` (third arg) to skip OS detection:
291306

292307
```typescript
293-
// Correct — works on macOS and Linux
308+
import { Utils, PackageManager } from '@codifycli/plugin-core';
309+
310+
// Auto-detect OS — works on macOS (brew) and Linux (apt/dnf/etc.)
294311
await Utils.installViaPkgMgr('curl');
295312
await Utils.uninstallViaPkgMgr('curl');
296313

297-
// Wrong — hardcoded to a specific platform/package manager
298-
await $.spawn('sudo apt-get install -y curl');
299-
await $.spawn('brew install curl');
314+
// Force brew for a macOS-only formula resource
315+
await Utils.installViaPkgMgr('syncthing', undefined, PackageManager.BREW);
316+
await Utils.uninstallViaPkgMgr('syncthing', undefined, PackageManager.BREW);
317+
318+
// Force brew + cask for a macOS GUI app
319+
await Utils.installViaPkgMgr('cursor', { [PackageManager.BREW]: { cask: true } }, PackageManager.BREW);
320+
await Utils.uninstallViaPkgMgr('cursor', { [PackageManager.BREW]: { cask: true } }, PackageManager.BREW);
321+
322+
// Auto-detect OS but pass custom flags per PM
323+
await Utils.installViaPkgMgr('github-cli', {
324+
[PackageManager.APT]: { flags: ['--allow-unauthenticated'] },
325+
[PackageManager.DNF]: { flags: ['--repo', 'gh-cli'] },
326+
});
327+
328+
// Wrong — direct brew calls are forbidden even in macOS-only code
329+
await $.spawn('brew install syncthing', { ... });
330+
await $.spawn('brew install --cask cursor', { ... });
300331
```
301332

302333
This applies to prerequisite checks too. When a resource needs a system dependency (e.g. `curl`, `git`, `make`), always install via `Utils.installViaPkgMgr` rather than spawning a package manager directly.
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
---
2+
title: github-cli
3+
description: Reference pages for the GitHub CLI (gh) resources
4+
---
5+
6+
The GitHub CLI resources install and configure the [GitHub CLI (`gh`)](https://cli.github.com/manual/) tool. Four resources are provided to manage distinct concerns: installation and global configuration, authentication, command aliases, and GitHub account SSH keys.
7+
8+
---
9+
10+
## github-cli
11+
12+
Installs `gh` and manages global configuration settings such as the default git protocol, editor, pager, and browser.
13+
14+
### Parameters
15+
16+
- **gitProtocol**: *(string: `https` | `ssh`)* Default protocol for git operations. Defaults to `https`.
17+
- **editor**: *(string)* Default text editor for gh commands (e.g. `vim`, `nano`, `code --wait`).
18+
- **prompt**: *(string: `enabled` | `disabled`)* Whether interactive prompts are shown. Defaults to `enabled`.
19+
- **pager**: *(string)* Pager program used to display long output (e.g. `less`).
20+
- **browser**: *(string)* Default browser to open URLs (e.g. `firefox`).
21+
22+
### Example usage
23+
24+
```json title="codify.jsonc"
25+
[
26+
{
27+
"type": "github-cli",
28+
"gitProtocol": "ssh",
29+
"editor": "vim"
30+
}
31+
]
32+
```
33+
34+
---
35+
36+
## github-cli-auth
37+
38+
Authenticates the GitHub CLI using a Personal Access Token (PAT). Supports multiple accounts and GitHub Enterprise Server hostnames.
39+
40+
> **Security note:** The `token` field is marked sensitive and is never logged or displayed by Codify. Store PATs in a secrets manager and reference them via environment variables where possible.
41+
42+
### Parameters
43+
44+
- **token** *(required)*: *(string)* GitHub personal access token (classic or fine-grained).
45+
- **hostname**: *(string)* GitHub hostname. Defaults to `github.com`. Set to your GHE hostname (e.g. `github.mycompany.com`) for enterprise instances.
46+
47+
### Example usage
48+
49+
```json title="codify.jsonc"
50+
[
51+
{
52+
"type": "github-cli",
53+
"gitProtocol": "https"
54+
},
55+
{
56+
"type": "github-cli-auth",
57+
"token": "<Replace me here!>"
58+
}
59+
]
60+
```
61+
62+
---
63+
64+
## github-cli-alias
65+
66+
Creates a short-hand alias for a `gh` command. Each alias is an independent resource, identified by its name.
67+
68+
### Parameters
69+
70+
- **alias** *(required)*: *(string)* The alias name used to invoke the command (e.g. `prc`).
71+
- **expansion** *(required)*: *(string)* The gh command or shell command this alias expands to (e.g. `pr create`).
72+
- **shell**: *(boolean)* When `true`, the expansion is executed as a shell command via `sh`, enabling pipes, redirects, and other shell features. Defaults to `false`.
73+
74+
### Example usage
75+
76+
```json title="codify.jsonc"
77+
[
78+
{
79+
"type": "github-cli-alias",
80+
"alias": "prc",
81+
"expansion": "pr create"
82+
},
83+
{
84+
"type": "github-cli-alias",
85+
"alias": "prs",
86+
"expansion": "pr status"
87+
}
88+
]
89+
```
90+
91+
---
92+
93+
## github-cli-ssh-key
94+
95+
Uploads a local SSH public key to your GitHub account. This is distinct from the `ssh-key` resource, which manages local key files — this resource registers an existing key with GitHub via the `gh ssh-key add` command.
96+
97+
Requires authentication (`github-cli-auth`) to be configured.
98+
99+
### Parameters
100+
101+
- **title** *(required)*: *(string)* Display name for the key on GitHub (e.g. `My Laptop`).
102+
- **keyFile** *(required)*: *(string)* Path to the local SSH public key file (e.g. `~/.ssh/id_ed25519.pub`).
103+
- **keyType**: *(string: `authentication` | `signing`)* Key usage type. Use `authentication` (default) for git over SSH, or `signing` for commit signing.
104+
105+
### Example usage
106+
107+
```json title="codify.jsonc"
108+
[
109+
{
110+
"type": "github-cli"
111+
},
112+
{
113+
"type": "github-cli-auth",
114+
"token": "<Replace me here!>"
115+
},
116+
{
117+
"type": "github-cli-ssh-key",
118+
"title": "My Laptop",
119+
"keyFile": "~/.ssh/id_ed25519.pub",
120+
"keyType": "authentication"
121+
}
122+
]
123+
```

package-lock.json

Lines changed: 9 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "default",
3-
"version": "1.10.0",
3+
"version": "1.11.0",
44
"description": "Default plugin for Codify - provides 50+ declarative resources for managing development tools and system configuration across macOS and Linux",
55
"main": "dist/index.js",
66
"scripts": {
@@ -41,7 +41,7 @@
4141
"license": "ISC",
4242
"type": "module",
4343
"dependencies": {
44-
"@codifycli/plugin-core": "1.2.3",
44+
"@codifycli/plugin-core": "^1.2.5",
4545
"@codifycli/schemas": "1.2.0",
4646
"ajv": "^8.18.0",
4747
"ajv-formats": "^2.1.1",

scripts/cleanup-github-actions.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ if (Utils.isLinux()) {
99
// Uninstall resources that have Codify resource definitions
1010
await PluginTester.uninstall(pluginPath, [
1111
{ type: 'docker' },
12-
{ type: 'aws-cli'}
12+
{ type: 'aws-cli'},
13+
{ type: 'github-cli' },
1314
]);
1415

1516
await testSpawn('apt-get autoremove -y ruby rpm python awscli needrestart', { requiresRoot: true }); // remove needrestart to keep logs clean.
@@ -26,6 +27,7 @@ if (Utils.isLinux()) {
2627
} else {
2728
await PluginTester.uninstall(pluginPath, [
2829
{ type: 'aws-cli' },
30+
{ type: 'github-cli' }
2931
]);
3032

3133
await testSpawn('brew uninstall ant gradle kotlin maven selenium-server google-chrome pipx $(brew list | grep -E \'^python(@|$)\') $(brew list | grep -E \'^ruby(@|$)\') aws-sam-cli azure-cli rustup git-lfs $(brew list | grep -E \'^openjdk(@|$)\')', { interactive: true });

scripts/deploy.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,26 @@ const versionRow = await client.from('registry_plugin_versions').upsert({
8585

8686
await uploadResources(isBeta);
8787

88+
if (isBeta) {
89+
// Generate embeddings for prerelease resources so the AI agent can find them via semantic search
90+
console.log('Triggering vector reindex for prerelease resources...')
91+
const reindexKey = process.env.REINDEX_API_KEY
92+
if (!reindexKey) {
93+
console.warn('REINDEX_API_KEY not set — skipping prerelease reindex')
94+
} else {
95+
const res = await fetch('https://api.codifycli.com/v1/embeddings/reindex', {
96+
method: 'POST',
97+
headers: { Authorization: `Bearer ${reindexKey}` },
98+
})
99+
if (!res.ok) {
100+
console.error(`Prerelease reindex failed: ${res.status} ${await res.text()}`)
101+
} else {
102+
const body = await res.json() as { resources_processed: number; templates_processed: number }
103+
console.log(`Prerelease reindex complete — resources: ${body.resources_processed}`)
104+
}
105+
}
106+
}
107+
88108
if (!isBeta) {
89109
// Build and deploy completions as well.
90110
console.log('Deploying completions...')

src/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ import { EnvFileResource } from './resources/file/env-file/env-file-resource.js'
1313
import { EnvFilesResource } from './resources/file/env-file/env-files-resource.js';
1414
import { FileResource } from './resources/file/file.js';
1515
import { RemoteFileResource } from './resources/file/remote-file.js';
16+
import { GithubCliResource } from './resources/github-cli/github-cli.js';
17+
import { GithubCliAuthResource } from './resources/github-cli/github-cli-auth.js';
18+
import { GithubCliAliasResource } from './resources/github-cli/github-cli-alias.js';
19+
import { GithubCliSshKeyResource } from './resources/github-cli/github-cli-ssh-key.js';
1620
import { GitResource } from './resources/git/git/git-resource.js';
1721
import { GitLfsResource } from './resources/git/lfs/git-lfs.js';
1822
import { GitRepositoriesResource } from './resources/git/repositories/git-repositories.js';
@@ -147,6 +151,10 @@ runPlugin(Plugin.create(
147151
new RbenvResource(),
148152
new OpenClawResource(),
149153
new RustResource(),
154+
new GithubCliResource(),
155+
new GithubCliAuthResource(),
156+
new GithubCliAliasResource(),
157+
new GithubCliSshKeyResource(),
150158
],
151159
{ minSupportedCliVersion: MIN_SUPPORTED_CLI_VERSION }
152160
))

src/resources/asdf/asdf.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { CreatePlan, ExampleConfig, FileUtils, Resource, ResourceSettings, SpawnStatus, Utils as CoreUtils, getPty, z, Utils } from '@codifycli/plugin-core';
1+
import { CreatePlan, ExampleConfig, FileUtils, Resource, ResourceSettings, SpawnStatus, Utils as CoreUtils, getPty, z, Utils, PackageManager } from '@codifycli/plugin-core';
22
import { OS } from '@codifycli/schemas';
33
import fs from 'node:fs/promises';
44
import os from 'node:os';
@@ -83,7 +83,7 @@ export class AsdfResource extends Resource<AsdfConfig> {
8383
throw new Error('Homebrew is not installed. Please install Homebrew before installing asdf.');
8484
}
8585

86-
await $.spawn('brew install asdf', { interactive: true, env: { HOMEBREW_NO_AUTO_UPDATE: 1 } });
86+
await Utils.installViaPkgMgr('asdf', undefined, PackageManager.BREW);
8787
}
8888

8989
if (Utils.isLinux()) {
@@ -124,7 +124,7 @@ export class AsdfResource extends Resource<AsdfConfig> {
124124
return;
125125
}
126126

127-
await $.spawn('brew uninstall asdf', { interactive: true, env: { HOMEBREW_NO_AUTO_UPDATE: 1 } });
127+
await Utils.uninstallViaPkgMgr('asdf', undefined, PackageManager.BREW);
128128
} else {
129129
await fs.rm(asdfDir, { recursive: true, force: true });
130130
}

0 commit comments

Comments
 (0)