@phnx-labs/agents-cli 1.19.1 → 1.20.0
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/CHANGELOG.md +67 -0
- package/README.md +70 -10
- package/dist/commands/browser.js +88 -16
- package/dist/commands/cli.d.ts +14 -0
- package/dist/commands/cli.js +244 -0
- package/dist/commands/commands.js +3 -3
- package/dist/commands/computer.js +18 -1
- package/dist/commands/doctor.d.ts +1 -1
- package/dist/commands/doctor.js +2 -2
- package/dist/commands/exec.js +3 -3
- package/dist/commands/factory.d.ts +3 -14
- package/dist/commands/factory.js +3 -3
- package/dist/commands/hooks.js +3 -3
- package/dist/commands/mcp.js +29 -0
- package/dist/commands/plugins.js +11 -4
- package/dist/commands/profiles.js +1 -1
- package/dist/commands/prune.js +39 -160
- package/dist/commands/pull.js +56 -3
- package/dist/commands/routines.js +106 -13
- package/dist/commands/secrets.js +6 -8
- package/dist/commands/sessions.d.ts +36 -7
- package/dist/commands/sessions.js +130 -53
- package/dist/commands/setup.d.ts +1 -0
- package/dist/commands/setup.js +37 -28
- package/dist/commands/skills.js +3 -3
- package/dist/commands/teams.js +13 -0
- package/dist/commands/versions.d.ts +4 -3
- package/dist/commands/versions.js +147 -124
- package/dist/commands/view.js +12 -12
- package/dist/index.js +34 -6
- package/dist/lib/acp/harnesses.js +8 -0
- package/dist/lib/agents.js +162 -9
- package/dist/lib/browser/cdp.d.ts +8 -1
- package/dist/lib/browser/cdp.js +40 -3
- package/dist/lib/browser/chrome.d.ts +13 -0
- package/dist/lib/browser/chrome.js +42 -3
- package/dist/lib/browser/domain-skills.d.ts +51 -0
- package/dist/lib/browser/domain-skills.js +157 -0
- package/dist/lib/browser/drivers/local.js +45 -4
- package/dist/lib/browser/drivers/ssh.js +1 -1
- package/dist/lib/browser/ipc.d.ts +8 -1
- package/dist/lib/browser/ipc.js +37 -28
- package/dist/lib/browser/profiles.d.ts +13 -0
- package/dist/lib/browser/profiles.js +41 -1
- package/dist/lib/browser/service.d.ts +3 -0
- package/dist/lib/browser/service.js +21 -5
- package/dist/lib/browser/types.d.ts +7 -0
- package/dist/lib/cli-resources.d.ts +109 -0
- package/dist/lib/cli-resources.js +255 -0
- package/dist/lib/cloud/rush.js +5 -5
- package/dist/lib/command-skills.js +0 -2
- package/dist/lib/computer-rpc.d.ts +3 -0
- package/dist/lib/computer-rpc.js +53 -0
- package/dist/lib/daemon.js +20 -0
- package/dist/lib/exec.d.ts +3 -2
- package/dist/lib/exec.js +62 -6
- package/dist/lib/hooks.js +182 -0
- package/dist/lib/mcp.js +6 -0
- package/dist/lib/migrate.js +1 -1
- package/dist/lib/overdue.d.ts +26 -0
- package/dist/lib/overdue.js +101 -0
- package/dist/lib/permissions.js +5 -1
- package/dist/lib/plugin-marketplace.js +1 -1
- package/dist/lib/profiles-presets.js +37 -0
- package/dist/lib/registry.d.ts +18 -0
- package/dist/lib/registry.js +44 -0
- package/dist/lib/resources/mcp.js +43 -1
- package/dist/lib/resources/types.d.ts +1 -1
- package/dist/lib/resources.d.ts +1 -1
- package/dist/lib/rotate.js +10 -4
- package/dist/lib/routines-format.d.ts +35 -0
- package/dist/lib/routines-format.js +173 -0
- package/dist/lib/routines.d.ts +7 -1
- package/dist/lib/routines.js +32 -12
- package/dist/lib/runner.js +19 -5
- package/dist/lib/scheduler.js +8 -1
- package/dist/lib/secrets/{AgentsKeychain.app → Agents CLI.app}/Contents/CodeResources +0 -0
- package/dist/lib/secrets/{AgentsKeychain.app/Contents/Info.plist → Agents CLI.app/Contents/Info.plist } +4 -2
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/bundles.d.ts +33 -2
- package/dist/lib/secrets/bundles.js +249 -26
- package/dist/lib/secrets/index.d.ts +10 -1
- package/dist/lib/secrets/index.js +143 -48
- package/dist/lib/session/active.d.ts +8 -0
- package/dist/lib/session/active.js +3 -2
- package/dist/lib/session/db.d.ts +10 -4
- package/dist/lib/session/db.js +16 -16
- package/dist/lib/session/parse.d.ts +1 -0
- package/dist/lib/session/parse.js +44 -0
- package/dist/lib/session/types.d.ts +1 -1
- package/dist/lib/session/types.js +1 -1
- package/dist/lib/shims.d.ts +6 -2
- package/dist/lib/shims.js +88 -10
- package/dist/lib/state.d.ts +0 -1
- package/dist/lib/state.js +2 -15
- package/dist/lib/teams/agents.js +1 -1
- package/dist/lib/teams/parsers.d.ts +1 -1
- package/dist/lib/teams/parsers.js +153 -3
- package/dist/lib/teams/summarizer.js +18 -2
- package/dist/lib/teams/worktree.js +14 -3
- package/dist/lib/types.d.ts +7 -4
- package/dist/lib/types.js +6 -3
- package/dist/lib/versions.d.ts +10 -2
- package/dist/lib/versions.js +227 -35
- package/package.json +9 -9
- package/dist/lib/secrets/AgentsKeychain.app/Contents/MacOS/AgentsKeychain +0 -0
- package/npm-shrinkwrap.json +0 -3162
- /package/dist/lib/secrets/{AgentsKeychain.app → Agents CLI.app}/Contents/_CodeSignature/CodeResources +0 -0
- /package/dist/lib/secrets/{AgentsKeychain.app → Agents CLI.app}/Contents/embedded.provisionprofile +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,72 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.20.0
|
|
4
|
+
|
|
5
|
+
**Routines (overdue detection + catchup)**
|
|
6
|
+
|
|
7
|
+
- Detect routines whose most recent scheduled fire was missed (laptop off, daemon crashed, reboot). The daemon logs them on startup and pops a native desktop notification (`osascript` on macOS, `notify-send` on Linux).
|
|
8
|
+
- `agents routines list` annotates overdue rows with `(overdue)` and prints a footer pointing at the catchup command.
|
|
9
|
+
- New `agents routines catchup` command: lists overdue routines and fires them in the background under the scheduler. `--dry-run` lists without triggering.
|
|
10
|
+
- `JobScheduler.schedule` now sets croner's `catch: true` and forwards `timezone` defensively, so a synchronous throw in one job's callback can't kill the whole cron loop.
|
|
11
|
+
|
|
12
|
+
**Landing page (agents-cli.sh)**
|
|
13
|
+
|
|
14
|
+
- Expanded the homepage with seven new sections: rotate accounts (`--rotate`), parallel teams (`agents teams`), browser automation, cross-agent session search, routines/cron, keychain secrets, and machine-to-machine sync (`agents drive`).
|
|
15
|
+
- Rewrote meta description + lede to spell out the actual feature set (pin versions, swap models, rotate accounts, drive a browser, spawn parallel teams, schedule on cron) instead of just "same interface, on your machine."
|
|
16
|
+
|
|
17
|
+
**Codex (commands-as-skills sync fix)**
|
|
18
|
+
|
|
19
|
+
- Fix recurring "N commands new" prompt on `agents view codex` for Codex >= 0.117.0. `getActuallySyncedResources` now detects converted command-skills via the `agents_command` marker in `~/.codex/skills/<name>/SKILL.md` instead of only scanning the empty legacy `prompts/` directory.
|
|
20
|
+
- Summary and selection prompts are version-aware: the static `COMMANDS_CAPABLE_AGENTS` gate is replaced by `supports(agent, 'commands', version)` so the "X commands" line only appears for versions that can actually take them.
|
|
21
|
+
- Generalize `shouldInstallCommandAsSkill` beyond Codex — any agent where commands are gated off and skills are on (e.g. Grok) now gets the same automatic slash-command → skill conversion at install/sync time.
|
|
22
|
+
|
|
23
|
+
**Grok Build (first-class support)**
|
|
24
|
+
|
|
25
|
+
- Add `grok` as a first-class supported agent (AgentId + full registry entry using official `~/.grok/README.md` paths).
|
|
26
|
+
- Implement proper binary resolution from `~/.grok/downloads/`.
|
|
27
|
+
- Add `GROK_HOME` isolation to generated shims for true versioned config (skills, hooks, plugins, agents/, MCP, memory, etc.).
|
|
28
|
+
- Extend `installVersion` to support Grok via its official installer script (`curl ... -s <version>`).
|
|
29
|
+
- Update shims, exec templates, MCP path helpers, session helpers, unmanaged detection, and docs.
|
|
30
|
+
- `agents add grok@<ver>`, `agents use grok@<ver>`, resource sync, and shims now work end-to-end for Grok Build.
|
|
31
|
+
|
|
32
|
+
**Browser**
|
|
33
|
+
|
|
34
|
+
- `agents browser start --record` convenience flag for one-shot recording sessions.
|
|
35
|
+
- Auto-discover per-site `SKILL.md` on `browser start` so skills appear under the active task without manual wiring.
|
|
36
|
+
- Auto-pick a Chromium-family browser when `--profile` is omitted; the limitation is surfaced in `--help` and the auto-pick error.
|
|
37
|
+
- No more stacktraces when the daemon is down or CDP is unreachable — error paths print a single human-readable line.
|
|
38
|
+
- Drop the Playwright `bundled-chromium` devdependency.
|
|
39
|
+
|
|
40
|
+
**Secrets / Keychain**
|
|
41
|
+
|
|
42
|
+
- `agents secrets list` and `agents run --secrets <bundle>` collapse to one Touch ID prompt per bundle instead of one per key. Previously every secret in a bundle would re-prompt for keychain unlock.
|
|
43
|
+
|
|
44
|
+
**Sessions**
|
|
45
|
+
|
|
46
|
+
- Extract `groupActiveSessions` into a tested helper for `--active` window grouping.
|
|
47
|
+
- Propagate `windowid` from live-terminals into the active session record.
|
|
48
|
+
|
|
49
|
+
**Copilot**
|
|
50
|
+
|
|
51
|
+
- Emit `COPILOT_HOME` in the shim and exec env builder for versioned isolation.
|
|
52
|
+
- Wire the Copilot session dir and `.jsonl` extension into the sessions reader.
|
|
53
|
+
|
|
54
|
+
**OpenClaw**
|
|
55
|
+
|
|
56
|
+
- Carry OpenClaw user data forward on version switch.
|
|
57
|
+
|
|
58
|
+
**Teams**
|
|
59
|
+
|
|
60
|
+
- Warn loudly when `--after` teammates reference a name whose watch process never launched, instead of silently sitting in pending state.
|
|
61
|
+
|
|
62
|
+
**Plugins**
|
|
63
|
+
|
|
64
|
+
- Use `'directory'` source discriminator (not `'local'`) for marketplace registration so plugins reload correctly.
|
|
65
|
+
|
|
66
|
+
**Dependencies**
|
|
67
|
+
|
|
68
|
+
- Bump `@inquirer/prompts` 7.10.1 → 8.5.1, `diff` 8.0.4 → 9.0.0, `tsx` 4.22.2 → 4.22.3, `actions/setup-node` 4.4.0 → 6.4.0.
|
|
69
|
+
|
|
3
70
|
## 1.18.6
|
|
4
71
|
|
|
5
72
|
**Claude**
|
package/README.md
CHANGED
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
<a href="https://github.com/openclaw/openclaw" title="OpenClaw"><img src="assets/harnesses/openclaw.svg" height="36" alt="OpenClaw" /></a>
|
|
28
28
|
|
|
29
29
|
<a href="https://github.com/NousResearch/hermes-agent" title="Hermes Agent"><img src="assets/harnesses/hermes.png" height="32" alt="Hermes Agent" /></a>
|
|
30
|
+
|
|
31
|
+
<a href="https://x.ai" title="Grok Build (xAI)"><strong>Grok</strong></a>
|
|
30
32
|
</p>
|
|
31
33
|
|
|
32
34
|
https://agents-cli.sh/demo.mp4
|
|
@@ -79,7 +81,7 @@ agents:
|
|
|
79
81
|
codex: "0.116.0"
|
|
80
82
|
```
|
|
81
83
|
|
|
82
|
-
Think `requirements.txt` for CLI coding agents, on steroids. A shim reads `agents.yaml` from the project root and routes `claude` / `codex` / `gemini` to the right version automatically. Each version gets its own isolated home -- switching backs up config and re-syncs resources.
|
|
84
|
+
Think `requirements.txt` for CLI coding agents, on steroids. A shim reads `agents.yaml` from the project root and routes `claude` / `codex` / `gemini` / `grok` (and others) to the right version automatically. Each version gets its own isolated home -- switching backs up config and re-syncs resources.
|
|
83
85
|
|
|
84
86
|
```bash
|
|
85
87
|
agents add claude@2.0.65 # Install a specific version
|
|
@@ -154,9 +156,9 @@ Supports plan (read-only) and edit modes, effort levels, JSON output for scripti
|
|
|
154
156
|
agents run claude "review this diff" --acp --json
|
|
155
157
|
```
|
|
156
158
|
|
|
157
|
-
`--acp` routes through the [Agent Client Protocol](https://
|
|
159
|
+
`--acp` routes through the [Agent Client Protocol](https://github.com/zed-industries/agent-client-protocol) so you get a unified event stream -- `agent_message_chunk`, `tool_call`, `plan_update`, `stop_reason` -- instead of writing a parser per CLI. File writes and shell commands flow through agents-cli, which means `--mode plan` becomes a real sandbox: the write RPC is denied, not just unused.
|
|
158
160
|
|
|
159
|
-
ACP adapters are documented for claude, codex, gemini, cursor, opencode, and
|
|
161
|
+
ACP adapters are documented for claude, codex, gemini, cursor, opencode, openclaw, and grok. Other harnesses keep running on the direct-exec path.
|
|
160
162
|
|
|
161
163
|
---
|
|
162
164
|
|
|
@@ -281,7 +283,7 @@ A workflow is a directory:
|
|
|
281
283
|
---
|
|
282
284
|
name: Code Review
|
|
283
285
|
description: Evidence-grounded PR review with file:line citations.
|
|
284
|
-
model:
|
|
286
|
+
model: opus
|
|
285
287
|
tools:
|
|
286
288
|
- Read
|
|
287
289
|
- Grep
|
|
@@ -296,15 +298,70 @@ Resolution is project > user > system: a `<repo>/.agents/workflows/<name>/` over
|
|
|
296
298
|
|
|
297
299
|
---
|
|
298
300
|
|
|
301
|
+
## Plugins
|
|
302
|
+
|
|
303
|
+
Bundle skills, commands, hooks, MCP servers, settings, and permissions under a single manifest. One source dir at `~/.agents/plugins/<name>/`, mirrored into every installed Claude / OpenClaw version automatically.
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
# Install from a git URL or local path
|
|
307
|
+
agents plugins install hivemind@https://github.com/activeloopai/hivemind.git
|
|
308
|
+
agents plugins install ./my-plugin
|
|
309
|
+
|
|
310
|
+
# Apply to one agent (default version) or all supported
|
|
311
|
+
agents plugins sync rush-toolkit claude
|
|
312
|
+
agents plugins sync rush-toolkit
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
A plugin is a directory with a manifest:
|
|
316
|
+
|
|
317
|
+
```
|
|
318
|
+
~/.agents/plugins/my-plugin/
|
|
319
|
+
.claude-plugin/plugin.json # required: { name, version, description }
|
|
320
|
+
skills/<name>/SKILL.md # optional
|
|
321
|
+
commands/*.md # optional
|
|
322
|
+
hooks/hooks.json # optional — executable surface
|
|
323
|
+
.mcp.json # optional — executable surface
|
|
324
|
+
bin/, scripts/, settings.json # optional — executable surface
|
|
325
|
+
permissions/ # optional — executable surface
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
On sync, agents-cli copies the plugin into each version home's marketplace (`<home>/.claude/plugins/marketplaces/agents-cli/plugins/<name>/`), registers the synthetic marketplace, and flips `settings.json#enabledPlugins[<name>@agents-cli] = true` so Claude / OpenClaw load it.
|
|
329
|
+
|
|
330
|
+
### Executable-surface gate
|
|
331
|
+
|
|
332
|
+
Plugins that ship `hooks/`, `.mcp.json`, `bin/`, `scripts/`, `settings.json` (non-permissions), or `permissions/` can execute code on session events. agents-cli requires explicit consent before flipping `enabledPlugins`:
|
|
333
|
+
|
|
334
|
+
```bash
|
|
335
|
+
# Hooks-bearing plugins copy in but stay disabled by default
|
|
336
|
+
agents plugins install hivemind@https://github.com/activeloopai/hivemind.git \
|
|
337
|
+
--allow-exec-surfaces
|
|
338
|
+
|
|
339
|
+
# Same gate on re-sync (e.g., after upstream updates)
|
|
340
|
+
agents plugins sync hivemind claude --allow-exec-surfaces
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
Skills, commands, and subagents are declarative and never trip the gate. The gate is per-plugin, per-install: consenting to hivemind doesn't grant blanket exec-surface trust to anything else.
|
|
344
|
+
|
|
345
|
+
### Version portability
|
|
346
|
+
|
|
347
|
+
Plugins live in the user repo (`~/.agents/plugins/`), not inside any single version home. Switching Claude via `agents use claude@<v>` re-syncs the plugin into the new version automatically — no re-install. New Claude versions added later pick it up on their first sync. Project-level `<repo>/.agents/plugins/<name>/` overrides a same-named user plugin (resolution is project > user > system, same as every other resource).
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
299
351
|
## Browser
|
|
300
352
|
|
|
301
353
|
Give agents access to a real browser — no relay extension, no cloud service, no Playwright getting blocked.
|
|
302
354
|
|
|
303
355
|
```bash
|
|
304
|
-
#
|
|
356
|
+
# First run: omit --profile and we auto-pick the first installed Chromium-family
|
|
357
|
+
# browser. macOS prefers Chrome > Brave > Edge > Chromium > Comet; Linux prefers
|
|
358
|
+
# Chrome > Chromium > Brave > Edge; Windows prefers Edge (always preinstalled) >
|
|
359
|
+
# Chrome > Brave. The auto-picked profile is saved as "default" for later runs.
|
|
360
|
+
export AGENTS_BROWSER_TASK=$(agents browser start --url https://app.example.com)
|
|
361
|
+
|
|
362
|
+
# Or pin a named profile to a specific browser (chrome, comet, brave, chromium,
|
|
363
|
+
# edge, or custom) when you want isolation from "default".
|
|
305
364
|
agents browser profiles create work --browser chrome
|
|
306
|
-
|
|
307
|
-
# Start a task once, then bind it to this shell — every later command picks it up.
|
|
308
365
|
# `start` writes the resolved name (e.g. `swift-crab-falcon-a3f92b1c`) to stdout
|
|
309
366
|
# and human-friendly commentary to stderr, so $(...) capture stays clean.
|
|
310
367
|
export AGENTS_BROWSER_TASK=$(agents browser start --profile work --url https://app.example.com)
|
|
@@ -384,6 +441,9 @@ agents browser profiles create cloud \
|
|
|
384
441
|
|
|
385
442
|
## Secrets
|
|
386
443
|
|
|
444
|
+
> **Platform:** `agents secrets` requires macOS Keychain or Linux libsecret.
|
|
445
|
+
> On Windows (non-WSL), use environment variables or a `.env` file instead.
|
|
446
|
+
|
|
387
447
|
```bash
|
|
388
448
|
# API keys in Keychain, not in .env files.
|
|
389
449
|
agents secrets create prod-stripe
|
|
@@ -414,7 +474,7 @@ agents secrets list # npm-tokens is already there;
|
|
|
414
474
|
agents run claude "..." --secrets npm-tokens # injects NPM_TOKEN automatically
|
|
415
475
|
```
|
|
416
476
|
|
|
417
|
-
Under the hood, synced bundles route writes through a notarized helper app (`
|
|
477
|
+
Under the hood, synced bundles route writes through a notarized helper app (`Agents CLI.app`) that holds the entitlement macOS requires for `kSecAttrSynchronizable`. Bundles created with `--no-icloud-sync` stay device-local.
|
|
418
478
|
|
|
419
479
|
Bundle definitions sync via iCloud Keychain too — no `agents repo push` needed for secrets, no recreate step on each Mac. Nothing about secrets ever lives in plaintext on disk.
|
|
420
480
|
|
|
@@ -538,7 +598,7 @@ Every agent run, version install, browser launch, and secrets access is logged t
|
|
|
538
598
|
}
|
|
539
599
|
```
|
|
540
600
|
|
|
541
|
-
**What's logged:** Operation type, agent, version, timing, truncated prompts (first 200 chars), exit codes, errors. **What's NOT logged:** Full prompts, outputs, file contents, secret values
|
|
601
|
+
**What's logged:** Operation type, agent, version, timing, truncated prompts (first 200 chars), exit codes, errors, and secret bundle/key names with caller context. **What's NOT logged:** Full prompts, outputs, file contents, or secret values.
|
|
542
602
|
|
|
543
603
|
**Permissions:** Logs directory is `0700` (owner-only), files are `0600`. Only you can read them.
|
|
544
604
|
|
|
@@ -603,7 +663,7 @@ Codex command sync is version-aware: Codex `0.116.x` and older receive slash com
|
|
|
603
663
|
|
|
604
664
|
### Why use `agents` instead of `claude` / `codex` / `gemini` directly?
|
|
605
665
|
|
|
606
|
-
Claude Code, Codex CLI,
|
|
666
|
+
Claude Code, Codex CLI, Gemini CLI, Grok Build, and others each have their own config format, MCP setup, version management, and skill system. If you use more than one, you maintain N copies of everything. `agents` gives you one interface, one config source, and one place to pin versions -- plus features the individual CLIs don't ship: cross-agent pipelines, shared teams, unified session search, and project-pinned versions like `.nvmrc`.
|
|
607
667
|
|
|
608
668
|
### Is it free?
|
|
609
669
|
|
package/dist/commands/browser.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
|
-
import { listProfiles, getProfile, createProfile, deleteProfile, getProfileRuntimeDir, extractConfiguredPort, findFreeProfilePort, getEndpointPresets, } from '../lib/browser/profiles.js';
|
|
3
|
+
import { listProfiles, getProfile, createProfile, deleteProfile, ensureDefaultBrowserProfile, getProfileRuntimeDir, extractConfiguredPort, findFreeProfilePort, getEndpointPresets, } from '../lib/browser/profiles.js';
|
|
4
4
|
import { findBrowserPath, getPortOccupant } from '../lib/browser/chrome.js';
|
|
5
5
|
import { listProfileCacheDirs, removeProfileCache, listAllProfileSnapshots, } from '../lib/browser/runtime-state.js';
|
|
6
6
|
import { DEFAULT_VIEWPORT } from '../lib/browser/devices.js';
|
|
7
7
|
import { discoverBrowserWsUrl, verifyBrowserIdentity } from '../lib/browser/cdp.js';
|
|
8
8
|
import { parseTargetFilter } from '../lib/browser/service.js';
|
|
9
|
-
import { sendIPCRequest } from '../lib/browser/ipc.js';
|
|
9
|
+
import { BrowserDaemonNotRunningError, formatBrowserDaemonNotRunningError, sendIPCRequest, } from '../lib/browser/ipc.js';
|
|
10
10
|
import { browserTaskPicker } from './browser-picker.js';
|
|
11
11
|
import { isInteractiveTerminal } from './utils.js';
|
|
12
12
|
import { registerCommandGroups, setHelpSections } from '../lib/help.js';
|
|
@@ -59,8 +59,11 @@ export function registerBrowserCommand(program) {
|
|
|
59
59
|
# Create a Chrome profile pointed at a CDP endpoint
|
|
60
60
|
agents browser profiles create work --browser chrome --endpoint http://localhost:9222
|
|
61
61
|
|
|
62
|
-
# Start a session
|
|
63
|
-
agents browser start
|
|
62
|
+
# Start a session — auto-picks the first installed Chromium-family browser
|
|
63
|
+
agents browser start
|
|
64
|
+
|
|
65
|
+
# Or pin to a specific profile
|
|
66
|
+
agents browser start --profile work
|
|
64
67
|
|
|
65
68
|
# Drive the page
|
|
66
69
|
agents browser navigate https://example.com
|
|
@@ -72,6 +75,12 @@ export function registerBrowserCommand(program) {
|
|
|
72
75
|
notes: `
|
|
73
76
|
Most agent workflows should use the 'browser' skill instead of raw subcommands.
|
|
74
77
|
The skill wraps profile selection, snapshotting, and tunneling.
|
|
78
|
+
|
|
79
|
+
Browser support: Chromium-family only (Chrome, Comet, Chromium, Brave, Edge).
|
|
80
|
+
Safari and Firefox are not supported — they don't speak the Chrome DevTools
|
|
81
|
+
Protocol the way agents browser expects. On Windows, Edge is the default
|
|
82
|
+
because it's preinstalled. On macOS and Linux, Chrome is preferred when
|
|
83
|
+
installed; otherwise the first Chromium-family binary on disk wins.
|
|
75
84
|
`,
|
|
76
85
|
});
|
|
77
86
|
registerProfilesCommands(browser);
|
|
@@ -346,7 +355,7 @@ function registerProfilesCommands(browser) {
|
|
|
346
355
|
}
|
|
347
356
|
else {
|
|
348
357
|
try {
|
|
349
|
-
const { browser } = await discoverBrowserWsUrl(port);
|
|
358
|
+
const { browser } = await discoverBrowserWsUrl(port, 'localhost', profile.name);
|
|
350
359
|
verifyBrowserIdentity(browser, profile.browser, port);
|
|
351
360
|
checks.push({
|
|
352
361
|
label: 'port',
|
|
@@ -437,39 +446,56 @@ function registerProfilesCommands(browser) {
|
|
|
437
446
|
function registerTaskCommands(browser) {
|
|
438
447
|
browser
|
|
439
448
|
.command('start')
|
|
440
|
-
.description('Start a browser task
|
|
441
|
-
.
|
|
449
|
+
.description('Start a browser task. Pass --profile <name>, or omit to auto-pick a Chromium-family browser already installed on this machine.')
|
|
450
|
+
.option('-p, --profile <name>', 'Browser profile to use (auto-picks from installed Chromium-family browsers if omitted)')
|
|
442
451
|
.option(TASK_OPTION_FLAG, 'Task name (auto-generated if omitted)')
|
|
443
452
|
.option('-e, --endpoint <name>', 'Endpoint preset (defaults to the profile\'s default)')
|
|
444
453
|
.option('-u, --url <url>', 'Open URL in first tab')
|
|
454
|
+
.option('--no-skills', 'Skip auto-discovery of site-specific SKILL.md from ~/.agents/skills/browser/domain-skills/')
|
|
455
|
+
.option('--record', 'Start recording right after the tab opens (shorthand for `agents browser record start` as a follow-up)')
|
|
456
|
+
.option('--fps <n>', 'Recording frames per second (with --record; 1–30, default 5)', (v) => parseInt(v, 10))
|
|
457
|
+
.option('--duration <sec>', 'Recording duration cap in seconds (with --record; default 60)', (v) => parseInt(v, 10))
|
|
458
|
+
.option('--max-mb <mb>', 'Recording size cap in MB (with --record; default 25)', (v) => parseInt(v, 10))
|
|
445
459
|
.action(async (opts) => {
|
|
460
|
+
let profileName = opts.profile;
|
|
461
|
+
if (!profileName) {
|
|
462
|
+
try {
|
|
463
|
+
const detected = await ensureDefaultBrowserProfile();
|
|
464
|
+
profileName = detected.name;
|
|
465
|
+
}
|
|
466
|
+
catch (err) {
|
|
467
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
468
|
+
process.exit(1);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
446
471
|
// Pre-check the profile locally so we fail fast with a helpful error
|
|
447
472
|
// instead of round-tripping a generic "Profile not found" through the daemon.
|
|
448
|
-
const profile = await getProfile(
|
|
473
|
+
const profile = await getProfile(profileName);
|
|
449
474
|
if (!profile) {
|
|
450
|
-
console.error(`Profile "${
|
|
475
|
+
console.error(`Profile "${profileName}" not found.`);
|
|
451
476
|
const all = await listProfiles();
|
|
452
477
|
if (all.length > 0) {
|
|
453
478
|
console.error(`Available profiles: ${all.map((p) => p.name).join(', ')}`);
|
|
454
479
|
}
|
|
455
|
-
console.error(`Create one with: agents browser profiles create ${
|
|
480
|
+
console.error(`Create one with: agents browser profiles create ${profileName} --browser <chrome|comet|chromium|brave|edge|custom>`);
|
|
456
481
|
process.exit(1);
|
|
457
482
|
}
|
|
458
483
|
// Pre-check the endpoint name too — same fail-fast rationale.
|
|
459
484
|
if (opts.endpoint) {
|
|
460
485
|
const presets = getEndpointPresets(profile);
|
|
461
486
|
if (!presets[opts.endpoint]) {
|
|
462
|
-
console.error(`Endpoint "${opts.endpoint}" not found on profile "${
|
|
487
|
+
console.error(`Endpoint "${opts.endpoint}" not found on profile "${profileName}". ` +
|
|
463
488
|
`Available: ${Object.keys(presets).join(', ')}`);
|
|
464
489
|
process.exit(1);
|
|
465
490
|
}
|
|
466
491
|
}
|
|
467
492
|
const response = await sendIPCRequest({
|
|
468
493
|
action: 'start',
|
|
469
|
-
profile:
|
|
494
|
+
profile: profileName,
|
|
470
495
|
taskName: opts.task,
|
|
471
496
|
url: opts.url,
|
|
472
497
|
endpoint: opts.endpoint,
|
|
498
|
+
skipDomainSkill: opts.skills === false,
|
|
473
499
|
});
|
|
474
500
|
if (!response.ok) {
|
|
475
501
|
console.error(response.error);
|
|
@@ -488,6 +514,36 @@ function registerTaskCommands(browser) {
|
|
|
488
514
|
}
|
|
489
515
|
console.error(`Tip: export AGENTS_BROWSER_TASK=${response.task}`);
|
|
490
516
|
console.error('Try: agents browser screenshot | agents browser console --level error');
|
|
517
|
+
// Surface the matched domain-skill (if any) so an agent driving the
|
|
518
|
+
// task picks up site-specific selectors and gotchas before it starts
|
|
519
|
+
// clicking. Header is recognizable so an agent parsing the stream can
|
|
520
|
+
// extract the skill content; suffix repeats the skill name for greps.
|
|
521
|
+
if (response.skill) {
|
|
522
|
+
console.error('');
|
|
523
|
+
console.error(`--- domain-skill: ${response.skill.name} (${response.skill.hostname}) ---`);
|
|
524
|
+
console.error(response.skill.content);
|
|
525
|
+
console.error(`--- end domain-skill: ${response.skill.name} ---`);
|
|
526
|
+
}
|
|
527
|
+
// --record convenience: fire record-start right after the tab opens so
|
|
528
|
+
// the user gets a single-command capture flow. Failures here are
|
|
529
|
+
// reported but don't fail the start — the task is already running.
|
|
530
|
+
if (opts.record) {
|
|
531
|
+
const recordResponse = await sendIPCRequest({
|
|
532
|
+
action: 'record-start',
|
|
533
|
+
task: response.task,
|
|
534
|
+
tabId: response.tabId,
|
|
535
|
+
fps: opts.fps,
|
|
536
|
+
duration: opts.duration,
|
|
537
|
+
maxMb: opts.maxMb,
|
|
538
|
+
});
|
|
539
|
+
if (!recordResponse.ok) {
|
|
540
|
+
console.error(`Recording failed to start: ${recordResponse.error}`);
|
|
541
|
+
}
|
|
542
|
+
else {
|
|
543
|
+
console.error(`Recording at ${recordResponse.fps} fps (cap ${recordResponse.durationCapSec}s / ${recordResponse.maxMb} MB) -> ${recordResponse.path}`);
|
|
544
|
+
console.error('Stop with: agents browser record stop');
|
|
545
|
+
}
|
|
546
|
+
}
|
|
491
547
|
});
|
|
492
548
|
browser
|
|
493
549
|
.command('done')
|
|
@@ -798,10 +854,26 @@ function registerTaskCommands(browser) {
|
|
|
798
854
|
.option('-p, --profile <name>', 'Filter by profile')
|
|
799
855
|
.option('--json', 'Output machine-readable JSON')
|
|
800
856
|
.action(async (opts) => {
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
857
|
+
let response;
|
|
858
|
+
try {
|
|
859
|
+
response = await sendIPCRequest({
|
|
860
|
+
action: 'status',
|
|
861
|
+
profile: opts.profile,
|
|
862
|
+
}, { autoStartDaemon: false });
|
|
863
|
+
}
|
|
864
|
+
catch (err) {
|
|
865
|
+
if (err instanceof BrowserDaemonNotRunningError) {
|
|
866
|
+
const message = formatBrowserDaemonNotRunningError();
|
|
867
|
+
if (opts.json) {
|
|
868
|
+
console.log(JSON.stringify({ ok: false, error: message }));
|
|
869
|
+
}
|
|
870
|
+
else {
|
|
871
|
+
console.error(message);
|
|
872
|
+
}
|
|
873
|
+
process.exit(1);
|
|
874
|
+
}
|
|
875
|
+
throw err;
|
|
876
|
+
}
|
|
805
877
|
if (!response.ok) {
|
|
806
878
|
if (opts.json) {
|
|
807
879
|
console.log(JSON.stringify({ ok: false, error: response.error }));
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `agents cli` — manage declarative CLI binary installs.
|
|
3
|
+
*
|
|
4
|
+
* Each entry under <repo>/cli/<name>.yaml declares a CLI tool the user wants on
|
|
5
|
+
* the host PATH (e.g. higgsfield, gh, glab). On a fresh machine `agents cli
|
|
6
|
+
* install` runs the first install method whose package manager is available
|
|
7
|
+
* (npm > brew > script > binary, in declared order).
|
|
8
|
+
*
|
|
9
|
+
* This is a sibling to `agents mcp` but one layer down: MCP wires servers into
|
|
10
|
+
* agent configs; CLI puts binaries on the user's normal PATH. CLI manifests are
|
|
11
|
+
* NOT copied into per-agent version homes — they are global to the user.
|
|
12
|
+
*/
|
|
13
|
+
import type { Command } from 'commander';
|
|
14
|
+
export declare function registerCliCommands(program: Command): void;
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { confirm } from '@inquirer/prompts';
|
|
5
|
+
import { listCliManifests, listCliStatus, resolveCliManifest, installCli, describeMethod, selectInstallMethod, isCliInstalled, } from '../lib/cli-resources.js';
|
|
6
|
+
import { getUserAgentsDir } from '../lib/state.js';
|
|
7
|
+
import { isPromptCancelled } from './utils.js';
|
|
8
|
+
function userCliDir() {
|
|
9
|
+
return path.join(getUserAgentsDir(), 'cli');
|
|
10
|
+
}
|
|
11
|
+
/** Render the status table — one row per declared CLI. */
|
|
12
|
+
function printStatus(rows) {
|
|
13
|
+
if (rows.length === 0) {
|
|
14
|
+
console.log(chalk.gray('No CLIs declared.'));
|
|
15
|
+
console.log(chalk.gray(`Create one with: agents cli add <name>`));
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const nameWidth = Math.max(4, ...rows.map((r) => r.manifest.name.length));
|
|
19
|
+
for (const row of rows) {
|
|
20
|
+
const status = row.installed
|
|
21
|
+
? chalk.green('installed')
|
|
22
|
+
: chalk.yellow('missing');
|
|
23
|
+
const name = row.manifest.name.padEnd(nameWidth);
|
|
24
|
+
const source = chalk.gray(`[${row.manifest.source}]`);
|
|
25
|
+
const desc = row.manifest.description ? ' ' + chalk.gray(row.manifest.description) : '';
|
|
26
|
+
console.log(` ${name} ${status} ${source}${desc}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export function registerCliCommands(program) {
|
|
30
|
+
const cliCmd = program
|
|
31
|
+
.command('cli')
|
|
32
|
+
.description('Declare and install host CLI binaries (gh, higgsfield, glab, ...)')
|
|
33
|
+
.addHelpText('after', `
|
|
34
|
+
CLI manifests live in <repo>/cli/<name>.yaml and declare how to install a
|
|
35
|
+
binary on the host. On a fresh machine, 'agents cli install' runs the first
|
|
36
|
+
compatible method (npm > brew > script > binary) for every declared entry.
|
|
37
|
+
|
|
38
|
+
Examples:
|
|
39
|
+
# See which declared CLIs are installed on this host
|
|
40
|
+
agents cli list
|
|
41
|
+
|
|
42
|
+
# Install everything that's missing
|
|
43
|
+
agents cli install
|
|
44
|
+
|
|
45
|
+
# Install one
|
|
46
|
+
agents cli install higgsfield
|
|
47
|
+
|
|
48
|
+
# Show the manifest detail
|
|
49
|
+
agents cli view higgsfield
|
|
50
|
+
|
|
51
|
+
# Exit 0 if all declared CLIs are installed (use in CI / setup scripts)
|
|
52
|
+
agents cli check
|
|
53
|
+
|
|
54
|
+
When to use:
|
|
55
|
+
- After 'agents pull' on a new machine, to materialize host binaries
|
|
56
|
+
- In a team setup: commit cli/ entries so teammates get the same toolchain
|
|
57
|
+
`);
|
|
58
|
+
cliCmd
|
|
59
|
+
.command('list')
|
|
60
|
+
.description('Show all declared CLIs and whether each is installed on this host')
|
|
61
|
+
.action(() => {
|
|
62
|
+
const { statuses, errors } = listCliStatus(process.cwd());
|
|
63
|
+
printStatus(statuses);
|
|
64
|
+
for (const err of errors) {
|
|
65
|
+
console.log(chalk.red(` parse error: ${err.file}: ${err.reason}`));
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
cliCmd
|
|
69
|
+
.command('check')
|
|
70
|
+
.description('Exit 0 if every declared CLI is installed, 1 otherwise')
|
|
71
|
+
.action(() => {
|
|
72
|
+
const { statuses, errors } = listCliStatus(process.cwd());
|
|
73
|
+
const missing = statuses.filter((s) => !s.installed);
|
|
74
|
+
if (errors.length > 0) {
|
|
75
|
+
for (const err of errors) {
|
|
76
|
+
console.error(chalk.red(`parse error: ${err.file}: ${err.reason}`));
|
|
77
|
+
}
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
if (missing.length === 0) {
|
|
81
|
+
console.log(chalk.green(`All ${statuses.length} declared CLI(s) installed.`));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
console.log(chalk.yellow(`Missing: ${missing.map((s) => s.manifest.name).join(', ')}`));
|
|
85
|
+
process.exit(1);
|
|
86
|
+
});
|
|
87
|
+
cliCmd
|
|
88
|
+
.command('install [name]')
|
|
89
|
+
.description('Install one (by name) or all missing declared CLIs')
|
|
90
|
+
.option('-y, --yes', 'skip the confirmation prompt')
|
|
91
|
+
.option('--dry-run', 'print install commands without executing them')
|
|
92
|
+
.option('--force', 'reinstall even when the check command currently passes')
|
|
93
|
+
.action(async (nameArg, opts) => {
|
|
94
|
+
let targets;
|
|
95
|
+
if (nameArg) {
|
|
96
|
+
const manifest = resolveCliManifest(nameArg, process.cwd());
|
|
97
|
+
if (!manifest) {
|
|
98
|
+
console.error(chalk.red(`No CLI manifest named "${nameArg}".`));
|
|
99
|
+
console.error(chalk.gray(`Looked in: ${userCliDir()}`));
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
targets = [manifest];
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
const { manifests, errors } = listCliManifests(process.cwd());
|
|
106
|
+
for (const err of errors) {
|
|
107
|
+
console.error(chalk.red(`parse error: ${err.file}: ${err.reason}`));
|
|
108
|
+
}
|
|
109
|
+
if (manifests.length === 0) {
|
|
110
|
+
console.log(chalk.gray('No CLIs declared. Nothing to install.'));
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
targets = manifests;
|
|
114
|
+
}
|
|
115
|
+
// Filter out already-installed unless --force
|
|
116
|
+
const work = targets.filter((m) => opts.force || !isCliInstalled(m));
|
|
117
|
+
if (work.length === 0) {
|
|
118
|
+
console.log(chalk.green(`All ${targets.length} declared CLI(s) already installed.`));
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
// Preview + confirm
|
|
122
|
+
console.log(chalk.bold('\nWill install:'));
|
|
123
|
+
for (const m of work) {
|
|
124
|
+
const method = selectInstallMethod(m);
|
|
125
|
+
const action = method ? describeMethod(method) : chalk.red('no compatible install method');
|
|
126
|
+
console.log(` ${chalk.cyan(m.name.padEnd(20))} ${chalk.gray(action)}`);
|
|
127
|
+
}
|
|
128
|
+
console.log('');
|
|
129
|
+
if (!opts.yes && !opts.dryRun) {
|
|
130
|
+
try {
|
|
131
|
+
const proceed = await confirm({ message: 'Proceed?', default: true });
|
|
132
|
+
if (!proceed) {
|
|
133
|
+
console.log(chalk.gray('Cancelled.'));
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
if (isPromptCancelled(err)) {
|
|
139
|
+
console.log(chalk.gray('Cancelled.'));
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
throw err;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// Execute
|
|
146
|
+
let failures = 0;
|
|
147
|
+
for (const m of work) {
|
|
148
|
+
console.log(chalk.bold(`\n→ ${m.name}`));
|
|
149
|
+
const result = installCli(m, { dryRun: opts.dryRun });
|
|
150
|
+
if (result.output)
|
|
151
|
+
console.log(chalk.gray(result.output));
|
|
152
|
+
if (result.error) {
|
|
153
|
+
console.log(chalk.red(` ${result.error}`));
|
|
154
|
+
failures++;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (opts.dryRun)
|
|
158
|
+
continue;
|
|
159
|
+
if (result.installed) {
|
|
160
|
+
console.log(chalk.green(` installed (${describeMethod(result.method)})`));
|
|
161
|
+
if (m.postInstall) {
|
|
162
|
+
console.log(chalk.gray(m.postInstall.trim().split('\n').map((l) => ' ' + l).join('\n')));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
console.log(chalk.yellow(` install command ran but \`${m.check}\` still fails — check the output above`));
|
|
167
|
+
failures++;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (failures > 0)
|
|
171
|
+
process.exit(1);
|
|
172
|
+
});
|
|
173
|
+
cliCmd
|
|
174
|
+
.command('view <name>')
|
|
175
|
+
.description('Show the parsed manifest detail')
|
|
176
|
+
.action((name) => {
|
|
177
|
+
const manifest = resolveCliManifest(name, process.cwd());
|
|
178
|
+
if (!manifest) {
|
|
179
|
+
console.error(chalk.red(`No CLI manifest named "${name}".`));
|
|
180
|
+
process.exit(1);
|
|
181
|
+
}
|
|
182
|
+
console.log(chalk.bold.cyan(manifest.name));
|
|
183
|
+
if (manifest.description)
|
|
184
|
+
console.log(' ' + chalk.gray(manifest.description));
|
|
185
|
+
if (manifest.homepage)
|
|
186
|
+
console.log(' ' + chalk.gray(manifest.homepage));
|
|
187
|
+
console.log(' ' + chalk.gray(`source: ${manifest.source}`));
|
|
188
|
+
console.log(' ' + chalk.gray(`file: ${manifest.path}`));
|
|
189
|
+
console.log('');
|
|
190
|
+
console.log(chalk.bold(' check'));
|
|
191
|
+
console.log(' ' + manifest.check);
|
|
192
|
+
console.log('');
|
|
193
|
+
console.log(chalk.bold(' install methods'));
|
|
194
|
+
for (const method of manifest.install) {
|
|
195
|
+
console.log(' ' + describeMethod(method));
|
|
196
|
+
}
|
|
197
|
+
if (manifest.postInstall) {
|
|
198
|
+
console.log('');
|
|
199
|
+
console.log(chalk.bold(' post_install'));
|
|
200
|
+
for (const line of manifest.postInstall.trim().split('\n')) {
|
|
201
|
+
console.log(' ' + line);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
console.log('');
|
|
205
|
+
console.log(` status: ${isCliInstalled(manifest) ? chalk.green('installed') : chalk.yellow('missing')}`);
|
|
206
|
+
});
|
|
207
|
+
cliCmd
|
|
208
|
+
.command('add <name>')
|
|
209
|
+
.description('Scaffold a new manifest at ~/.agents/cli/<name>.yaml')
|
|
210
|
+
.option('--npm <pkg>', 'declare an npm install method')
|
|
211
|
+
.option('--brew <formula>', 'declare a brew install method')
|
|
212
|
+
.option('--script <url>', 'declare a curl|sh install method')
|
|
213
|
+
.option('--description <text>', 'one-line description')
|
|
214
|
+
.option('--homepage <url>', 'project homepage')
|
|
215
|
+
.action((name, opts) => {
|
|
216
|
+
const dir = userCliDir();
|
|
217
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
218
|
+
const target = path.join(dir, `${name}.yaml`);
|
|
219
|
+
if (fs.existsSync(target)) {
|
|
220
|
+
console.error(chalk.red(`Already exists: ${target}`));
|
|
221
|
+
process.exit(1);
|
|
222
|
+
}
|
|
223
|
+
const methods = [];
|
|
224
|
+
if (opts.npm)
|
|
225
|
+
methods.push(` - npm: "${opts.npm}"`);
|
|
226
|
+
if (opts.brew)
|
|
227
|
+
methods.push(` - brew: ${opts.brew}`);
|
|
228
|
+
if (opts.script)
|
|
229
|
+
methods.push(` - script: ${opts.script}`);
|
|
230
|
+
if (methods.length === 0) {
|
|
231
|
+
methods.push(` - npm: "${name}"`);
|
|
232
|
+
}
|
|
233
|
+
const lines = [
|
|
234
|
+
`name: ${name}`,
|
|
235
|
+
...(opts.description ? [`description: ${opts.description}`] : []),
|
|
236
|
+
...(opts.homepage ? [`homepage: ${opts.homepage}`] : []),
|
|
237
|
+
`check: ${name} --version`,
|
|
238
|
+
`install:`,
|
|
239
|
+
...methods,
|
|
240
|
+
];
|
|
241
|
+
fs.writeFileSync(target, lines.join('\n') + '\n');
|
|
242
|
+
console.log(chalk.green(`Created ${target}`));
|
|
243
|
+
});
|
|
244
|
+
}
|