Getting started with Pinion
Pinion is a handy tool that helps you generate code and automate tasks. You can create and modify files directly from the command line based on descriptions or examples. Pinion also allows to create custom code generators that bring together the benefits of static templates, a task runner and AI-generated code.
CLI
With NodeJS available, Pinion can be installed globally like this:
npm i @feathershq/pinion@pre -g
npm i @feathershq/pinion@pre -g
Now the pinion
command should be available everywhere. It takes a description with %s
as placeholders for filenames, followed by a list of those filenames:
pinion "Write about code generators in %s" generators.md
pinion "Write about code generators in %s" generators.md
You can also use it with existing files:
pinion "Translate %s to German as %s" generators.md generators.de.md
pinion "Translate %s to German as %s" generators.md generators.de.md
Note
The first time you use the command with a description you will be asked to sign into feathers.cloud using GitHub. You can try command line descriptions and AI tasks for free with no time limit. For more information see our pricing.
SDK
The Pinion SDK allows to create flexible code generators using TypeScript. Install it as a development dependency into your project like this:
npm install @feathershq/pinion@pre --save-dev
npm install @feathershq/pinion@pre --save-dev
Note
While generators are written in TypeScript your project can use any programming language. For projects without a package.json
run npm init --yes
first.
Generator files
A Pinion generator is any TypeScript file that exports a generate
function. It can live anywhere in your project. For example, the following generators/readme.ts
file generates a basic readme.md
file from a template string:
import type { PinionContext } from '@feathershq/pinion'
import { generator, file, renderTemplate } from '@feathershq/pinion'
interface Context extends PinionContext {}
// A template for a markdown Readme file
const readme = () => `
# Hello world
This is a readme generated by Pinion
Copyright (c) ${new Date().getFullYear()}
`
export const generate = (context: Context) =>
generator(context)
// Render the readme template
.then(renderTemplate(readme, file('readme.md')))
import type { PinionContext } from '@feathershq/pinion'
import { generator, file, renderTemplate } from '@feathershq/pinion'
interface Context extends PinionContext {}
// A template for a markdown Readme file
const readme = () => `
# Hello world
This is a readme generated by Pinion
Copyright (c) ${new Date().getFullYear()}
`
export const generate = (context: Context) =>
generator(context)
// Render the readme template
.then(renderTemplate(readme, file('readme.md')))
import { generator, file, renderTemplate } from '@feathershq/pinion'
// A template for a markdown Readme file
const readme = () => `
# Hello world
This is a readme generated by Pinion
Copyright (c) ${new Date().getFullYear()}
`
export const generate = (context) =>
generator(context)
// Render the readme template
.then(renderTemplate(readme, file('readme.md')))
import { generator, file, renderTemplate } from '@feathershq/pinion'
// A template for a markdown Readme file
const readme = () => `
# Hello world
This is a readme generated by Pinion
Copyright (c) ${new Date().getFullYear()}
`
export const generate = (context) =>
generator(context)
// Render the readme template
.then(renderTemplate(readme, file('readme.md')))
Then run
npx pinion run generators/readme.ts
npx pinion run generators/readme.ts
Asking questions
You can ask questions from the command line using Inquirer with the prompt
operation:
import type { PinionContext } from '@feathershq/pinion'
import {
generator,
renderTemplate,
file,
prompt
} from '@feathershq/pinion'
interface Context extends PinionContext {
name: string
description: string
}
// A template for a markdown Readme file
const readme = ({ name, description }: Context) => `
# ${name}
> ${description}
This is a readme generated by Pinion
Copyright (c) ${new Date().getFullYear()}
`
export const generate = (context: Context) =>
generator(context)
// Ask prompts (using Inquirer)
.then(
prompt<Context>([
{
type: 'input',
name: 'name',
message: 'What is the name of your app?'
},
{
type: 'input',
name: 'description',
description: 'Write a short description'
}
])
)
// Render the readme template
.then(renderTemplate(readme, file('readme.md')))
import type { PinionContext } from '@feathershq/pinion'
import {
generator,
renderTemplate,
file,
prompt
} from '@feathershq/pinion'
interface Context extends PinionContext {
name: string
description: string
}
// A template for a markdown Readme file
const readme = ({ name, description }: Context) => `
# ${name}
> ${description}
This is a readme generated by Pinion
Copyright (c) ${new Date().getFullYear()}
`
export const generate = (context: Context) =>
generator(context)
// Ask prompts (using Inquirer)
.then(
prompt<Context>([
{
type: 'input',
name: 'name',
message: 'What is the name of your app?'
},
{
type: 'input',
name: 'description',
description: 'Write a short description'
}
])
)
// Render the readme template
.then(renderTemplate(readme, file('readme.md')))
import {
generator,
renderTemplate,
file,
prompt
} from '@feathershq/pinion'
// A template for a markdown Readme file
const readme = ({ name, description }) => `
# ${name}
> ${description}
This is a readme generated by Pinion
Copyright (c) ${new Date().getFullYear()}
`
export const generate = (context) =>
generator(context)
// Ask prompts (using Inquirer)
.then(
prompt([
{
type: 'input',
name: 'name',
message: 'What is the name of your app?'
},
{
type: 'input',
name: 'description',
description: 'Write a short description'
}
])
)
// Render the readme template
.then(renderTemplate(readme, file('readme.md')))
import {
generator,
renderTemplate,
file,
prompt
} from '@feathershq/pinion'
// A template for a markdown Readme file
const readme = ({ name, description }) => `
# ${name}
> ${description}
This is a readme generated by Pinion
Copyright (c) ${new Date().getFullYear()}
`
export const generate = (context) =>
generator(context)
// Ask prompts (using Inquirer)
.then(
prompt([
{
type: 'input',
name: 'name',
message: 'What is the name of your app?'
},
{
type: 'input',
name: 'description',
description: 'Write a short description'
}
])
)
// Render the readme template
.then(renderTemplate(readme, file('readme.md')))
AI tasks
Just like the descriptions in the pinion command, AI tasks can also be included in generators. The following file translates the generated readme to German and French:
import type { PinionContext } from '@feathershq/pinion'
import {
generator,
renderTemplate,
file,
prompt,
gpt
} from '@feathershq/pinion'
interface Context extends PinionContext {
name: string
description: string
}
// A template for a markdown Readme file
const readme = ({ name, description }: Context) => `
# ${name}
> ${description}
This is a readme generated by Pinion
Copyright (c) ${new Date().getFullYear()}
`
export const generate = (context: Context) =>
generator(context)
// Ask prompts (using Inquirer)
.then(
prompt<Context>([
{
type: 'input',
name: 'name',
message: 'What is the name of your app?'
},
{
type: 'input',
name: 'description',
description: 'Write a short description'
}
])
)
// Render the readme template
.then(renderTemplate(readme, file('readme.md')))
// Translate the generated file
.then(
gpt`Translate ${file('readme.md')} to German as ${file(
'readme.de.md'
)} and to French as ${file('readme.fr.md')}`
)
import type { PinionContext } from '@feathershq/pinion'
import {
generator,
renderTemplate,
file,
prompt,
gpt
} from '@feathershq/pinion'
interface Context extends PinionContext {
name: string
description: string
}
// A template for a markdown Readme file
const readme = ({ name, description }: Context) => `
# ${name}
> ${description}
This is a readme generated by Pinion
Copyright (c) ${new Date().getFullYear()}
`
export const generate = (context: Context) =>
generator(context)
// Ask prompts (using Inquirer)
.then(
prompt<Context>([
{
type: 'input',
name: 'name',
message: 'What is the name of your app?'
},
{
type: 'input',
name: 'description',
description: 'Write a short description'
}
])
)
// Render the readme template
.then(renderTemplate(readme, file('readme.md')))
// Translate the generated file
.then(
gpt`Translate ${file('readme.md')} to German as ${file(
'readme.de.md'
)} and to French as ${file('readme.fr.md')}`
)
import {
generator,
renderTemplate,
file,
prompt,
gpt
} from '@feathershq/pinion'
// A template for a markdown Readme file
const readme = ({ name, description }) => `
# ${name}
> ${description}
This is a readme generated by Pinion
Copyright (c) ${new Date().getFullYear()}
`
export const generate = (context) =>
generator(context)
// Ask prompts (using Inquirer)
.then(
prompt([
{
type: 'input',
name: 'name',
message: 'What is the name of your app?'
},
{
type: 'input',
name: 'description',
description: 'Write a short description'
}
])
)
// Render the readme template
.then(renderTemplate(readme, file('readme.md')))
// Translate the generated file
.then(
gpt`Translate ${file('readme.md')} to German as ${file(
'readme.de.md'
)} and to French as ${file('readme.fr.md')}`
)
import {
generator,
renderTemplate,
file,
prompt,
gpt
} from '@feathershq/pinion'
// A template for a markdown Readme file
const readme = ({ name, description }) => `
# ${name}
> ${description}
This is a readme generated by Pinion
Copyright (c) ${new Date().getFullYear()}
`
export const generate = (context) =>
generator(context)
// Ask prompts (using Inquirer)
.then(
prompt([
{
type: 'input',
name: 'name',
message: 'What is the name of your app?'
},
{
type: 'input',
name: 'description',
description: 'Write a short description'
}
])
)
// Render the readme template
.then(renderTemplate(readme, file('readme.md')))
// Translate the generated file
.then(
gpt`Translate ${file('readme.md')} to German as ${file(
'readme.de.md'
)} and to French as ${file('readme.fr.md')}`
)
Pricing
When using the pinion command or AI tasks, requests are sent to feathers.cloud and measured by total incoming and outgoing data transfer in kilobytes (Kb). You can try Pinion for free for an unlimited time for your first 100Kb. You can see your current data transfer in the dashboard.
The Pinion GPT plan starts at
- 5$ per month for the first 250Kb
- Then $0.01 per additional Kb
You will get
- Unlimited transfer
- API key support
Note
The Pinion codebase and generators are open source under the MIT license. When not using AI tasks, no data will be sent and you do not require an account.