Skip to content
Draft
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@
"react": "^19.2.5",
"react-dom": "^19.2.5",
"smol-toml": "^1.6.1",
"tar": "^7.5.9",
"ws": "^8.20.0",
"zod": "^3.25.76"
},
Expand Down
31 changes: 31 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions scripts/postinstall-welcome.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ console.log(` Version: ${version}\n`);

// Display the welcome messages
console.log('Ably CLI installed successfully!');
console.log('To get started, explore commands:');
console.log(' ably --help');
console.log('\nOr log in to your Ably account:');
console.log(' ably login');
console.log('\nGet started in one command (authenticate + install Agent Skills):');
console.log(' ably init');
console.log('\nOr explore:');
console.log(' ably --help');
8 changes: 8 additions & 0 deletions src/base-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ export const WEB_CLI_RESTRICTED_COMMANDS = [
// File-reading commands can expose server filesystem contents in web CLI mode
"push:config:set-apns",
"push:config:set-fcm",

// Agent-skills onboarding writes/removes files on the local filesystem —
// in web CLI mode these would touch the server's filesystem, not the user's.
"init",
"skills*",
];

/* Additional restricted commands when running in anonymous web CLI mode */
Expand Down Expand Up @@ -101,6 +106,9 @@ export const INTERACTIVE_UNSUITABLE_COMMANDS = [
"autocomplete", // Autocomplete setup is not needed in interactive mode
"config", // Config editing is not suitable for interactive mode
"version", // Version is shown at startup and available via --version
"init", // One-time setup; not meaningful inside an already-running session
"skills:install", // Filesystem install; not meaningful inside an interactive session
"skills:uninstall", // Filesystem cleanup; not meaningful inside an interactive session
];

// List of commands that should not show account/app info
Expand Down
121 changes: 121 additions & 0 deletions src/commands/init.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { Flags } from "@oclif/core";
import chalk from "chalk";

import { AblyBaseCommand } from "../base-command.js";
import { coreGlobalFlags } from "../flags.js";
import { TARGET_CONFIGS } from "../services/skills-installer.js";
import { BaseFlags } from "../types/cli.js";
import { displayLogo } from "../utils/logo.js";

export default class Init extends AblyBaseCommand {
static override description =
"Set up Ably for AI-powered development — authenticate and install Agent Skills";

static override examples = [
"<%= config.bin %> <%= command.id %>",
"<%= config.bin %> <%= command.id %> --global",
"<%= config.bin %> <%= command.id %> --target claude-code",
"<%= config.bin %> <%= command.id %> --skip-auth",
"<%= config.bin %> <%= command.id %> --skip-auth --json",
];

static override flags = {
...coreGlobalFlags,
global: Flags.boolean({
char: "g",
default: false,
description: "Install skills globally (~/) instead of project-level",
}),
target: Flags.string({
char: "t",
multiple: true,
options: ["auto", ...Object.keys(TARGET_CONFIGS), "all"],
default: ["auto"],
description: "Target IDE(s) to install skills for",
}),
force: Flags.boolean({
char: "f",
default: false,
description: "Overwrite existing skills without prompting",
}),
"skip-auth": Flags.boolean({
default: false,
description: "Skip authentication step",
}),
skill: Flags.string({
char: "s",
multiple: true,
description: "Install only specific skill(s) by name",
}),
"skills-repo": Flags.string({
default: "ably/agent-skills",
description: "GitHub repo to fetch skills from",
}),
};

async run(): Promise<void> {
const { flags } = await this.parse(Init);
const jsonMode = this.shouldOutputJson(flags);

if (!jsonMode) {
displayLogo(this.log.bind(this));
}

await this.runAuth(flags);
await this.config.runCommand(
"skills:install",
this.buildInstallArgv(flags),
);
}

private async runAuth(
flags: BaseFlags & { "skip-auth": boolean },
): Promise<void> {
if (flags["skip-auth"]) return;

if (this.shouldOutputJson(flags)) {
this.fail(
"Authentication cannot run in --json mode. Use --skip-auth or set ABLY_ACCESS_TOKEN.",
flags,
"init",
);
}

this.log(chalk.bold("\n Authenticate with Ably\n"));
try {
await this.config.runCommand("accounts:login", []);
} catch {
this.fail(
"Authentication failed. Use --skip-auth if you are already logged in.",
flags,
"init",
);
}
}

private buildInstallArgv(
flags: BaseFlags & {
global: boolean;
target: string[];
force: boolean;
skill?: string[];
"skills-repo": string;
},
): string[] {
const argv: string[] = [];

if (flags.global) argv.push("--global");
for (const target of flags.target) argv.push("--target", target);
if (flags.force) argv.push("--force");
if (flags.skill) {
for (const skill of flags.skill) argv.push("--skill", skill);
}
argv.push("--skills-repo", flags["skills-repo"]);

if (flags.json) argv.push("--json");
else if (flags["pretty-json"]) argv.push("--pretty-json");
if (flags.verbose) argv.push("--verbose");

return argv;
}
}
15 changes: 15 additions & 0 deletions src/commands/skills/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { BaseTopicCommand } from "../../base-topic-command.js";

export default class Skills extends BaseTopicCommand {
protected topicName = "skills";
protected commandGroup = "Agent Skills";

static override description =
"Install or remove Ably Agent Skills for AI coding tools";

static override examples = [
"<%= config.bin %> <%= command.id %> install",
"<%= config.bin %> <%= command.id %> install --target claude-code",
"<%= config.bin %> <%= command.id %> uninstall --force",
];
}
Loading
Loading