stoke/src/cli.js

998 lines
36 KiB
JavaScript
Raw Normal View History

#!/usr/bin/env node
Audit: fix auth/config bugs, add issue/pr create, tests and docs Fixes found during a full audit of the CLI: - auth logout: remote token revocation always failed with 401 because Forgejo only accepts Basic auth on the token endpoints. Logout now asks for (or accepts) the account password, supports --password, --password-file and --local-only, and clearly reports when the token is left active. - Silent password prompt actually echoed the password on a TTY: overriding rl.write does not suppress readline echo. Switched to the callback readline module and mute _writeToOutput instead (the readline/promises interface does not honor that hook). - Global --config flag was silently ignored: config paths were resolved at require time, before the preAction hook set STOKE_CONFIG_FILE. Paths are now resolved lazily on every access. - XDG_CONFIG_HOME handling put the config in $XDG_CONFIG_HOME/.config/stoke; per the XDG spec it now resolves to $XDG_CONFIG_HOME/stoke. - repo create: --auto-init defaulted to true with no way to disable it; added --no-auto-init. - repo import/import-batch: a GitHub token was required even for non-GitHub services (e.g. --service git), making those imports fail without gh/GITHUB_TOKEN. Tokens are now only auto-resolved for the github service; batch imports resolve per entry and memoize. - Branding leftovers: 'Run: forgejo auth login' hint and forgejo-cli/1.0.0 User-Agent now say stoke (UA tracks pkg.version). - Added request timeouts (30s default, 10m for migrations). - --limit and --team-id are validated as integers instead of silently misbehaving on garbage (NaN made -l show all results). New commands (per the repo's every-operation-becomes-a-command design): - stoke issue create (title/body/body-file/assignees) - stoke pr create (head/base/title/body/body-file) Tests and metadata: - New test suite on the built-in node:test runner (25 tests) covering config resolution/persistence, the API client with a mocked fetch, and end-to-end CLI behavior. npm test previously matched no files. - package.json: engines >=22.12.0 (required by commander@15 — the README claimed Node 18), repository, keywords, author; version 1.1.0. - README: corrected Node requirement, documented repo rename (was missing), issue create, pr create, logout options and revocation caveat, --no-auto-init, XDG behavior, import token rules, testing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 19:28:23 +00:00
const { Command, InvalidArgumentError } = require('commander');
const readline = require('node:readline');
const fs = require('node:fs');
const { execSync } = require('node:child_process');
const { stdin: input, stdout: output } = require('node:process');
Audit: fix auth/config bugs, add issue/pr create, tests and docs Fixes found during a full audit of the CLI: - auth logout: remote token revocation always failed with 401 because Forgejo only accepts Basic auth on the token endpoints. Logout now asks for (or accepts) the account password, supports --password, --password-file and --local-only, and clearly reports when the token is left active. - Silent password prompt actually echoed the password on a TTY: overriding rl.write does not suppress readline echo. Switched to the callback readline module and mute _writeToOutput instead (the readline/promises interface does not honor that hook). - Global --config flag was silently ignored: config paths were resolved at require time, before the preAction hook set STOKE_CONFIG_FILE. Paths are now resolved lazily on every access. - XDG_CONFIG_HOME handling put the config in $XDG_CONFIG_HOME/.config/stoke; per the XDG spec it now resolves to $XDG_CONFIG_HOME/stoke. - repo create: --auto-init defaulted to true with no way to disable it; added --no-auto-init. - repo import/import-batch: a GitHub token was required even for non-GitHub services (e.g. --service git), making those imports fail without gh/GITHUB_TOKEN. Tokens are now only auto-resolved for the github service; batch imports resolve per entry and memoize. - Branding leftovers: 'Run: forgejo auth login' hint and forgejo-cli/1.0.0 User-Agent now say stoke (UA tracks pkg.version). - Added request timeouts (30s default, 10m for migrations). - --limit and --team-id are validated as integers instead of silently misbehaving on garbage (NaN made -l show all results). New commands (per the repo's every-operation-becomes-a-command design): - stoke issue create (title/body/body-file/assignees) - stoke pr create (head/base/title/body/body-file) Tests and metadata: - New test suite on the built-in node:test runner (25 tests) covering config resolution/persistence, the API client with a mocked fetch, and end-to-end CLI behavior. npm test previously matched no files. - package.json: engines >=22.12.0 (required by commander@15 — the README claimed Node 18), repository, keywords, author; version 1.1.0. - README: corrected Node requirement, documented repo rename (was missing), issue create, pr create, logout options and revocation caveat, --no-auto-init, XDG behavior, import token rules, testing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 19:28:23 +00:00
const { loadConfig, saveConfig, clearConfig, getConfigPath } = require('./config');
const { ForgejoClient } = require('./api');
const pkg = require('../package.json');
const program = new Command();
program
2026-07-22 15:16:42 +00:00
.name('stoke')
.description('CLI for the heavy-duty forge (https://forgejo.heavyduty.builders)')
.version(pkg.version)
.configureOutput({ outputError: (str, write) => write(`Error: ${str}`) });
program
.option('-c, --config <path>', 'path to configuration file')
.hook('preAction', (thisCommand) => {
if (thisCommand.opts().config) {
2026-07-22 15:16:42 +00:00
process.env.STOKE_CONFIG_FILE = thisCommand.opts().config;
}
});
Audit: fix auth/config bugs, add issue/pr create, tests and docs Fixes found during a full audit of the CLI: - auth logout: remote token revocation always failed with 401 because Forgejo only accepts Basic auth on the token endpoints. Logout now asks for (or accepts) the account password, supports --password, --password-file and --local-only, and clearly reports when the token is left active. - Silent password prompt actually echoed the password on a TTY: overriding rl.write does not suppress readline echo. Switched to the callback readline module and mute _writeToOutput instead (the readline/promises interface does not honor that hook). - Global --config flag was silently ignored: config paths were resolved at require time, before the preAction hook set STOKE_CONFIG_FILE. Paths are now resolved lazily on every access. - XDG_CONFIG_HOME handling put the config in $XDG_CONFIG_HOME/.config/stoke; per the XDG spec it now resolves to $XDG_CONFIG_HOME/stoke. - repo create: --auto-init defaulted to true with no way to disable it; added --no-auto-init. - repo import/import-batch: a GitHub token was required even for non-GitHub services (e.g. --service git), making those imports fail without gh/GITHUB_TOKEN. Tokens are now only auto-resolved for the github service; batch imports resolve per entry and memoize. - Branding leftovers: 'Run: forgejo auth login' hint and forgejo-cli/1.0.0 User-Agent now say stoke (UA tracks pkg.version). - Added request timeouts (30s default, 10m for migrations). - --limit and --team-id are validated as integers instead of silently misbehaving on garbage (NaN made -l show all results). New commands (per the repo's every-operation-becomes-a-command design): - stoke issue create (title/body/body-file/assignees) - stoke pr create (head/base/title/body/body-file) Tests and metadata: - New test suite on the built-in node:test runner (25 tests) covering config resolution/persistence, the API client with a mocked fetch, and end-to-end CLI behavior. npm test previously matched no files. - package.json: engines >=22.12.0 (required by commander@15 — the README claimed Node 18), repository, keywords, author; version 1.1.0. - README: corrected Node requirement, documented repo rename (was missing), issue create, pr create, logout options and revocation caveat, --no-auto-init, XDG behavior, import token rules, testing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 19:28:23 +00:00
// Uses the callback readline module: it echoes keystrokes through the
// overridable _writeToOutput hook, which the readline/promises interface
// does not honor, so muting that hook is what actually keeps passwords
// off the terminal.
function prompt(question, silent = false) {
return new Promise((resolve) => {
const rl = readline.createInterface({ input, output });
if (silent) {
rl._writeToOutput = () => {};
output.write(question);
rl.question('', (answer) => {
output.write('\n');
rl.close();
resolve(answer);
});
} else {
rl.question(question, (answer) => {
rl.close();
resolve(answer);
});
}
});
}
function readSecretFile(filePath, label) {
try {
return fs.readFileSync(filePath, 'utf8').replace(/\r?\n$/, '');
} catch (err) {
throw new Error(`Could not read ${label} file ${filePath}: ${err.message}`);
}
}
Audit: fix auth/config bugs, add issue/pr create, tests and docs Fixes found during a full audit of the CLI: - auth logout: remote token revocation always failed with 401 because Forgejo only accepts Basic auth on the token endpoints. Logout now asks for (or accepts) the account password, supports --password, --password-file and --local-only, and clearly reports when the token is left active. - Silent password prompt actually echoed the password on a TTY: overriding rl.write does not suppress readline echo. Switched to the callback readline module and mute _writeToOutput instead (the readline/promises interface does not honor that hook). - Global --config flag was silently ignored: config paths were resolved at require time, before the preAction hook set STOKE_CONFIG_FILE. Paths are now resolved lazily on every access. - XDG_CONFIG_HOME handling put the config in $XDG_CONFIG_HOME/.config/stoke; per the XDG spec it now resolves to $XDG_CONFIG_HOME/stoke. - repo create: --auto-init defaulted to true with no way to disable it; added --no-auto-init. - repo import/import-batch: a GitHub token was required even for non-GitHub services (e.g. --service git), making those imports fail without gh/GITHUB_TOKEN. Tokens are now only auto-resolved for the github service; batch imports resolve per entry and memoize. - Branding leftovers: 'Run: forgejo auth login' hint and forgejo-cli/1.0.0 User-Agent now say stoke (UA tracks pkg.version). - Added request timeouts (30s default, 10m for migrations). - --limit and --team-id are validated as integers instead of silently misbehaving on garbage (NaN made -l show all results). New commands (per the repo's every-operation-becomes-a-command design): - stoke issue create (title/body/body-file/assignees) - stoke pr create (head/base/title/body/body-file) Tests and metadata: - New test suite on the built-in node:test runner (25 tests) covering config resolution/persistence, the API client with a mocked fetch, and end-to-end CLI behavior. npm test previously matched no files. - package.json: engines >=22.12.0 (required by commander@15 — the README claimed Node 18), repository, keywords, author; version 1.1.0. - README: corrected Node requirement, documented repo rename (was missing), issue create, pr create, logout options and revocation caveat, --no-auto-init, XDG behavior, import token rules, testing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 19:28:23 +00:00
function readBodyOption(options) {
if (options.bodyFile) {
try {
return fs.readFileSync(options.bodyFile, 'utf8');
} catch (err) {
throw new Error(`Could not read body file ${options.bodyFile}: ${err.message}`);
}
}
return options.body;
}
function parseLimit(value) {
const n = Number(value);
if (!Number.isInteger(n) || n < 0) {
throw new InvalidArgumentError('Limit must be a non-negative integer (0 means all).');
}
return n;
}
function parseId(value) {
const n = Number(value);
if (!Number.isInteger(n) || n <= 0) {
throw new InvalidArgumentError('Id must be a positive integer.');
}
return n;
}
function makeTokenName() {
const host = require('node:os').hostname() || 'unknown';
2026-07-22 15:16:42 +00:00
return `stoke-${host}-${Date.now()}`;
}
const DEFAULT_TOKEN_SCOPES = [
'read:activitypub', 'write:activitypub',
'read:issue', 'write:issue',
'read:misc', 'write:misc',
'read:organization', 'write:organization',
'read:package', 'write:package',
'read:repository', 'write:repository',
'read:user', 'write:user',
];
function printErrorAndExit(err) {
console.error(`Authentication failed: ${err.message}`);
if (err.status) {
console.error(`HTTP status: ${err.status}`);
}
if (err.body && err.body.url) {
console.error(`URL: ${err.body.url}`);
}
process.exit(1);
}
const auth = program
.command('auth')
.description('Manage Forgejo authentication');
auth
.command('login')
.description('Authenticate against a Forgejo instance and store an access token')
2026-07-22 15:16:42 +00:00
.option('-u, --url <url>', 'Forgejo base URL', process.env.STOKE_URL || process.env.FORGEJO_URL || 'https://forgejo.heavyduty.builders')
.option('-n, --username <username>', 'account username or email', process.env.STOKE_USERNAME || process.env.FORGEJO_USERNAME)
.option('-p, --password <password>', 'account password', process.env.STOKE_PASSWORD || process.env.FORGEJO_PASSWORD)
.option('--password-file <path>', 'read account password from a file')
.option('-t, --token <token>', 'use an existing personal access token instead of generating one')
.option('--token-file <path>', 'read an existing personal access token from a file')
.option('--token-name <name>', 'name for the generated personal access token', makeTokenName())
.action(async (options) => {
try {
let { url, username, password, passwordFile, token, tokenFile, tokenName } = options;
if (tokenFile) token = readSecretFile(tokenFile, 'token');
if (passwordFile) password = readSecretFile(passwordFile, 'password');
if (!username && !token) {
username = await prompt('Username or email: ');
}
if (!password && !token) {
password = await prompt('Password: ', true);
}
const client = new ForgejoClient(url);
let config = { url };
if (token) {
// Validate the supplied token and resolve the login name.
const tokenClient = new ForgejoClient(url, token);
const me = await tokenClient.get('/user');
config = {
url,
login: me.login,
username: me.username || me.login,
email: me.email,
token,
tokenId: null,
};
console.log(`Authenticated as ${me.login} using provided token.`);
} else {
// Verify username/password and get the canonical login name.
const me = await client.verifyBasicAuth(username, password);
const login = me.login;
const tokenRes = await client.createToken(login, password, tokenName, DEFAULT_TOKEN_SCOPES);
if (!tokenRes.sha1) {
throw new Error('Token generation succeeded but no token value was returned.');
}
config = {
url,
login,
username: me.username || login,
email: me.email,
token: tokenRes.sha1,
tokenId: tokenRes.id,
};
console.log(`Authenticated as ${login}. Token "${tokenRes.name}" created.`);
}
saveConfig(config);
Audit: fix auth/config bugs, add issue/pr create, tests and docs Fixes found during a full audit of the CLI: - auth logout: remote token revocation always failed with 401 because Forgejo only accepts Basic auth on the token endpoints. Logout now asks for (or accepts) the account password, supports --password, --password-file and --local-only, and clearly reports when the token is left active. - Silent password prompt actually echoed the password on a TTY: overriding rl.write does not suppress readline echo. Switched to the callback readline module and mute _writeToOutput instead (the readline/promises interface does not honor that hook). - Global --config flag was silently ignored: config paths were resolved at require time, before the preAction hook set STOKE_CONFIG_FILE. Paths are now resolved lazily on every access. - XDG_CONFIG_HOME handling put the config in $XDG_CONFIG_HOME/.config/stoke; per the XDG spec it now resolves to $XDG_CONFIG_HOME/stoke. - repo create: --auto-init defaulted to true with no way to disable it; added --no-auto-init. - repo import/import-batch: a GitHub token was required even for non-GitHub services (e.g. --service git), making those imports fail without gh/GITHUB_TOKEN. Tokens are now only auto-resolved for the github service; batch imports resolve per entry and memoize. - Branding leftovers: 'Run: forgejo auth login' hint and forgejo-cli/1.0.0 User-Agent now say stoke (UA tracks pkg.version). - Added request timeouts (30s default, 10m for migrations). - --limit and --team-id are validated as integers instead of silently misbehaving on garbage (NaN made -l show all results). New commands (per the repo's every-operation-becomes-a-command design): - stoke issue create (title/body/body-file/assignees) - stoke pr create (head/base/title/body/body-file) Tests and metadata: - New test suite on the built-in node:test runner (25 tests) covering config resolution/persistence, the API client with a mocked fetch, and end-to-end CLI behavior. npm test previously matched no files. - package.json: engines >=22.12.0 (required by commander@15 — the README claimed Node 18), repository, keywords, author; version 1.1.0. - README: corrected Node requirement, documented repo rename (was missing), issue create, pr create, logout options and revocation caveat, --no-auto-init, XDG behavior, import token rules, testing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 19:28:23 +00:00
console.log(`Credentials stored in ${getConfigPath()}`);
} catch (err) {
printErrorAndExit(err);
}
});
auth
.command('logout')
.description('Revoke the stored access token and remove local configuration')
Audit: fix auth/config bugs, add issue/pr create, tests and docs Fixes found during a full audit of the CLI: - auth logout: remote token revocation always failed with 401 because Forgejo only accepts Basic auth on the token endpoints. Logout now asks for (or accepts) the account password, supports --password, --password-file and --local-only, and clearly reports when the token is left active. - Silent password prompt actually echoed the password on a TTY: overriding rl.write does not suppress readline echo. Switched to the callback readline module and mute _writeToOutput instead (the readline/promises interface does not honor that hook). - Global --config flag was silently ignored: config paths were resolved at require time, before the preAction hook set STOKE_CONFIG_FILE. Paths are now resolved lazily on every access. - XDG_CONFIG_HOME handling put the config in $XDG_CONFIG_HOME/.config/stoke; per the XDG spec it now resolves to $XDG_CONFIG_HOME/stoke. - repo create: --auto-init defaulted to true with no way to disable it; added --no-auto-init. - repo import/import-batch: a GitHub token was required even for non-GitHub services (e.g. --service git), making those imports fail without gh/GITHUB_TOKEN. Tokens are now only auto-resolved for the github service; batch imports resolve per entry and memoize. - Branding leftovers: 'Run: forgejo auth login' hint and forgejo-cli/1.0.0 User-Agent now say stoke (UA tracks pkg.version). - Added request timeouts (30s default, 10m for migrations). - --limit and --team-id are validated as integers instead of silently misbehaving on garbage (NaN made -l show all results). New commands (per the repo's every-operation-becomes-a-command design): - stoke issue create (title/body/body-file/assignees) - stoke pr create (head/base/title/body/body-file) Tests and metadata: - New test suite on the built-in node:test runner (25 tests) covering config resolution/persistence, the API client with a mocked fetch, and end-to-end CLI behavior. npm test previously matched no files. - package.json: engines >=22.12.0 (required by commander@15 — the README claimed Node 18), repository, keywords, author; version 1.1.0. - README: corrected Node requirement, documented repo rename (was missing), issue create, pr create, logout options and revocation caveat, --no-auto-init, XDG behavior, import token rules, testing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 19:28:23 +00:00
.option('-p, --password <password>', 'account password, needed to revoke the token remotely', process.env.STOKE_PASSWORD || process.env.FORGEJO_PASSWORD)
.option('--password-file <path>', 'read account password from a file')
.option('--local-only', 'skip remote token revocation and only delete the local config', false)
.action(async (options) => {
try {
const config = loadConfig();
if (!config || !config.token) {
console.log('No active session.');
return;
}
Audit: fix auth/config bugs, add issue/pr create, tests and docs Fixes found during a full audit of the CLI: - auth logout: remote token revocation always failed with 401 because Forgejo only accepts Basic auth on the token endpoints. Logout now asks for (or accepts) the account password, supports --password, --password-file and --local-only, and clearly reports when the token is left active. - Silent password prompt actually echoed the password on a TTY: overriding rl.write does not suppress readline echo. Switched to the callback readline module and mute _writeToOutput instead (the readline/promises interface does not honor that hook). - Global --config flag was silently ignored: config paths were resolved at require time, before the preAction hook set STOKE_CONFIG_FILE. Paths are now resolved lazily on every access. - XDG_CONFIG_HOME handling put the config in $XDG_CONFIG_HOME/.config/stoke; per the XDG spec it now resolves to $XDG_CONFIG_HOME/stoke. - repo create: --auto-init defaulted to true with no way to disable it; added --no-auto-init. - repo import/import-batch: a GitHub token was required even for non-GitHub services (e.g. --service git), making those imports fail without gh/GITHUB_TOKEN. Tokens are now only auto-resolved for the github service; batch imports resolve per entry and memoize. - Branding leftovers: 'Run: forgejo auth login' hint and forgejo-cli/1.0.0 User-Agent now say stoke (UA tracks pkg.version). - Added request timeouts (30s default, 10m for migrations). - --limit and --team-id are validated as integers instead of silently misbehaving on garbage (NaN made -l show all results). New commands (per the repo's every-operation-becomes-a-command design): - stoke issue create (title/body/body-file/assignees) - stoke pr create (head/base/title/body/body-file) Tests and metadata: - New test suite on the built-in node:test runner (25 tests) covering config resolution/persistence, the API client with a mocked fetch, and end-to-end CLI behavior. npm test previously matched no files. - package.json: engines >=22.12.0 (required by commander@15 — the README claimed Node 18), repository, keywords, author; version 1.1.0. - README: corrected Node requirement, documented repo rename (was missing), issue create, pr create, logout options and revocation caveat, --no-auto-init, XDG behavior, import token rules, testing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 19:28:23 +00:00
// Forgejo only accepts Basic auth on the token endpoints, so remote
// revocation needs the account password (a token cannot revoke itself).
if (config.tokenId && !options.localOnly) {
let password = options.password;
if (options.passwordFile) password = readSecretFile(options.passwordFile, 'password');
if (!password && input.isTTY) {
password = await prompt(`Password for ${config.login} (empty to skip revocation): `, true);
}
if (password) {
const client = new ForgejoClient(config.url);
try {
await client.deleteToken(config.username || config.login, password, config.login, config.tokenId);
console.log(`Revoked token ${config.tokenId} on ${config.url}.`);
} catch (err) {
console.error(`Warning: could not revoke remote token: ${err.message}`);
}
} else {
console.log(`Skipping remote revocation (no password provided). Token ${config.tokenId} stays active on ${config.url}; revoke it from the web UI under Settings > Applications.`);
}
}
clearConfig();
console.log('Local credentials removed.');
} catch (err) {
console.error(`Logout failed: ${err.message}`);
process.exit(1);
}
});
auth
.command('status')
.description('Show the current authentication status')
.action(async () => {
try {
const config = loadConfig();
if (!config || !config.token) {
console.log('Not authenticated.');
return;
}
const client = ForgejoClient.fromConfig(config);
const me = await client.get('/user');
console.log('Instance: ', config.url);
console.log('Login: ', me.login);
console.log('Username: ', me.username);
console.log('Email: ', me.email);
Audit: fix auth/config bugs, add issue/pr create, tests and docs Fixes found during a full audit of the CLI: - auth logout: remote token revocation always failed with 401 because Forgejo only accepts Basic auth on the token endpoints. Logout now asks for (or accepts) the account password, supports --password, --password-file and --local-only, and clearly reports when the token is left active. - Silent password prompt actually echoed the password on a TTY: overriding rl.write does not suppress readline echo. Switched to the callback readline module and mute _writeToOutput instead (the readline/promises interface does not honor that hook). - Global --config flag was silently ignored: config paths were resolved at require time, before the preAction hook set STOKE_CONFIG_FILE. Paths are now resolved lazily on every access. - XDG_CONFIG_HOME handling put the config in $XDG_CONFIG_HOME/.config/stoke; per the XDG spec it now resolves to $XDG_CONFIG_HOME/stoke. - repo create: --auto-init defaulted to true with no way to disable it; added --no-auto-init. - repo import/import-batch: a GitHub token was required even for non-GitHub services (e.g. --service git), making those imports fail without gh/GITHUB_TOKEN. Tokens are now only auto-resolved for the github service; batch imports resolve per entry and memoize. - Branding leftovers: 'Run: forgejo auth login' hint and forgejo-cli/1.0.0 User-Agent now say stoke (UA tracks pkg.version). - Added request timeouts (30s default, 10m for migrations). - --limit and --team-id are validated as integers instead of silently misbehaving on garbage (NaN made -l show all results). New commands (per the repo's every-operation-becomes-a-command design): - stoke issue create (title/body/body-file/assignees) - stoke pr create (head/base/title/body/body-file) Tests and metadata: - New test suite on the built-in node:test runner (25 tests) covering config resolution/persistence, the API client with a mocked fetch, and end-to-end CLI behavior. npm test previously matched no files. - package.json: engines >=22.12.0 (required by commander@15 — the README claimed Node 18), repository, keywords, author; version 1.1.0. - README: corrected Node requirement, documented repo rename (was missing), issue create, pr create, logout options and revocation caveat, --no-auto-init, XDG behavior, import token rules, testing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 19:28:23 +00:00
console.log('Token path: ', getConfigPath());
} catch (err) {
console.error(`Status check failed: ${err.message}`);
process.exit(1);
}
});
Audit: fix auth/config bugs, add issue/pr create, tests and docs Fixes found during a full audit of the CLI: - auth logout: remote token revocation always failed with 401 because Forgejo only accepts Basic auth on the token endpoints. Logout now asks for (or accepts) the account password, supports --password, --password-file and --local-only, and clearly reports when the token is left active. - Silent password prompt actually echoed the password on a TTY: overriding rl.write does not suppress readline echo. Switched to the callback readline module and mute _writeToOutput instead (the readline/promises interface does not honor that hook). - Global --config flag was silently ignored: config paths were resolved at require time, before the preAction hook set STOKE_CONFIG_FILE. Paths are now resolved lazily on every access. - XDG_CONFIG_HOME handling put the config in $XDG_CONFIG_HOME/.config/stoke; per the XDG spec it now resolves to $XDG_CONFIG_HOME/stoke. - repo create: --auto-init defaulted to true with no way to disable it; added --no-auto-init. - repo import/import-batch: a GitHub token was required even for non-GitHub services (e.g. --service git), making those imports fail without gh/GITHUB_TOKEN. Tokens are now only auto-resolved for the github service; batch imports resolve per entry and memoize. - Branding leftovers: 'Run: forgejo auth login' hint and forgejo-cli/1.0.0 User-Agent now say stoke (UA tracks pkg.version). - Added request timeouts (30s default, 10m for migrations). - --limit and --team-id are validated as integers instead of silently misbehaving on garbage (NaN made -l show all results). New commands (per the repo's every-operation-becomes-a-command design): - stoke issue create (title/body/body-file/assignees) - stoke pr create (head/base/title/body/body-file) Tests and metadata: - New test suite on the built-in node:test runner (25 tests) covering config resolution/persistence, the API client with a mocked fetch, and end-to-end CLI behavior. npm test previously matched no files. - package.json: engines >=22.12.0 (required by commander@15 — the README claimed Node 18), repository, keywords, author; version 1.1.0. - README: corrected Node requirement, documented repo rename (was missing), issue create, pr create, logout options and revocation caveat, --no-auto-init, XDG behavior, import token rules, testing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 19:28:23 +00:00
// Resolve the auth token to send to the source service of a migration.
// GitHub needs one in practice (rate limits, private repos); for any other
// service a token is optional and only used when explicitly provided.
let cachedGhToken;
function resolveSourceToken(tokenOption, service) {
if (tokenOption) return tokenOption;
Audit: fix auth/config bugs, add issue/pr create, tests and docs Fixes found during a full audit of the CLI: - auth logout: remote token revocation always failed with 401 because Forgejo only accepts Basic auth on the token endpoints. Logout now asks for (or accepts) the account password, supports --password, --password-file and --local-only, and clearly reports when the token is left active. - Silent password prompt actually echoed the password on a TTY: overriding rl.write does not suppress readline echo. Switched to the callback readline module and mute _writeToOutput instead (the readline/promises interface does not honor that hook). - Global --config flag was silently ignored: config paths were resolved at require time, before the preAction hook set STOKE_CONFIG_FILE. Paths are now resolved lazily on every access. - XDG_CONFIG_HOME handling put the config in $XDG_CONFIG_HOME/.config/stoke; per the XDG spec it now resolves to $XDG_CONFIG_HOME/stoke. - repo create: --auto-init defaulted to true with no way to disable it; added --no-auto-init. - repo import/import-batch: a GitHub token was required even for non-GitHub services (e.g. --service git), making those imports fail without gh/GITHUB_TOKEN. Tokens are now only auto-resolved for the github service; batch imports resolve per entry and memoize. - Branding leftovers: 'Run: forgejo auth login' hint and forgejo-cli/1.0.0 User-Agent now say stoke (UA tracks pkg.version). - Added request timeouts (30s default, 10m for migrations). - --limit and --team-id are validated as integers instead of silently misbehaving on garbage (NaN made -l show all results). New commands (per the repo's every-operation-becomes-a-command design): - stoke issue create (title/body/body-file/assignees) - stoke pr create (head/base/title/body/body-file) Tests and metadata: - New test suite on the built-in node:test runner (25 tests) covering config resolution/persistence, the API client with a mocked fetch, and end-to-end CLI behavior. npm test previously matched no files. - package.json: engines >=22.12.0 (required by commander@15 — the README claimed Node 18), repository, keywords, author; version 1.1.0. - README: corrected Node requirement, documented repo rename (was missing), issue create, pr create, logout options and revocation caveat, --no-auto-init, XDG behavior, import token rules, testing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 19:28:23 +00:00
if (service !== 'github') return undefined;
if (process.env.GITHUB_TOKEN) return process.env.GITHUB_TOKEN;
Audit: fix auth/config bugs, add issue/pr create, tests and docs Fixes found during a full audit of the CLI: - auth logout: remote token revocation always failed with 401 because Forgejo only accepts Basic auth on the token endpoints. Logout now asks for (or accepts) the account password, supports --password, --password-file and --local-only, and clearly reports when the token is left active. - Silent password prompt actually echoed the password on a TTY: overriding rl.write does not suppress readline echo. Switched to the callback readline module and mute _writeToOutput instead (the readline/promises interface does not honor that hook). - Global --config flag was silently ignored: config paths were resolved at require time, before the preAction hook set STOKE_CONFIG_FILE. Paths are now resolved lazily on every access. - XDG_CONFIG_HOME handling put the config in $XDG_CONFIG_HOME/.config/stoke; per the XDG spec it now resolves to $XDG_CONFIG_HOME/stoke. - repo create: --auto-init defaulted to true with no way to disable it; added --no-auto-init. - repo import/import-batch: a GitHub token was required even for non-GitHub services (e.g. --service git), making those imports fail without gh/GITHUB_TOKEN. Tokens are now only auto-resolved for the github service; batch imports resolve per entry and memoize. - Branding leftovers: 'Run: forgejo auth login' hint and forgejo-cli/1.0.0 User-Agent now say stoke (UA tracks pkg.version). - Added request timeouts (30s default, 10m for migrations). - --limit and --team-id are validated as integers instead of silently misbehaving on garbage (NaN made -l show all results). New commands (per the repo's every-operation-becomes-a-command design): - stoke issue create (title/body/body-file/assignees) - stoke pr create (head/base/title/body/body-file) Tests and metadata: - New test suite on the built-in node:test runner (25 tests) covering config resolution/persistence, the API client with a mocked fetch, and end-to-end CLI behavior. npm test previously matched no files. - package.json: engines >=22.12.0 (required by commander@15 — the README claimed Node 18), repository, keywords, author; version 1.1.0. - README: corrected Node requirement, documented repo rename (was missing), issue create, pr create, logout options and revocation caveat, --no-auto-init, XDG behavior, import token rules, testing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 19:28:23 +00:00
if (cachedGhToken) return cachedGhToken;
try {
Audit: fix auth/config bugs, add issue/pr create, tests and docs Fixes found during a full audit of the CLI: - auth logout: remote token revocation always failed with 401 because Forgejo only accepts Basic auth on the token endpoints. Logout now asks for (or accepts) the account password, supports --password, --password-file and --local-only, and clearly reports when the token is left active. - Silent password prompt actually echoed the password on a TTY: overriding rl.write does not suppress readline echo. Switched to the callback readline module and mute _writeToOutput instead (the readline/promises interface does not honor that hook). - Global --config flag was silently ignored: config paths were resolved at require time, before the preAction hook set STOKE_CONFIG_FILE. Paths are now resolved lazily on every access. - XDG_CONFIG_HOME handling put the config in $XDG_CONFIG_HOME/.config/stoke; per the XDG spec it now resolves to $XDG_CONFIG_HOME/stoke. - repo create: --auto-init defaulted to true with no way to disable it; added --no-auto-init. - repo import/import-batch: a GitHub token was required even for non-GitHub services (e.g. --service git), making those imports fail without gh/GITHUB_TOKEN. Tokens are now only auto-resolved for the github service; batch imports resolve per entry and memoize. - Branding leftovers: 'Run: forgejo auth login' hint and forgejo-cli/1.0.0 User-Agent now say stoke (UA tracks pkg.version). - Added request timeouts (30s default, 10m for migrations). - --limit and --team-id are validated as integers instead of silently misbehaving on garbage (NaN made -l show all results). New commands (per the repo's every-operation-becomes-a-command design): - stoke issue create (title/body/body-file/assignees) - stoke pr create (head/base/title/body/body-file) Tests and metadata: - New test suite on the built-in node:test runner (25 tests) covering config resolution/persistence, the API client with a mocked fetch, and end-to-end CLI behavior. npm test previously matched no files. - package.json: engines >=22.12.0 (required by commander@15 — the README claimed Node 18), repository, keywords, author; version 1.1.0. - README: corrected Node requirement, documented repo rename (was missing), issue create, pr create, logout options and revocation caveat, --no-auto-init, XDG behavior, import token rules, testing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 19:28:23 +00:00
cachedGhToken = execSync('gh auth token', { encoding: 'utf8', timeout: 10000 }).trim();
return cachedGhToken;
} catch {
Audit: fix auth/config bugs, add issue/pr create, tests and docs Fixes found during a full audit of the CLI: - auth logout: remote token revocation always failed with 401 because Forgejo only accepts Basic auth on the token endpoints. Logout now asks for (or accepts) the account password, supports --password, --password-file and --local-only, and clearly reports when the token is left active. - Silent password prompt actually echoed the password on a TTY: overriding rl.write does not suppress readline echo. Switched to the callback readline module and mute _writeToOutput instead (the readline/promises interface does not honor that hook). - Global --config flag was silently ignored: config paths were resolved at require time, before the preAction hook set STOKE_CONFIG_FILE. Paths are now resolved lazily on every access. - XDG_CONFIG_HOME handling put the config in $XDG_CONFIG_HOME/.config/stoke; per the XDG spec it now resolves to $XDG_CONFIG_HOME/stoke. - repo create: --auto-init defaulted to true with no way to disable it; added --no-auto-init. - repo import/import-batch: a GitHub token was required even for non-GitHub services (e.g. --service git), making those imports fail without gh/GITHUB_TOKEN. Tokens are now only auto-resolved for the github service; batch imports resolve per entry and memoize. - Branding leftovers: 'Run: forgejo auth login' hint and forgejo-cli/1.0.0 User-Agent now say stoke (UA tracks pkg.version). - Added request timeouts (30s default, 10m for migrations). - --limit and --team-id are validated as integers instead of silently misbehaving on garbage (NaN made -l show all results). New commands (per the repo's every-operation-becomes-a-command design): - stoke issue create (title/body/body-file/assignees) - stoke pr create (head/base/title/body/body-file) Tests and metadata: - New test suite on the built-in node:test runner (25 tests) covering config resolution/persistence, the API client with a mocked fetch, and end-to-end CLI behavior. npm test previously matched no files. - package.json: engines >=22.12.0 (required by commander@15 — the README claimed Node 18), repository, keywords, author; version 1.1.0. - README: corrected Node requirement, documented repo rename (was missing), issue create, pr create, logout options and revocation caveat, --no-auto-init, XDG behavior, import token rules, testing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 19:28:23 +00:00
throw new Error("No GitHub token found. Set --github-token, GITHUB_TOKEN, or ensure 'gh auth token' works.");
}
}
function normalizeBool(value, defaultValue) {
return value === undefined ? defaultValue : Boolean(value);
}
const repo = program
.command('repo')
.description('Manage repositories');
repo
.command('list')
.description('List repositories for the authenticated user')
Audit: fix auth/config bugs, add issue/pr create, tests and docs Fixes found during a full audit of the CLI: - auth logout: remote token revocation always failed with 401 because Forgejo only accepts Basic auth on the token endpoints. Logout now asks for (or accepts) the account password, supports --password, --password-file and --local-only, and clearly reports when the token is left active. - Silent password prompt actually echoed the password on a TTY: overriding rl.write does not suppress readline echo. Switched to the callback readline module and mute _writeToOutput instead (the readline/promises interface does not honor that hook). - Global --config flag was silently ignored: config paths were resolved at require time, before the preAction hook set STOKE_CONFIG_FILE. Paths are now resolved lazily on every access. - XDG_CONFIG_HOME handling put the config in $XDG_CONFIG_HOME/.config/stoke; per the XDG spec it now resolves to $XDG_CONFIG_HOME/stoke. - repo create: --auto-init defaulted to true with no way to disable it; added --no-auto-init. - repo import/import-batch: a GitHub token was required even for non-GitHub services (e.g. --service git), making those imports fail without gh/GITHUB_TOKEN. Tokens are now only auto-resolved for the github service; batch imports resolve per entry and memoize. - Branding leftovers: 'Run: forgejo auth login' hint and forgejo-cli/1.0.0 User-Agent now say stoke (UA tracks pkg.version). - Added request timeouts (30s default, 10m for migrations). - --limit and --team-id are validated as integers instead of silently misbehaving on garbage (NaN made -l show all results). New commands (per the repo's every-operation-becomes-a-command design): - stoke issue create (title/body/body-file/assignees) - stoke pr create (head/base/title/body/body-file) Tests and metadata: - New test suite on the built-in node:test runner (25 tests) covering config resolution/persistence, the API client with a mocked fetch, and end-to-end CLI behavior. npm test previously matched no files. - package.json: engines >=22.12.0 (required by commander@15 — the README claimed Node 18), repository, keywords, author; version 1.1.0. - README: corrected Node requirement, documented repo rename (was missing), issue create, pr create, logout options and revocation caveat, --no-auto-init, XDG behavior, import token rules, testing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 19:28:23 +00:00
.option('-l, --limit <number>', 'maximum repositories to return (0 for all)', parseLimit, 50)
.action(async (options) => {
try {
const config = loadConfig();
const client = ForgejoClient.fromConfig(config);
const repos = await client.listRepos();
Audit: fix auth/config bugs, add issue/pr create, tests and docs Fixes found during a full audit of the CLI: - auth logout: remote token revocation always failed with 401 because Forgejo only accepts Basic auth on the token endpoints. Logout now asks for (or accepts) the account password, supports --password, --password-file and --local-only, and clearly reports when the token is left active. - Silent password prompt actually echoed the password on a TTY: overriding rl.write does not suppress readline echo. Switched to the callback readline module and mute _writeToOutput instead (the readline/promises interface does not honor that hook). - Global --config flag was silently ignored: config paths were resolved at require time, before the preAction hook set STOKE_CONFIG_FILE. Paths are now resolved lazily on every access. - XDG_CONFIG_HOME handling put the config in $XDG_CONFIG_HOME/.config/stoke; per the XDG spec it now resolves to $XDG_CONFIG_HOME/stoke. - repo create: --auto-init defaulted to true with no way to disable it; added --no-auto-init. - repo import/import-batch: a GitHub token was required even for non-GitHub services (e.g. --service git), making those imports fail without gh/GITHUB_TOKEN. Tokens are now only auto-resolved for the github service; batch imports resolve per entry and memoize. - Branding leftovers: 'Run: forgejo auth login' hint and forgejo-cli/1.0.0 User-Agent now say stoke (UA tracks pkg.version). - Added request timeouts (30s default, 10m for migrations). - --limit and --team-id are validated as integers instead of silently misbehaving on garbage (NaN made -l show all results). New commands (per the repo's every-operation-becomes-a-command design): - stoke issue create (title/body/body-file/assignees) - stoke pr create (head/base/title/body/body-file) Tests and metadata: - New test suite on the built-in node:test runner (25 tests) covering config resolution/persistence, the API client with a mocked fetch, and end-to-end CLI behavior. npm test previously matched no files. - package.json: engines >=22.12.0 (required by commander@15 — the README claimed Node 18), repository, keywords, author; version 1.1.0. - README: corrected Node requirement, documented repo rename (was missing), issue create, pr create, logout options and revocation caveat, --no-auto-init, XDG behavior, import token rules, testing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 19:28:23 +00:00
const display = options.limit > 0 ? repos.slice(0, options.limit) : repos;
if (!display.length) {
console.log('No repositories found.');
return;
}
for (const r of display) {
const vis = r.private ? 'private' : 'public';
console.log(`${r.full_name} [${vis}] ${r.html_url}`);
}
if (repos.length > display.length) {
console.log(`...and ${repos.length - display.length} more (use -l 0 for all).`);
}
} catch (err) {
console.error(`Failed to list repositories: ${err.message}`);
process.exit(1);
}
});
repo
.command('create')
.description('Create a new repository for the authenticated user')
.requiredOption('--name <name>', 'repository name')
.option('-d, --description <description>', 'repository description', '')
.option('--private', 'make the repository private', false)
.option('--public', 'make the repository public')
Audit: fix auth/config bugs, add issue/pr create, tests and docs Fixes found during a full audit of the CLI: - auth logout: remote token revocation always failed with 401 because Forgejo only accepts Basic auth on the token endpoints. Logout now asks for (or accepts) the account password, supports --password, --password-file and --local-only, and clearly reports when the token is left active. - Silent password prompt actually echoed the password on a TTY: overriding rl.write does not suppress readline echo. Switched to the callback readline module and mute _writeToOutput instead (the readline/promises interface does not honor that hook). - Global --config flag was silently ignored: config paths were resolved at require time, before the preAction hook set STOKE_CONFIG_FILE. Paths are now resolved lazily on every access. - XDG_CONFIG_HOME handling put the config in $XDG_CONFIG_HOME/.config/stoke; per the XDG spec it now resolves to $XDG_CONFIG_HOME/stoke. - repo create: --auto-init defaulted to true with no way to disable it; added --no-auto-init. - repo import/import-batch: a GitHub token was required even for non-GitHub services (e.g. --service git), making those imports fail without gh/GITHUB_TOKEN. Tokens are now only auto-resolved for the github service; batch imports resolve per entry and memoize. - Branding leftovers: 'Run: forgejo auth login' hint and forgejo-cli/1.0.0 User-Agent now say stoke (UA tracks pkg.version). - Added request timeouts (30s default, 10m for migrations). - --limit and --team-id are validated as integers instead of silently misbehaving on garbage (NaN made -l show all results). New commands (per the repo's every-operation-becomes-a-command design): - stoke issue create (title/body/body-file/assignees) - stoke pr create (head/base/title/body/body-file) Tests and metadata: - New test suite on the built-in node:test runner (25 tests) covering config resolution/persistence, the API client with a mocked fetch, and end-to-end CLI behavior. npm test previously matched no files. - package.json: engines >=22.12.0 (required by commander@15 — the README claimed Node 18), repository, keywords, author; version 1.1.0. - README: corrected Node requirement, documented repo rename (was missing), issue create, pr create, logout options and revocation caveat, --no-auto-init, XDG behavior, import token rules, testing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 19:28:23 +00:00
.option('--auto-init', 'initialize with a README (default)')
.option('--no-auto-init', 'create an empty repository without a README')
.option('--default-branch <branch>', 'default branch name', 'main')
.action(async (options) => {
try {
const config = loadConfig();
const client = ForgejoClient.fromConfig(config);
const isPrivate = options.public ? false : options.private;
const payload = {
name: options.name,
description: options.description,
private: isPrivate,
Audit: fix auth/config bugs, add issue/pr create, tests and docs Fixes found during a full audit of the CLI: - auth logout: remote token revocation always failed with 401 because Forgejo only accepts Basic auth on the token endpoints. Logout now asks for (or accepts) the account password, supports --password, --password-file and --local-only, and clearly reports when the token is left active. - Silent password prompt actually echoed the password on a TTY: overriding rl.write does not suppress readline echo. Switched to the callback readline module and mute _writeToOutput instead (the readline/promises interface does not honor that hook). - Global --config flag was silently ignored: config paths were resolved at require time, before the preAction hook set STOKE_CONFIG_FILE. Paths are now resolved lazily on every access. - XDG_CONFIG_HOME handling put the config in $XDG_CONFIG_HOME/.config/stoke; per the XDG spec it now resolves to $XDG_CONFIG_HOME/stoke. - repo create: --auto-init defaulted to true with no way to disable it; added --no-auto-init. - repo import/import-batch: a GitHub token was required even for non-GitHub services (e.g. --service git), making those imports fail without gh/GITHUB_TOKEN. Tokens are now only auto-resolved for the github service; batch imports resolve per entry and memoize. - Branding leftovers: 'Run: forgejo auth login' hint and forgejo-cli/1.0.0 User-Agent now say stoke (UA tracks pkg.version). - Added request timeouts (30s default, 10m for migrations). - --limit and --team-id are validated as integers instead of silently misbehaving on garbage (NaN made -l show all results). New commands (per the repo's every-operation-becomes-a-command design): - stoke issue create (title/body/body-file/assignees) - stoke pr create (head/base/title/body/body-file) Tests and metadata: - New test suite on the built-in node:test runner (25 tests) covering config resolution/persistence, the API client with a mocked fetch, and end-to-end CLI behavior. npm test previously matched no files. - package.json: engines >=22.12.0 (required by commander@15 — the README claimed Node 18), repository, keywords, author; version 1.1.0. - README: corrected Node requirement, documented repo rename (was missing), issue create, pr create, logout options and revocation caveat, --no-auto-init, XDG behavior, import token rules, testing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 19:28:23 +00:00
auto_init: normalizeBool(options.autoInit, true),
default_branch: options.defaultBranch,
};
const result = await client.createRepo(payload);
console.log(`Repository created: ${result.full_name}`);
console.log(`URL: ${result.html_url}`);
console.log(`Clone (SSH): ${result.ssh_url}`);
console.log(`Clone (HTTP): ${result.clone_url}`);
} catch (err) {
console.error(`Repository creation failed: ${err.message}`);
if (err.status) console.error(`HTTP status: ${err.status}`);
process.exit(1);
}
});
repo
.command('import')
.description('Import a remote repository (GitHub, GitLab, plain git, etc.)')
.requiredOption('--from <clone-addr>', 'source clone URL, e.g. https://github.com/owner/repo.git')
.requiredOption('--name <name>', 'name for the imported repository')
.option('--service <service>', 'source service type', 'github')
.option('--owner <owner>', 'Forgejo owner for the imported repository')
.option('-d, --description <description>', 'repository description')
.option('--private', 'make the repository private', false)
.option('--public', 'make the repository public')
.option('--issues', 'migrate issues', true)
.option('--no-issues', 'skip migrating issues')
.option('--labels', 'migrate labels', true)
.option('--no-labels', 'skip migrating labels')
.option('--milestones', 'migrate milestones', true)
.option('--no-milestones', 'skip migrating milestones')
.option('--pull-requests', 'migrate pull requests', true)
.option('--no-pull-requests', 'skip migrating pull requests')
.option('--releases', 'migrate releases', true)
.option('--no-releases', 'skip migrating releases')
.option('--wiki', 'migrate wiki', true)
.option('--no-wiki', 'skip migrating wiki')
.option('--lfs', 'migrate LFS objects', false)
Audit: fix auth/config bugs, add issue/pr create, tests and docs Fixes found during a full audit of the CLI: - auth logout: remote token revocation always failed with 401 because Forgejo only accepts Basic auth on the token endpoints. Logout now asks for (or accepts) the account password, supports --password, --password-file and --local-only, and clearly reports when the token is left active. - Silent password prompt actually echoed the password on a TTY: overriding rl.write does not suppress readline echo. Switched to the callback readline module and mute _writeToOutput instead (the readline/promises interface does not honor that hook). - Global --config flag was silently ignored: config paths were resolved at require time, before the preAction hook set STOKE_CONFIG_FILE. Paths are now resolved lazily on every access. - XDG_CONFIG_HOME handling put the config in $XDG_CONFIG_HOME/.config/stoke; per the XDG spec it now resolves to $XDG_CONFIG_HOME/stoke. - repo create: --auto-init defaulted to true with no way to disable it; added --no-auto-init. - repo import/import-batch: a GitHub token was required even for non-GitHub services (e.g. --service git), making those imports fail without gh/GITHUB_TOKEN. Tokens are now only auto-resolved for the github service; batch imports resolve per entry and memoize. - Branding leftovers: 'Run: forgejo auth login' hint and forgejo-cli/1.0.0 User-Agent now say stoke (UA tracks pkg.version). - Added request timeouts (30s default, 10m for migrations). - --limit and --team-id are validated as integers instead of silently misbehaving on garbage (NaN made -l show all results). New commands (per the repo's every-operation-becomes-a-command design): - stoke issue create (title/body/body-file/assignees) - stoke pr create (head/base/title/body/body-file) Tests and metadata: - New test suite on the built-in node:test runner (25 tests) covering config resolution/persistence, the API client with a mocked fetch, and end-to-end CLI behavior. npm test previously matched no files. - package.json: engines >=22.12.0 (required by commander@15 — the README claimed Node 18), repository, keywords, author; version 1.1.0. - README: corrected Node requirement, documented repo rename (was missing), issue create, pr create, logout options and revocation caveat, --no-auto-init, XDG behavior, import token rules, testing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 19:28:23 +00:00
.option('--github-token <token>', 'source service token (for GitHub defaults to GITHUB_TOKEN or "gh auth token")')
.action(async (options) => {
try {
const config = loadConfig();
const client = ForgejoClient.fromConfig(config);
const isPrivate = options.public ? false : options.private;
Audit: fix auth/config bugs, add issue/pr create, tests and docs Fixes found during a full audit of the CLI: - auth logout: remote token revocation always failed with 401 because Forgejo only accepts Basic auth on the token endpoints. Logout now asks for (or accepts) the account password, supports --password, --password-file and --local-only, and clearly reports when the token is left active. - Silent password prompt actually echoed the password on a TTY: overriding rl.write does not suppress readline echo. Switched to the callback readline module and mute _writeToOutput instead (the readline/promises interface does not honor that hook). - Global --config flag was silently ignored: config paths were resolved at require time, before the preAction hook set STOKE_CONFIG_FILE. Paths are now resolved lazily on every access. - XDG_CONFIG_HOME handling put the config in $XDG_CONFIG_HOME/.config/stoke; per the XDG spec it now resolves to $XDG_CONFIG_HOME/stoke. - repo create: --auto-init defaulted to true with no way to disable it; added --no-auto-init. - repo import/import-batch: a GitHub token was required even for non-GitHub services (e.g. --service git), making those imports fail without gh/GITHUB_TOKEN. Tokens are now only auto-resolved for the github service; batch imports resolve per entry and memoize. - Branding leftovers: 'Run: forgejo auth login' hint and forgejo-cli/1.0.0 User-Agent now say stoke (UA tracks pkg.version). - Added request timeouts (30s default, 10m for migrations). - --limit and --team-id are validated as integers instead of silently misbehaving on garbage (NaN made -l show all results). New commands (per the repo's every-operation-becomes-a-command design): - stoke issue create (title/body/body-file/assignees) - stoke pr create (head/base/title/body/body-file) Tests and metadata: - New test suite on the built-in node:test runner (25 tests) covering config resolution/persistence, the API client with a mocked fetch, and end-to-end CLI behavior. npm test previously matched no files. - package.json: engines >=22.12.0 (required by commander@15 — the README claimed Node 18), repository, keywords, author; version 1.1.0. - README: corrected Node requirement, documented repo rename (was missing), issue create, pr create, logout options and revocation caveat, --no-auto-init, XDG behavior, import token rules, testing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 19:28:23 +00:00
const token = resolveSourceToken(options.githubToken, options.service);
const payload = {
clone_addr: options.from,
repo_name: options.name,
repo_owner: options.owner || config.login,
service: options.service,
description: options.description || undefined,
private: isPrivate,
issues: normalizeBool(options.issues, true),
labels: normalizeBool(options.labels, true),
milestones: normalizeBool(options.milestones, true),
pull_requests: normalizeBool(options.pullRequests, true),
releases: normalizeBool(options.releases, true),
wiki: normalizeBool(options.wiki, true),
lfs: normalizeBool(options.lfs, false),
auth_token: token,
};
// Remove undefined fields
Object.keys(payload).forEach((key) => {
if (payload[key] === undefined) delete payload[key];
});
const result = await client.migrateRepo(payload);
console.log(`Repository imported: ${result.full_name}`);
console.log(`URL: ${result.html_url}`);
console.log(`Clone (SSH): ${result.ssh_url}`);
console.log(`Clone (HTTP): ${result.clone_url}`);
console.log(`Empty: ${result.empty}`);
} catch (err) {
console.error(`Repository import failed: ${err.message}`);
if (err.status) console.error(`HTTP status: ${err.status}`);
process.exit(1);
}
});
2026-07-22 15:16:42 +00:00
repo
.command('rename')
.description('Rename a repository')
.requiredOption('-o, --owner <owner>', 'repository owner')
.requiredOption('-r, --repo <repo>', 'current repository name')
.requiredOption('--name <new-name>', 'new repository name')
.action(async (options) => {
try {
const config = loadConfig();
const client = ForgejoClient.fromConfig(config);
const result = await client.renameRepo(options.owner, options.repo, options.name);
console.log(`Repository renamed to ${result.full_name}`);
console.log(`URL: ${result.html_url}`);
} catch (err) {
console.error(`Repository rename failed: ${err.message}`);
if (err.status) console.error(`HTTP status: ${err.status}`);
process.exit(1);
}
});
repo
.command('import-batch')
.description('Import multiple repositories from a JSON manifest')
.requiredOption('-f, --file <path>', 'path to JSON manifest')
.option('--dry-run', 'print the manifest without importing', false)
.action(async (options) => {
try {
const config = loadConfig();
const client = ForgejoClient.fromConfig(config);
const raw = fs.readFileSync(options.file, 'utf8');
const manifest = JSON.parse(raw);
if (!Array.isArray(manifest)) {
throw new Error('Manifest must be a JSON array');
}
if (options.dryRun) {
console.log(JSON.stringify(manifest, null, 2));
return;
}
const results = [];
for (const item of manifest) {
const name = item.name || item.repo_name;
const from = item.from || item.clone_addr;
if (!name || !from) {
console.error(`Skipping invalid manifest entry: ${JSON.stringify(item)}`);
continue;
}
Audit: fix auth/config bugs, add issue/pr create, tests and docs Fixes found during a full audit of the CLI: - auth logout: remote token revocation always failed with 401 because Forgejo only accepts Basic auth on the token endpoints. Logout now asks for (or accepts) the account password, supports --password, --password-file and --local-only, and clearly reports when the token is left active. - Silent password prompt actually echoed the password on a TTY: overriding rl.write does not suppress readline echo. Switched to the callback readline module and mute _writeToOutput instead (the readline/promises interface does not honor that hook). - Global --config flag was silently ignored: config paths were resolved at require time, before the preAction hook set STOKE_CONFIG_FILE. Paths are now resolved lazily on every access. - XDG_CONFIG_HOME handling put the config in $XDG_CONFIG_HOME/.config/stoke; per the XDG spec it now resolves to $XDG_CONFIG_HOME/stoke. - repo create: --auto-init defaulted to true with no way to disable it; added --no-auto-init. - repo import/import-batch: a GitHub token was required even for non-GitHub services (e.g. --service git), making those imports fail without gh/GITHUB_TOKEN. Tokens are now only auto-resolved for the github service; batch imports resolve per entry and memoize. - Branding leftovers: 'Run: forgejo auth login' hint and forgejo-cli/1.0.0 User-Agent now say stoke (UA tracks pkg.version). - Added request timeouts (30s default, 10m for migrations). - --limit and --team-id are validated as integers instead of silently misbehaving on garbage (NaN made -l show all results). New commands (per the repo's every-operation-becomes-a-command design): - stoke issue create (title/body/body-file/assignees) - stoke pr create (head/base/title/body/body-file) Tests and metadata: - New test suite on the built-in node:test runner (25 tests) covering config resolution/persistence, the API client with a mocked fetch, and end-to-end CLI behavior. npm test previously matched no files. - package.json: engines >=22.12.0 (required by commander@15 — the README claimed Node 18), repository, keywords, author; version 1.1.0. - README: corrected Node requirement, documented repo rename (was missing), issue create, pr create, logout options and revocation caveat, --no-auto-init, XDG behavior, import token rules, testing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 19:28:23 +00:00
const service = item.service || 'github';
const isPrivate = item.public ? false : Boolean(item.private);
const payload = {
clone_addr: from,
repo_name: name,
repo_owner: item.owner || item.repo_owner || config.login,
Audit: fix auth/config bugs, add issue/pr create, tests and docs Fixes found during a full audit of the CLI: - auth logout: remote token revocation always failed with 401 because Forgejo only accepts Basic auth on the token endpoints. Logout now asks for (or accepts) the account password, supports --password, --password-file and --local-only, and clearly reports when the token is left active. - Silent password prompt actually echoed the password on a TTY: overriding rl.write does not suppress readline echo. Switched to the callback readline module and mute _writeToOutput instead (the readline/promises interface does not honor that hook). - Global --config flag was silently ignored: config paths were resolved at require time, before the preAction hook set STOKE_CONFIG_FILE. Paths are now resolved lazily on every access. - XDG_CONFIG_HOME handling put the config in $XDG_CONFIG_HOME/.config/stoke; per the XDG spec it now resolves to $XDG_CONFIG_HOME/stoke. - repo create: --auto-init defaulted to true with no way to disable it; added --no-auto-init. - repo import/import-batch: a GitHub token was required even for non-GitHub services (e.g. --service git), making those imports fail without gh/GITHUB_TOKEN. Tokens are now only auto-resolved for the github service; batch imports resolve per entry and memoize. - Branding leftovers: 'Run: forgejo auth login' hint and forgejo-cli/1.0.0 User-Agent now say stoke (UA tracks pkg.version). - Added request timeouts (30s default, 10m for migrations). - --limit and --team-id are validated as integers instead of silently misbehaving on garbage (NaN made -l show all results). New commands (per the repo's every-operation-becomes-a-command design): - stoke issue create (title/body/body-file/assignees) - stoke pr create (head/base/title/body/body-file) Tests and metadata: - New test suite on the built-in node:test runner (25 tests) covering config resolution/persistence, the API client with a mocked fetch, and end-to-end CLI behavior. npm test previously matched no files. - package.json: engines >=22.12.0 (required by commander@15 — the README claimed Node 18), repository, keywords, author; version 1.1.0. - README: corrected Node requirement, documented repo rename (was missing), issue create, pr create, logout options and revocation caveat, --no-auto-init, XDG behavior, import token rules, testing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 19:28:23 +00:00
service,
description: item.description || undefined,
private: isPrivate,
issues: normalizeBool(item.issues, true),
labels: normalizeBool(item.labels, true),
milestones: normalizeBool(item.milestones, true),
pull_requests: normalizeBool(item.pull_requests, true),
releases: normalizeBool(item.releases, true),
wiki: normalizeBool(item.wiki, true),
lfs: normalizeBool(item.lfs, false),
Audit: fix auth/config bugs, add issue/pr create, tests and docs Fixes found during a full audit of the CLI: - auth logout: remote token revocation always failed with 401 because Forgejo only accepts Basic auth on the token endpoints. Logout now asks for (or accepts) the account password, supports --password, --password-file and --local-only, and clearly reports when the token is left active. - Silent password prompt actually echoed the password on a TTY: overriding rl.write does not suppress readline echo. Switched to the callback readline module and mute _writeToOutput instead (the readline/promises interface does not honor that hook). - Global --config flag was silently ignored: config paths were resolved at require time, before the preAction hook set STOKE_CONFIG_FILE. Paths are now resolved lazily on every access. - XDG_CONFIG_HOME handling put the config in $XDG_CONFIG_HOME/.config/stoke; per the XDG spec it now resolves to $XDG_CONFIG_HOME/stoke. - repo create: --auto-init defaulted to true with no way to disable it; added --no-auto-init. - repo import/import-batch: a GitHub token was required even for non-GitHub services (e.g. --service git), making those imports fail without gh/GITHUB_TOKEN. Tokens are now only auto-resolved for the github service; batch imports resolve per entry and memoize. - Branding leftovers: 'Run: forgejo auth login' hint and forgejo-cli/1.0.0 User-Agent now say stoke (UA tracks pkg.version). - Added request timeouts (30s default, 10m for migrations). - --limit and --team-id are validated as integers instead of silently misbehaving on garbage (NaN made -l show all results). New commands (per the repo's every-operation-becomes-a-command design): - stoke issue create (title/body/body-file/assignees) - stoke pr create (head/base/title/body/body-file) Tests and metadata: - New test suite on the built-in node:test runner (25 tests) covering config resolution/persistence, the API client with a mocked fetch, and end-to-end CLI behavior. npm test previously matched no files. - package.json: engines >=22.12.0 (required by commander@15 — the README claimed Node 18), repository, keywords, author; version 1.1.0. - README: corrected Node requirement, documented repo rename (was missing), issue create, pr create, logout options and revocation caveat, --no-auto-init, XDG behavior, import token rules, testing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 19:28:23 +00:00
auth_token: resolveSourceToken(item.github_token, service),
};
Object.keys(payload).forEach((key) => {
if (payload[key] === undefined) delete payload[key];
});
try {
const result = await client.migrateRepo(payload);
console.log(`Imported: ${result.full_name} -> ${result.html_url}`);
results.push({ name, status: 'ok', url: result.html_url });
} catch (err) {
console.error(`Failed to import ${name}: ${err.message}`);
results.push({ name, status: 'failed', error: err.message });
}
}
const ok = results.filter((r) => r.status === 'ok').length;
console.log(`\nBatch complete: ${ok}/${results.length} imported.`);
if (ok < results.length) process.exit(1);
} catch (err) {
console.error(`Batch import failed: ${err.message}`);
process.exit(1);
}
});
repo
.command('transfer')
.description('Transfer a repository to a new owner (user or organization)')
.requiredOption('-o, --owner <owner>', 'current repository owner')
.requiredOption('-r, --repo <repo>', 'repository name')
.requiredOption('--to <new-owner>', 'new owner (username or organization name)')
.action(async (options) => {
try {
const config = loadConfig();
const client = ForgejoClient.fromConfig(config);
const result = await client.transferRepo(options.owner, options.repo, options.to);
console.log(`Repository transferred: ${result.full_name}`);
console.log(`URL: ${result.html_url}`);
} catch (err) {
console.error(`Repository transfer failed: ${err.message}`);
if (err.status) console.error(`HTTP status: ${err.status}`);
process.exit(1);
}
});
const issue = program
.command('issue')
.description('Manage issues');
issue
.command('list')
.description('List issues in a repository')
.requiredOption('-o, --owner <owner>', 'repository owner')
.requiredOption('-r, --repo <repo>', 'repository name')
.option('-s, --state <state>', 'issue state: open, closed, all', 'open')
.option('-t, --type <type>', 'issue type filter: issues, pulls', 'issues')
Audit: fix auth/config bugs, add issue/pr create, tests and docs Fixes found during a full audit of the CLI: - auth logout: remote token revocation always failed with 401 because Forgejo only accepts Basic auth on the token endpoints. Logout now asks for (or accepts) the account password, supports --password, --password-file and --local-only, and clearly reports when the token is left active. - Silent password prompt actually echoed the password on a TTY: overriding rl.write does not suppress readline echo. Switched to the callback readline module and mute _writeToOutput instead (the readline/promises interface does not honor that hook). - Global --config flag was silently ignored: config paths were resolved at require time, before the preAction hook set STOKE_CONFIG_FILE. Paths are now resolved lazily on every access. - XDG_CONFIG_HOME handling put the config in $XDG_CONFIG_HOME/.config/stoke; per the XDG spec it now resolves to $XDG_CONFIG_HOME/stoke. - repo create: --auto-init defaulted to true with no way to disable it; added --no-auto-init. - repo import/import-batch: a GitHub token was required even for non-GitHub services (e.g. --service git), making those imports fail without gh/GITHUB_TOKEN. Tokens are now only auto-resolved for the github service; batch imports resolve per entry and memoize. - Branding leftovers: 'Run: forgejo auth login' hint and forgejo-cli/1.0.0 User-Agent now say stoke (UA tracks pkg.version). - Added request timeouts (30s default, 10m for migrations). - --limit and --team-id are validated as integers instead of silently misbehaving on garbage (NaN made -l show all results). New commands (per the repo's every-operation-becomes-a-command design): - stoke issue create (title/body/body-file/assignees) - stoke pr create (head/base/title/body/body-file) Tests and metadata: - New test suite on the built-in node:test runner (25 tests) covering config resolution/persistence, the API client with a mocked fetch, and end-to-end CLI behavior. npm test previously matched no files. - package.json: engines >=22.12.0 (required by commander@15 — the README claimed Node 18), repository, keywords, author; version 1.1.0. - README: corrected Node requirement, documented repo rename (was missing), issue create, pr create, logout options and revocation caveat, --no-auto-init, XDG behavior, import token rules, testing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 19:28:23 +00:00
.option('-l, --limit <number>', 'maximum issues to return (0 for all)', parseLimit, 50)
.action(async (options) => {
try {
const config = loadConfig();
const client = ForgejoClient.fromConfig(config);
const issues = await client.listIssues(options.owner, options.repo, {
state: options.state,
type: options.type,
});
Audit: fix auth/config bugs, add issue/pr create, tests and docs Fixes found during a full audit of the CLI: - auth logout: remote token revocation always failed with 401 because Forgejo only accepts Basic auth on the token endpoints. Logout now asks for (or accepts) the account password, supports --password, --password-file and --local-only, and clearly reports when the token is left active. - Silent password prompt actually echoed the password on a TTY: overriding rl.write does not suppress readline echo. Switched to the callback readline module and mute _writeToOutput instead (the readline/promises interface does not honor that hook). - Global --config flag was silently ignored: config paths were resolved at require time, before the preAction hook set STOKE_CONFIG_FILE. Paths are now resolved lazily on every access. - XDG_CONFIG_HOME handling put the config in $XDG_CONFIG_HOME/.config/stoke; per the XDG spec it now resolves to $XDG_CONFIG_HOME/stoke. - repo create: --auto-init defaulted to true with no way to disable it; added --no-auto-init. - repo import/import-batch: a GitHub token was required even for non-GitHub services (e.g. --service git), making those imports fail without gh/GITHUB_TOKEN. Tokens are now only auto-resolved for the github service; batch imports resolve per entry and memoize. - Branding leftovers: 'Run: forgejo auth login' hint and forgejo-cli/1.0.0 User-Agent now say stoke (UA tracks pkg.version). - Added request timeouts (30s default, 10m for migrations). - --limit and --team-id are validated as integers instead of silently misbehaving on garbage (NaN made -l show all results). New commands (per the repo's every-operation-becomes-a-command design): - stoke issue create (title/body/body-file/assignees) - stoke pr create (head/base/title/body/body-file) Tests and metadata: - New test suite on the built-in node:test runner (25 tests) covering config resolution/persistence, the API client with a mocked fetch, and end-to-end CLI behavior. npm test previously matched no files. - package.json: engines >=22.12.0 (required by commander@15 — the README claimed Node 18), repository, keywords, author; version 1.1.0. - README: corrected Node requirement, documented repo rename (was missing), issue create, pr create, logout options and revocation caveat, --no-auto-init, XDG behavior, import token rules, testing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 19:28:23 +00:00
const display = options.limit > 0 ? issues.slice(0, options.limit) : issues;
if (!display.length) {
console.log('No issues found.');
return;
}
for (const i of display) {
console.log(`#${i.number} [${i.state}] ${i.title}`);
}
if (issues.length > display.length) {
console.log(`...and ${issues.length - display.length} more (use -l 0 for all).`);
}
} catch (err) {
console.error(`Failed to list issues: ${err.message}`);
process.exit(1);
}
});
Audit: fix auth/config bugs, add issue/pr create, tests and docs Fixes found during a full audit of the CLI: - auth logout: remote token revocation always failed with 401 because Forgejo only accepts Basic auth on the token endpoints. Logout now asks for (or accepts) the account password, supports --password, --password-file and --local-only, and clearly reports when the token is left active. - Silent password prompt actually echoed the password on a TTY: overriding rl.write does not suppress readline echo. Switched to the callback readline module and mute _writeToOutput instead (the readline/promises interface does not honor that hook). - Global --config flag was silently ignored: config paths were resolved at require time, before the preAction hook set STOKE_CONFIG_FILE. Paths are now resolved lazily on every access. - XDG_CONFIG_HOME handling put the config in $XDG_CONFIG_HOME/.config/stoke; per the XDG spec it now resolves to $XDG_CONFIG_HOME/stoke. - repo create: --auto-init defaulted to true with no way to disable it; added --no-auto-init. - repo import/import-batch: a GitHub token was required even for non-GitHub services (e.g. --service git), making those imports fail without gh/GITHUB_TOKEN. Tokens are now only auto-resolved for the github service; batch imports resolve per entry and memoize. - Branding leftovers: 'Run: forgejo auth login' hint and forgejo-cli/1.0.0 User-Agent now say stoke (UA tracks pkg.version). - Added request timeouts (30s default, 10m for migrations). - --limit and --team-id are validated as integers instead of silently misbehaving on garbage (NaN made -l show all results). New commands (per the repo's every-operation-becomes-a-command design): - stoke issue create (title/body/body-file/assignees) - stoke pr create (head/base/title/body/body-file) Tests and metadata: - New test suite on the built-in node:test runner (25 tests) covering config resolution/persistence, the API client with a mocked fetch, and end-to-end CLI behavior. npm test previously matched no files. - package.json: engines >=22.12.0 (required by commander@15 — the README claimed Node 18), repository, keywords, author; version 1.1.0. - README: corrected Node requirement, documented repo rename (was missing), issue create, pr create, logout options and revocation caveat, --no-auto-init, XDG behavior, import token rules, testing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 19:28:23 +00:00
issue
.command('create')
.description('Create an issue in a repository')
.requiredOption('-o, --owner <owner>', 'repository owner')
.requiredOption('-r, --repo <repo>', 'repository name')
.requiredOption('-t, --title <title>', 'issue title')
.option('-b, --body <body>', 'issue body (markdown)')
.option('--body-file <path>', 'read the issue body from a file')
.option('--assignee <username...>', 'assign the issue to one or more users')
.action(async (options) => {
try {
const config = loadConfig();
const client = ForgejoClient.fromConfig(config);
const payload = {
title: options.title,
body: readBodyOption(options) || '',
};
if (options.assignee && options.assignee.length) {
payload.assignees = options.assignee;
}
const result = await client.createIssue(options.owner, options.repo, payload);
console.log(`Issue created: #${result.number} ${result.title}`);
console.log(`URL: ${result.html_url}`);
} catch (err) {
console.error(`Issue creation failed: ${err.message}`);
if (err.status) console.error(`HTTP status: ${err.status}`);
process.exit(1);
}
});
const pr = program
.command('pr')
.description('Manage pull requests');
pr
.command('list')
.description('List pull requests in a repository')
.requiredOption('-o, --owner <owner>', 'repository owner')
.requiredOption('-r, --repo <repo>', 'repository name')
.option('-s, --state <state>', 'PR state: open, closed, all', 'open')
Audit: fix auth/config bugs, add issue/pr create, tests and docs Fixes found during a full audit of the CLI: - auth logout: remote token revocation always failed with 401 because Forgejo only accepts Basic auth on the token endpoints. Logout now asks for (or accepts) the account password, supports --password, --password-file and --local-only, and clearly reports when the token is left active. - Silent password prompt actually echoed the password on a TTY: overriding rl.write does not suppress readline echo. Switched to the callback readline module and mute _writeToOutput instead (the readline/promises interface does not honor that hook). - Global --config flag was silently ignored: config paths were resolved at require time, before the preAction hook set STOKE_CONFIG_FILE. Paths are now resolved lazily on every access. - XDG_CONFIG_HOME handling put the config in $XDG_CONFIG_HOME/.config/stoke; per the XDG spec it now resolves to $XDG_CONFIG_HOME/stoke. - repo create: --auto-init defaulted to true with no way to disable it; added --no-auto-init. - repo import/import-batch: a GitHub token was required even for non-GitHub services (e.g. --service git), making those imports fail without gh/GITHUB_TOKEN. Tokens are now only auto-resolved for the github service; batch imports resolve per entry and memoize. - Branding leftovers: 'Run: forgejo auth login' hint and forgejo-cli/1.0.0 User-Agent now say stoke (UA tracks pkg.version). - Added request timeouts (30s default, 10m for migrations). - --limit and --team-id are validated as integers instead of silently misbehaving on garbage (NaN made -l show all results). New commands (per the repo's every-operation-becomes-a-command design): - stoke issue create (title/body/body-file/assignees) - stoke pr create (head/base/title/body/body-file) Tests and metadata: - New test suite on the built-in node:test runner (25 tests) covering config resolution/persistence, the API client with a mocked fetch, and end-to-end CLI behavior. npm test previously matched no files. - package.json: engines >=22.12.0 (required by commander@15 — the README claimed Node 18), repository, keywords, author; version 1.1.0. - README: corrected Node requirement, documented repo rename (was missing), issue create, pr create, logout options and revocation caveat, --no-auto-init, XDG behavior, import token rules, testing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 19:28:23 +00:00
.option('-l, --limit <number>', 'maximum pull requests to return (0 for all)', parseLimit, 50)
.action(async (options) => {
try {
const config = loadConfig();
const client = ForgejoClient.fromConfig(config);
const pulls = await client.listPullRequests(options.owner, options.repo, {
state: options.state,
});
Audit: fix auth/config bugs, add issue/pr create, tests and docs Fixes found during a full audit of the CLI: - auth logout: remote token revocation always failed with 401 because Forgejo only accepts Basic auth on the token endpoints. Logout now asks for (or accepts) the account password, supports --password, --password-file and --local-only, and clearly reports when the token is left active. - Silent password prompt actually echoed the password on a TTY: overriding rl.write does not suppress readline echo. Switched to the callback readline module and mute _writeToOutput instead (the readline/promises interface does not honor that hook). - Global --config flag was silently ignored: config paths were resolved at require time, before the preAction hook set STOKE_CONFIG_FILE. Paths are now resolved lazily on every access. - XDG_CONFIG_HOME handling put the config in $XDG_CONFIG_HOME/.config/stoke; per the XDG spec it now resolves to $XDG_CONFIG_HOME/stoke. - repo create: --auto-init defaulted to true with no way to disable it; added --no-auto-init. - repo import/import-batch: a GitHub token was required even for non-GitHub services (e.g. --service git), making those imports fail without gh/GITHUB_TOKEN. Tokens are now only auto-resolved for the github service; batch imports resolve per entry and memoize. - Branding leftovers: 'Run: forgejo auth login' hint and forgejo-cli/1.0.0 User-Agent now say stoke (UA tracks pkg.version). - Added request timeouts (30s default, 10m for migrations). - --limit and --team-id are validated as integers instead of silently misbehaving on garbage (NaN made -l show all results). New commands (per the repo's every-operation-becomes-a-command design): - stoke issue create (title/body/body-file/assignees) - stoke pr create (head/base/title/body/body-file) Tests and metadata: - New test suite on the built-in node:test runner (25 tests) covering config resolution/persistence, the API client with a mocked fetch, and end-to-end CLI behavior. npm test previously matched no files. - package.json: engines >=22.12.0 (required by commander@15 — the README claimed Node 18), repository, keywords, author; version 1.1.0. - README: corrected Node requirement, documented repo rename (was missing), issue create, pr create, logout options and revocation caveat, --no-auto-init, XDG behavior, import token rules, testing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 19:28:23 +00:00
const display = options.limit > 0 ? pulls.slice(0, options.limit) : pulls;
if (!display.length) {
console.log('No pull requests found.');
return;
}
for (const p of display) {
console.log(`!${p.number} [${p.state}] ${p.title} (${p.head.ref} -> ${p.base.ref})`);
}
if (pulls.length > display.length) {
console.log(`...and ${pulls.length - display.length} more (use -l 0 for all).`);
}
} catch (err) {
console.error(`Failed to list pull requests: ${err.message}`);
process.exit(1);
}
});
Audit: fix auth/config bugs, add issue/pr create, tests and docs Fixes found during a full audit of the CLI: - auth logout: remote token revocation always failed with 401 because Forgejo only accepts Basic auth on the token endpoints. Logout now asks for (or accepts) the account password, supports --password, --password-file and --local-only, and clearly reports when the token is left active. - Silent password prompt actually echoed the password on a TTY: overriding rl.write does not suppress readline echo. Switched to the callback readline module and mute _writeToOutput instead (the readline/promises interface does not honor that hook). - Global --config flag was silently ignored: config paths were resolved at require time, before the preAction hook set STOKE_CONFIG_FILE. Paths are now resolved lazily on every access. - XDG_CONFIG_HOME handling put the config in $XDG_CONFIG_HOME/.config/stoke; per the XDG spec it now resolves to $XDG_CONFIG_HOME/stoke. - repo create: --auto-init defaulted to true with no way to disable it; added --no-auto-init. - repo import/import-batch: a GitHub token was required even for non-GitHub services (e.g. --service git), making those imports fail without gh/GITHUB_TOKEN. Tokens are now only auto-resolved for the github service; batch imports resolve per entry and memoize. - Branding leftovers: 'Run: forgejo auth login' hint and forgejo-cli/1.0.0 User-Agent now say stoke (UA tracks pkg.version). - Added request timeouts (30s default, 10m for migrations). - --limit and --team-id are validated as integers instead of silently misbehaving on garbage (NaN made -l show all results). New commands (per the repo's every-operation-becomes-a-command design): - stoke issue create (title/body/body-file/assignees) - stoke pr create (head/base/title/body/body-file) Tests and metadata: - New test suite on the built-in node:test runner (25 tests) covering config resolution/persistence, the API client with a mocked fetch, and end-to-end CLI behavior. npm test previously matched no files. - package.json: engines >=22.12.0 (required by commander@15 — the README claimed Node 18), repository, keywords, author; version 1.1.0. - README: corrected Node requirement, documented repo rename (was missing), issue create, pr create, logout options and revocation caveat, --no-auto-init, XDG behavior, import token rules, testing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 19:28:23 +00:00
pr
.command('create')
.description('Create a pull request in a repository')
.requiredOption('-o, --owner <owner>', 'repository owner')
.requiredOption('-r, --repo <repo>', 'repository name')
.requiredOption('-t, --title <title>', 'pull request title')
.requiredOption('--head <branch>', 'source branch (for cross-repo PRs use user:branch)')
.option('--base <branch>', 'target branch', 'main')
.option('-b, --body <body>', 'pull request body (markdown)')
.option('--body-file <path>', 'read the pull request body from a file')
.action(async (options) => {
try {
const config = loadConfig();
const client = ForgejoClient.fromConfig(config);
const payload = {
title: options.title,
head: options.head,
base: options.base,
body: readBodyOption(options) || '',
};
const result = await client.createPullRequest(options.owner, options.repo, payload);
console.log(`Pull request created: !${result.number} ${result.title}`);
console.log(`URL: ${result.html_url}`);
} catch (err) {
console.error(`Pull request creation failed: ${err.message}`);
if (err.status) console.error(`HTTP status: ${err.status}`);
process.exit(1);
}
});
Add apt distribution: deb packaging, registry publish, docs (#1) Implements #1 — stoke installable with apt-get install stoke. Packaging: - scripts/build-deb.sh: builds dist/stoke_<version>_all.deb from a clean staging copy (src + fresh npm ci --omit=dev), pure-JS Architecture: all, Depends: nodejs (>= 22.12), /usr/lib/stoke payload with /usr/bin/stoke symlink, copyright + changelog, normalized permissions. Lintian-clean. - scripts/publish-deb.sh: uploads a .deb to the Forgejo Debian registry (owner/distribution/component parameterized, defaults heavy-duty/ stable/main), authenticating with STOKE_TOKEN or the stoke login token. - scripts/install-apt.sh: consumer-side one-time setup — adds the registry key and apt source, then apt-get install stoke. Falls back to a [trusted=yes] source when apt's sqv verifier rejects the forge's registry signature (known upstream Forgejo signing bug; the script prefers the signed source so setups heal once the forge is fixed). - .forgejo/workflows/release.yml: on v* tags — test, build, publish to the heavy-duty registry, attach the .deb to the release page. Needs a runner and a RELEASE_TOKEN secret with org package write. New command: - stoke pr merge (-n, --method merge|rebase|rebase-merge|squash, --title, --message, --delete-branch) — gap found while merging !2. Docs and housekeeping: - README: 'Install with apt' as the primary installation method with manual setup and dpkg fallback, signature caveat, pr merge reference, Packaging and releasing section with a release checklist. - dist/ gitignored; version bumped to 1.2.0. Verified end-to-end on this machine: built the deb (lintian-clean), published it to the Forgejo Debian registry, installed it with apt-get install stoke via install-apt.sh, and confirmed the installed CLI works against the live forge. The test upload was removed from the personal namespace afterwards; publishing under heavy-duty needs an org-member token (401 reqPackageAccess with this restricted account). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 19:40:51 +00:00
pr
.command('merge')
.description('Merge a pull request')
.requiredOption('-o, --owner <owner>', 'repository owner')
.requiredOption('-r, --repo <repo>', 'repository name')
.requiredOption('-n, --number <number>', 'pull request number', parseId)
.option('--method <method>', 'merge method: merge, rebase, rebase-merge, squash', 'merge')
.option('--title <title>', 'custom merge commit title')
.option('--message <message>', 'custom merge commit message')
.option('--delete-branch', 'delete the source branch after merging', false)
.action(async (options) => {
try {
const config = loadConfig();
const client = ForgejoClient.fromConfig(config);
const payload = {
Do: options.method,
delete_branch_after_merge: options.deleteBranch,
};
if (options.title) payload.MergeTitleField = options.title;
if (options.message) payload.MergeMessageField = options.message;
await client.mergePullRequest(options.owner, options.repo, options.number, payload);
console.log(`Merged !${options.number} in ${options.owner}/${options.repo} (${options.method}).`);
if (options.deleteBranch) console.log('Source branch deleted.');
} catch (err) {
console.error(`Pull request merge failed: ${err.message}`);
if (err.status) console.error(`HTTP status: ${err.status}`);
process.exit(1);
}
});
const branchCmd = program
.command('branch')
.description('Manage branches');
branchCmd
.command('list')
.description('List branches in a repository')
.requiredOption('-o, --owner <owner>', 'repository owner')
.requiredOption('-r, --repo <repo>', 'repository name')
Audit: fix auth/config bugs, add issue/pr create, tests and docs Fixes found during a full audit of the CLI: - auth logout: remote token revocation always failed with 401 because Forgejo only accepts Basic auth on the token endpoints. Logout now asks for (or accepts) the account password, supports --password, --password-file and --local-only, and clearly reports when the token is left active. - Silent password prompt actually echoed the password on a TTY: overriding rl.write does not suppress readline echo. Switched to the callback readline module and mute _writeToOutput instead (the readline/promises interface does not honor that hook). - Global --config flag was silently ignored: config paths were resolved at require time, before the preAction hook set STOKE_CONFIG_FILE. Paths are now resolved lazily on every access. - XDG_CONFIG_HOME handling put the config in $XDG_CONFIG_HOME/.config/stoke; per the XDG spec it now resolves to $XDG_CONFIG_HOME/stoke. - repo create: --auto-init defaulted to true with no way to disable it; added --no-auto-init. - repo import/import-batch: a GitHub token was required even for non-GitHub services (e.g. --service git), making those imports fail without gh/GITHUB_TOKEN. Tokens are now only auto-resolved for the github service; batch imports resolve per entry and memoize. - Branding leftovers: 'Run: forgejo auth login' hint and forgejo-cli/1.0.0 User-Agent now say stoke (UA tracks pkg.version). - Added request timeouts (30s default, 10m for migrations). - --limit and --team-id are validated as integers instead of silently misbehaving on garbage (NaN made -l show all results). New commands (per the repo's every-operation-becomes-a-command design): - stoke issue create (title/body/body-file/assignees) - stoke pr create (head/base/title/body/body-file) Tests and metadata: - New test suite on the built-in node:test runner (25 tests) covering config resolution/persistence, the API client with a mocked fetch, and end-to-end CLI behavior. npm test previously matched no files. - package.json: engines >=22.12.0 (required by commander@15 — the README claimed Node 18), repository, keywords, author; version 1.1.0. - README: corrected Node requirement, documented repo rename (was missing), issue create, pr create, logout options and revocation caveat, --no-auto-init, XDG behavior, import token rules, testing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 19:28:23 +00:00
.option('-l, --limit <number>', 'maximum branches to return (0 for all)', parseLimit, 50)
.action(async (options) => {
try {
const config = loadConfig();
const client = ForgejoClient.fromConfig(config);
const branches = await client.listBranches(options.owner, options.repo);
Audit: fix auth/config bugs, add issue/pr create, tests and docs Fixes found during a full audit of the CLI: - auth logout: remote token revocation always failed with 401 because Forgejo only accepts Basic auth on the token endpoints. Logout now asks for (or accepts) the account password, supports --password, --password-file and --local-only, and clearly reports when the token is left active. - Silent password prompt actually echoed the password on a TTY: overriding rl.write does not suppress readline echo. Switched to the callback readline module and mute _writeToOutput instead (the readline/promises interface does not honor that hook). - Global --config flag was silently ignored: config paths were resolved at require time, before the preAction hook set STOKE_CONFIG_FILE. Paths are now resolved lazily on every access. - XDG_CONFIG_HOME handling put the config in $XDG_CONFIG_HOME/.config/stoke; per the XDG spec it now resolves to $XDG_CONFIG_HOME/stoke. - repo create: --auto-init defaulted to true with no way to disable it; added --no-auto-init. - repo import/import-batch: a GitHub token was required even for non-GitHub services (e.g. --service git), making those imports fail without gh/GITHUB_TOKEN. Tokens are now only auto-resolved for the github service; batch imports resolve per entry and memoize. - Branding leftovers: 'Run: forgejo auth login' hint and forgejo-cli/1.0.0 User-Agent now say stoke (UA tracks pkg.version). - Added request timeouts (30s default, 10m for migrations). - --limit and --team-id are validated as integers instead of silently misbehaving on garbage (NaN made -l show all results). New commands (per the repo's every-operation-becomes-a-command design): - stoke issue create (title/body/body-file/assignees) - stoke pr create (head/base/title/body/body-file) Tests and metadata: - New test suite on the built-in node:test runner (25 tests) covering config resolution/persistence, the API client with a mocked fetch, and end-to-end CLI behavior. npm test previously matched no files. - package.json: engines >=22.12.0 (required by commander@15 — the README claimed Node 18), repository, keywords, author; version 1.1.0. - README: corrected Node requirement, documented repo rename (was missing), issue create, pr create, logout options and revocation caveat, --no-auto-init, XDG behavior, import token rules, testing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 19:28:23 +00:00
const display = options.limit > 0 ? branches.slice(0, options.limit) : branches;
if (!display.length) {
console.log('No branches found.');
return;
}
for (const b of display) {
console.log(b.name);
}
if (branches.length > display.length) {
console.log(`...and ${branches.length - display.length} more (use -l 0 for all).`);
}
} catch (err) {
console.error(`Failed to list branches: ${err.message}`);
process.exit(1);
}
});
2026-07-22 15:06:35 +00:00
const collaborator = program
.command('collaborator')
.description('Manage repository collaborators');
collaborator
.command('add')
.description('Add a collaborator to a repository')
.requiredOption('-o, --owner <owner>', 'repository owner')
.requiredOption('-r, --repo <repo>', 'repository name')
.requiredOption('-u, --user <username>', 'username of the collaborator')
.option('--permission <permission>', 'permission level: read, write, admin', 'write')
.action(async (options) => {
try {
const config = loadConfig();
const client = ForgejoClient.fromConfig(config);
await client.addCollaborator(options.owner, options.repo, options.user, options.permission);
console.log(`Added ${options.user} as ${options.permission} collaborator to ${options.owner}/${options.repo}.`);
} catch (err) {
console.error(`Failed to add collaborator: ${err.message}`);
if (err.status) console.error(`HTTP status: ${err.status}`);
process.exit(1);
}
});
const org = program
.command('org')
.description('Manage organizations');
org
.command('create')
.description('Create a new organization')
.requiredOption('--name <name>', 'organization username (short name used in URLs)')
.option('--full-name <full-name>', 'display name of the organization', '')
.option('-d, --description <description>', 'organization description', '')
.option('--website <website>', 'organization website', '')
.option('--location <location>', 'organization location', '')
.option('--visibility <visibility>', 'visibility: public, limited, private', 'public')
.action(async (options) => {
try {
const config = loadConfig();
const client = ForgejoClient.fromConfig(config);
const payload = {
username: options.name,
full_name: options.fullName,
description: options.description,
website: options.website,
location: options.location,
visibility: options.visibility,
};
const result = await client.createOrg(payload);
console.log(`Organization created: ${result.username}`);
if (result.full_name) console.log(`Full name: ${result.full_name}`);
console.log(`URL: ${config.url}/${result.username}`);
} catch (err) {
console.error(`Organization creation failed: ${err.message}`);
if (err.status) console.error(`HTTP status: ${err.status}`);
process.exit(1);
}
});
org
.command('repos')
.description('List repositories owned by an organization')
.requiredOption('-o, --org <org>', 'organization name')
Audit: fix auth/config bugs, add issue/pr create, tests and docs Fixes found during a full audit of the CLI: - auth logout: remote token revocation always failed with 401 because Forgejo only accepts Basic auth on the token endpoints. Logout now asks for (or accepts) the account password, supports --password, --password-file and --local-only, and clearly reports when the token is left active. - Silent password prompt actually echoed the password on a TTY: overriding rl.write does not suppress readline echo. Switched to the callback readline module and mute _writeToOutput instead (the readline/promises interface does not honor that hook). - Global --config flag was silently ignored: config paths were resolved at require time, before the preAction hook set STOKE_CONFIG_FILE. Paths are now resolved lazily on every access. - XDG_CONFIG_HOME handling put the config in $XDG_CONFIG_HOME/.config/stoke; per the XDG spec it now resolves to $XDG_CONFIG_HOME/stoke. - repo create: --auto-init defaulted to true with no way to disable it; added --no-auto-init. - repo import/import-batch: a GitHub token was required even for non-GitHub services (e.g. --service git), making those imports fail without gh/GITHUB_TOKEN. Tokens are now only auto-resolved for the github service; batch imports resolve per entry and memoize. - Branding leftovers: 'Run: forgejo auth login' hint and forgejo-cli/1.0.0 User-Agent now say stoke (UA tracks pkg.version). - Added request timeouts (30s default, 10m for migrations). - --limit and --team-id are validated as integers instead of silently misbehaving on garbage (NaN made -l show all results). New commands (per the repo's every-operation-becomes-a-command design): - stoke issue create (title/body/body-file/assignees) - stoke pr create (head/base/title/body/body-file) Tests and metadata: - New test suite on the built-in node:test runner (25 tests) covering config resolution/persistence, the API client with a mocked fetch, and end-to-end CLI behavior. npm test previously matched no files. - package.json: engines >=22.12.0 (required by commander@15 — the README claimed Node 18), repository, keywords, author; version 1.1.0. - README: corrected Node requirement, documented repo rename (was missing), issue create, pr create, logout options and revocation caveat, --no-auto-init, XDG behavior, import token rules, testing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 19:28:23 +00:00
.option('-l, --limit <number>', 'maximum repositories to return (0 for all)', parseLimit, 50)
.action(async (options) => {
try {
const config = loadConfig();
const client = ForgejoClient.fromConfig(config);
const repos = await client.listOrgRepos(options.org);
Audit: fix auth/config bugs, add issue/pr create, tests and docs Fixes found during a full audit of the CLI: - auth logout: remote token revocation always failed with 401 because Forgejo only accepts Basic auth on the token endpoints. Logout now asks for (or accepts) the account password, supports --password, --password-file and --local-only, and clearly reports when the token is left active. - Silent password prompt actually echoed the password on a TTY: overriding rl.write does not suppress readline echo. Switched to the callback readline module and mute _writeToOutput instead (the readline/promises interface does not honor that hook). - Global --config flag was silently ignored: config paths were resolved at require time, before the preAction hook set STOKE_CONFIG_FILE. Paths are now resolved lazily on every access. - XDG_CONFIG_HOME handling put the config in $XDG_CONFIG_HOME/.config/stoke; per the XDG spec it now resolves to $XDG_CONFIG_HOME/stoke. - repo create: --auto-init defaulted to true with no way to disable it; added --no-auto-init. - repo import/import-batch: a GitHub token was required even for non-GitHub services (e.g. --service git), making those imports fail without gh/GITHUB_TOKEN. Tokens are now only auto-resolved for the github service; batch imports resolve per entry and memoize. - Branding leftovers: 'Run: forgejo auth login' hint and forgejo-cli/1.0.0 User-Agent now say stoke (UA tracks pkg.version). - Added request timeouts (30s default, 10m for migrations). - --limit and --team-id are validated as integers instead of silently misbehaving on garbage (NaN made -l show all results). New commands (per the repo's every-operation-becomes-a-command design): - stoke issue create (title/body/body-file/assignees) - stoke pr create (head/base/title/body/body-file) Tests and metadata: - New test suite on the built-in node:test runner (25 tests) covering config resolution/persistence, the API client with a mocked fetch, and end-to-end CLI behavior. npm test previously matched no files. - package.json: engines >=22.12.0 (required by commander@15 — the README claimed Node 18), repository, keywords, author; version 1.1.0. - README: corrected Node requirement, documented repo rename (was missing), issue create, pr create, logout options and revocation caveat, --no-auto-init, XDG behavior, import token rules, testing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 19:28:23 +00:00
const display = options.limit > 0 ? repos.slice(0, options.limit) : repos;
if (!display.length) {
console.log('No repositories found.');
return;
}
for (const r of display) {
const vis = r.private ? 'private' : 'public';
console.log(`${r.full_name} [${vis}] ${r.html_url}`);
}
if (repos.length > display.length) {
console.log(`...and ${repos.length - display.length} more (use -l 0 for all).`);
}
} catch (err) {
console.error(`Failed to list organization repositories: ${err.message}`);
process.exit(1);
}
});
org
.command('avatar')
.description('Set the avatar (logo) of an organization from an image file')
.requiredOption('-o, --org <org>', 'organization name')
.requiredOption('-f, --file <path>', 'path to the image file (png, jpeg, gif, ...)')
.action(async (options) => {
try {
const config = loadConfig();
const client = ForgejoClient.fromConfig(config);
let image;
try {
image = fs.readFileSync(options.file);
} catch (err) {
throw new Error(`Could not read image file ${options.file}: ${err.message}`);
}
await client.updateOrgAvatar(options.org, image.toString('base64'));
console.log(`Avatar updated for organization ${options.org}.`);
} catch (err) {
console.error(`Failed to update organization avatar: ${err.message}`);
if (err.status) console.error(`HTTP status: ${err.status}`);
process.exit(1);
}
});
const team = org
.command('team')
.description('Manage organization teams');
team
.command('list')
.description('List teams in an organization')
.requiredOption('-o, --org <org>', 'organization name')
.action(async (options) => {
try {
const config = loadConfig();
const client = ForgejoClient.fromConfig(config);
const teams = await client.listOrgTeams(options.org);
if (!teams.length) {
console.log('No teams found.');
return;
}
for (const t of teams) {
console.log(`#${t.id} ${t.name} [${t.permission}]`);
}
} catch (err) {
console.error(`Failed to list teams: ${err.message}`);
process.exit(1);
}
});
team
.command('create')
.description('Create a team in an organization')
.requiredOption('-o, --org <org>', 'organization name')
.requiredOption('--name <name>', 'team name')
.option('-d, --description <description>', 'team description', '')
.option('--permission <permission>', 'permission level: read, write, admin', 'read')
.option('--all-repos', 'grant access to all current and future organization repositories', false)
.option('--can-create-repo', 'allow members to create repositories in the organization', false)
.option('--units <units>', 'comma-separated team units', 'repo.code,repo.issues,repo.pulls,repo.releases,repo.wiki,repo.projects')
.action(async (options) => {
try {
const config = loadConfig();
const client = ForgejoClient.fromConfig(config);
const payload = {
name: options.name,
description: options.description,
permission: options.permission,
includes_all_repositories: options.allRepos,
can_create_org_repo: options.canCreateRepo,
units: options.units.split(',').map((u) => u.trim()).filter(Boolean),
};
const result = await client.createTeam(options.org, payload);
console.log(`Team created: #${result.id} ${result.name} [${result.permission}]`);
} catch (err) {
console.error(`Team creation failed: ${err.message}`);
if (err.status) console.error(`HTTP status: ${err.status}`);
process.exit(1);
}
});
team
.command('member-list')
.description('List members of a team')
Audit: fix auth/config bugs, add issue/pr create, tests and docs Fixes found during a full audit of the CLI: - auth logout: remote token revocation always failed with 401 because Forgejo only accepts Basic auth on the token endpoints. Logout now asks for (or accepts) the account password, supports --password, --password-file and --local-only, and clearly reports when the token is left active. - Silent password prompt actually echoed the password on a TTY: overriding rl.write does not suppress readline echo. Switched to the callback readline module and mute _writeToOutput instead (the readline/promises interface does not honor that hook). - Global --config flag was silently ignored: config paths were resolved at require time, before the preAction hook set STOKE_CONFIG_FILE. Paths are now resolved lazily on every access. - XDG_CONFIG_HOME handling put the config in $XDG_CONFIG_HOME/.config/stoke; per the XDG spec it now resolves to $XDG_CONFIG_HOME/stoke. - repo create: --auto-init defaulted to true with no way to disable it; added --no-auto-init. - repo import/import-batch: a GitHub token was required even for non-GitHub services (e.g. --service git), making those imports fail without gh/GITHUB_TOKEN. Tokens are now only auto-resolved for the github service; batch imports resolve per entry and memoize. - Branding leftovers: 'Run: forgejo auth login' hint and forgejo-cli/1.0.0 User-Agent now say stoke (UA tracks pkg.version). - Added request timeouts (30s default, 10m for migrations). - --limit and --team-id are validated as integers instead of silently misbehaving on garbage (NaN made -l show all results). New commands (per the repo's every-operation-becomes-a-command design): - stoke issue create (title/body/body-file/assignees) - stoke pr create (head/base/title/body/body-file) Tests and metadata: - New test suite on the built-in node:test runner (25 tests) covering config resolution/persistence, the API client with a mocked fetch, and end-to-end CLI behavior. npm test previously matched no files. - package.json: engines >=22.12.0 (required by commander@15 — the README claimed Node 18), repository, keywords, author; version 1.1.0. - README: corrected Node requirement, documented repo rename (was missing), issue create, pr create, logout options and revocation caveat, --no-auto-init, XDG behavior, import token rules, testing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 19:28:23 +00:00
.requiredOption('--team-id <id>', 'team id (see `stoke org team list`)', parseId)
.action(async (options) => {
try {
const config = loadConfig();
const client = ForgejoClient.fromConfig(config);
Audit: fix auth/config bugs, add issue/pr create, tests and docs Fixes found during a full audit of the CLI: - auth logout: remote token revocation always failed with 401 because Forgejo only accepts Basic auth on the token endpoints. Logout now asks for (or accepts) the account password, supports --password, --password-file and --local-only, and clearly reports when the token is left active. - Silent password prompt actually echoed the password on a TTY: overriding rl.write does not suppress readline echo. Switched to the callback readline module and mute _writeToOutput instead (the readline/promises interface does not honor that hook). - Global --config flag was silently ignored: config paths were resolved at require time, before the preAction hook set STOKE_CONFIG_FILE. Paths are now resolved lazily on every access. - XDG_CONFIG_HOME handling put the config in $XDG_CONFIG_HOME/.config/stoke; per the XDG spec it now resolves to $XDG_CONFIG_HOME/stoke. - repo create: --auto-init defaulted to true with no way to disable it; added --no-auto-init. - repo import/import-batch: a GitHub token was required even for non-GitHub services (e.g. --service git), making those imports fail without gh/GITHUB_TOKEN. Tokens are now only auto-resolved for the github service; batch imports resolve per entry and memoize. - Branding leftovers: 'Run: forgejo auth login' hint and forgejo-cli/1.0.0 User-Agent now say stoke (UA tracks pkg.version). - Added request timeouts (30s default, 10m for migrations). - --limit and --team-id are validated as integers instead of silently misbehaving on garbage (NaN made -l show all results). New commands (per the repo's every-operation-becomes-a-command design): - stoke issue create (title/body/body-file/assignees) - stoke pr create (head/base/title/body/body-file) Tests and metadata: - New test suite on the built-in node:test runner (25 tests) covering config resolution/persistence, the API client with a mocked fetch, and end-to-end CLI behavior. npm test previously matched no files. - package.json: engines >=22.12.0 (required by commander@15 — the README claimed Node 18), repository, keywords, author; version 1.1.0. - README: corrected Node requirement, documented repo rename (was missing), issue create, pr create, logout options and revocation caveat, --no-auto-init, XDG behavior, import token rules, testing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 19:28:23 +00:00
const members = await client.listTeamMembers(options.teamId);
if (!members.length) {
console.log('No members found.');
return;
}
for (const m of members) {
const name = m.full_name ? ` (${m.full_name})` : '';
console.log(`${m.login}${name}`);
}
} catch (err) {
console.error(`Failed to list team members: ${err.message}`);
process.exit(1);
}
});
team
.command('member-add')
.description('Add a user to a team')
Audit: fix auth/config bugs, add issue/pr create, tests and docs Fixes found during a full audit of the CLI: - auth logout: remote token revocation always failed with 401 because Forgejo only accepts Basic auth on the token endpoints. Logout now asks for (or accepts) the account password, supports --password, --password-file and --local-only, and clearly reports when the token is left active. - Silent password prompt actually echoed the password on a TTY: overriding rl.write does not suppress readline echo. Switched to the callback readline module and mute _writeToOutput instead (the readline/promises interface does not honor that hook). - Global --config flag was silently ignored: config paths were resolved at require time, before the preAction hook set STOKE_CONFIG_FILE. Paths are now resolved lazily on every access. - XDG_CONFIG_HOME handling put the config in $XDG_CONFIG_HOME/.config/stoke; per the XDG spec it now resolves to $XDG_CONFIG_HOME/stoke. - repo create: --auto-init defaulted to true with no way to disable it; added --no-auto-init. - repo import/import-batch: a GitHub token was required even for non-GitHub services (e.g. --service git), making those imports fail without gh/GITHUB_TOKEN. Tokens are now only auto-resolved for the github service; batch imports resolve per entry and memoize. - Branding leftovers: 'Run: forgejo auth login' hint and forgejo-cli/1.0.0 User-Agent now say stoke (UA tracks pkg.version). - Added request timeouts (30s default, 10m for migrations). - --limit and --team-id are validated as integers instead of silently misbehaving on garbage (NaN made -l show all results). New commands (per the repo's every-operation-becomes-a-command design): - stoke issue create (title/body/body-file/assignees) - stoke pr create (head/base/title/body/body-file) Tests and metadata: - New test suite on the built-in node:test runner (25 tests) covering config resolution/persistence, the API client with a mocked fetch, and end-to-end CLI behavior. npm test previously matched no files. - package.json: engines >=22.12.0 (required by commander@15 — the README claimed Node 18), repository, keywords, author; version 1.1.0. - README: corrected Node requirement, documented repo rename (was missing), issue create, pr create, logout options and revocation caveat, --no-auto-init, XDG behavior, import token rules, testing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 19:28:23 +00:00
.requiredOption('--team-id <id>', 'team id (see `stoke org team list`)', parseId)
.requiredOption('-u, --user <username>', 'username to add')
.action(async (options) => {
try {
const config = loadConfig();
const client = ForgejoClient.fromConfig(config);
Audit: fix auth/config bugs, add issue/pr create, tests and docs Fixes found during a full audit of the CLI: - auth logout: remote token revocation always failed with 401 because Forgejo only accepts Basic auth on the token endpoints. Logout now asks for (or accepts) the account password, supports --password, --password-file and --local-only, and clearly reports when the token is left active. - Silent password prompt actually echoed the password on a TTY: overriding rl.write does not suppress readline echo. Switched to the callback readline module and mute _writeToOutput instead (the readline/promises interface does not honor that hook). - Global --config flag was silently ignored: config paths were resolved at require time, before the preAction hook set STOKE_CONFIG_FILE. Paths are now resolved lazily on every access. - XDG_CONFIG_HOME handling put the config in $XDG_CONFIG_HOME/.config/stoke; per the XDG spec it now resolves to $XDG_CONFIG_HOME/stoke. - repo create: --auto-init defaulted to true with no way to disable it; added --no-auto-init. - repo import/import-batch: a GitHub token was required even for non-GitHub services (e.g. --service git), making those imports fail without gh/GITHUB_TOKEN. Tokens are now only auto-resolved for the github service; batch imports resolve per entry and memoize. - Branding leftovers: 'Run: forgejo auth login' hint and forgejo-cli/1.0.0 User-Agent now say stoke (UA tracks pkg.version). - Added request timeouts (30s default, 10m for migrations). - --limit and --team-id are validated as integers instead of silently misbehaving on garbage (NaN made -l show all results). New commands (per the repo's every-operation-becomes-a-command design): - stoke issue create (title/body/body-file/assignees) - stoke pr create (head/base/title/body/body-file) Tests and metadata: - New test suite on the built-in node:test runner (25 tests) covering config resolution/persistence, the API client with a mocked fetch, and end-to-end CLI behavior. npm test previously matched no files. - package.json: engines >=22.12.0 (required by commander@15 — the README claimed Node 18), repository, keywords, author; version 1.1.0. - README: corrected Node requirement, documented repo rename (was missing), issue create, pr create, logout options and revocation caveat, --no-auto-init, XDG behavior, import token rules, testing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 19:28:23 +00:00
await client.addTeamMember(options.teamId, options.user);
console.log(`Added ${options.user} to team #${options.teamId}.`);
} catch (err) {
console.error(`Failed to add team member: ${err.message}`);
if (err.status) console.error(`HTTP status: ${err.status}`);
process.exit(1);
}
});
team
.command('member-remove')
.description('Remove a user from a team')
Audit: fix auth/config bugs, add issue/pr create, tests and docs Fixes found during a full audit of the CLI: - auth logout: remote token revocation always failed with 401 because Forgejo only accepts Basic auth on the token endpoints. Logout now asks for (or accepts) the account password, supports --password, --password-file and --local-only, and clearly reports when the token is left active. - Silent password prompt actually echoed the password on a TTY: overriding rl.write does not suppress readline echo. Switched to the callback readline module and mute _writeToOutput instead (the readline/promises interface does not honor that hook). - Global --config flag was silently ignored: config paths were resolved at require time, before the preAction hook set STOKE_CONFIG_FILE. Paths are now resolved lazily on every access. - XDG_CONFIG_HOME handling put the config in $XDG_CONFIG_HOME/.config/stoke; per the XDG spec it now resolves to $XDG_CONFIG_HOME/stoke. - repo create: --auto-init defaulted to true with no way to disable it; added --no-auto-init. - repo import/import-batch: a GitHub token was required even for non-GitHub services (e.g. --service git), making those imports fail without gh/GITHUB_TOKEN. Tokens are now only auto-resolved for the github service; batch imports resolve per entry and memoize. - Branding leftovers: 'Run: forgejo auth login' hint and forgejo-cli/1.0.0 User-Agent now say stoke (UA tracks pkg.version). - Added request timeouts (30s default, 10m for migrations). - --limit and --team-id are validated as integers instead of silently misbehaving on garbage (NaN made -l show all results). New commands (per the repo's every-operation-becomes-a-command design): - stoke issue create (title/body/body-file/assignees) - stoke pr create (head/base/title/body/body-file) Tests and metadata: - New test suite on the built-in node:test runner (25 tests) covering config resolution/persistence, the API client with a mocked fetch, and end-to-end CLI behavior. npm test previously matched no files. - package.json: engines >=22.12.0 (required by commander@15 — the README claimed Node 18), repository, keywords, author; version 1.1.0. - README: corrected Node requirement, documented repo rename (was missing), issue create, pr create, logout options and revocation caveat, --no-auto-init, XDG behavior, import token rules, testing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 19:28:23 +00:00
.requiredOption('--team-id <id>', 'team id (see `stoke org team list`)', parseId)
.requiredOption('-u, --user <username>', 'username to remove')
.action(async (options) => {
try {
const config = loadConfig();
const client = ForgejoClient.fromConfig(config);
Audit: fix auth/config bugs, add issue/pr create, tests and docs Fixes found during a full audit of the CLI: - auth logout: remote token revocation always failed with 401 because Forgejo only accepts Basic auth on the token endpoints. Logout now asks for (or accepts) the account password, supports --password, --password-file and --local-only, and clearly reports when the token is left active. - Silent password prompt actually echoed the password on a TTY: overriding rl.write does not suppress readline echo. Switched to the callback readline module and mute _writeToOutput instead (the readline/promises interface does not honor that hook). - Global --config flag was silently ignored: config paths were resolved at require time, before the preAction hook set STOKE_CONFIG_FILE. Paths are now resolved lazily on every access. - XDG_CONFIG_HOME handling put the config in $XDG_CONFIG_HOME/.config/stoke; per the XDG spec it now resolves to $XDG_CONFIG_HOME/stoke. - repo create: --auto-init defaulted to true with no way to disable it; added --no-auto-init. - repo import/import-batch: a GitHub token was required even for non-GitHub services (e.g. --service git), making those imports fail without gh/GITHUB_TOKEN. Tokens are now only auto-resolved for the github service; batch imports resolve per entry and memoize. - Branding leftovers: 'Run: forgejo auth login' hint and forgejo-cli/1.0.0 User-Agent now say stoke (UA tracks pkg.version). - Added request timeouts (30s default, 10m for migrations). - --limit and --team-id are validated as integers instead of silently misbehaving on garbage (NaN made -l show all results). New commands (per the repo's every-operation-becomes-a-command design): - stoke issue create (title/body/body-file/assignees) - stoke pr create (head/base/title/body/body-file) Tests and metadata: - New test suite on the built-in node:test runner (25 tests) covering config resolution/persistence, the API client with a mocked fetch, and end-to-end CLI behavior. npm test previously matched no files. - package.json: engines >=22.12.0 (required by commander@15 — the README claimed Node 18), repository, keywords, author; version 1.1.0. - README: corrected Node requirement, documented repo rename (was missing), issue create, pr create, logout options and revocation caveat, --no-auto-init, XDG behavior, import token rules, testing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 19:28:23 +00:00
await client.removeTeamMember(options.teamId, options.user);
console.log(`Removed ${options.user} from team #${options.teamId}.`);
} catch (err) {
console.error(`Failed to remove team member: ${err.message}`);
if (err.status) console.error(`HTTP status: ${err.status}`);
process.exit(1);
}
});
const user = program
.command('user')
.description('Manage users');
user
.command('list')
.description('Search/list users on the Forgejo instance')
.option('-q, --query <query>', 'search query (empty lists all visible users)', '')
Audit: fix auth/config bugs, add issue/pr create, tests and docs Fixes found during a full audit of the CLI: - auth logout: remote token revocation always failed with 401 because Forgejo only accepts Basic auth on the token endpoints. Logout now asks for (or accepts) the account password, supports --password, --password-file and --local-only, and clearly reports when the token is left active. - Silent password prompt actually echoed the password on a TTY: overriding rl.write does not suppress readline echo. Switched to the callback readline module and mute _writeToOutput instead (the readline/promises interface does not honor that hook). - Global --config flag was silently ignored: config paths were resolved at require time, before the preAction hook set STOKE_CONFIG_FILE. Paths are now resolved lazily on every access. - XDG_CONFIG_HOME handling put the config in $XDG_CONFIG_HOME/.config/stoke; per the XDG spec it now resolves to $XDG_CONFIG_HOME/stoke. - repo create: --auto-init defaulted to true with no way to disable it; added --no-auto-init. - repo import/import-batch: a GitHub token was required even for non-GitHub services (e.g. --service git), making those imports fail without gh/GITHUB_TOKEN. Tokens are now only auto-resolved for the github service; batch imports resolve per entry and memoize. - Branding leftovers: 'Run: forgejo auth login' hint and forgejo-cli/1.0.0 User-Agent now say stoke (UA tracks pkg.version). - Added request timeouts (30s default, 10m for migrations). - --limit and --team-id are validated as integers instead of silently misbehaving on garbage (NaN made -l show all results). New commands (per the repo's every-operation-becomes-a-command design): - stoke issue create (title/body/body-file/assignees) - stoke pr create (head/base/title/body/body-file) Tests and metadata: - New test suite on the built-in node:test runner (25 tests) covering config resolution/persistence, the API client with a mocked fetch, and end-to-end CLI behavior. npm test previously matched no files. - package.json: engines >=22.12.0 (required by commander@15 — the README claimed Node 18), repository, keywords, author; version 1.1.0. - README: corrected Node requirement, documented repo rename (was missing), issue create, pr create, logout options and revocation caveat, --no-auto-init, XDG behavior, import token rules, testing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 19:28:23 +00:00
.option('-l, --limit <number>', 'maximum users to return (0 for all)', parseLimit, 50)
.action(async (options) => {
try {
const config = loadConfig();
const client = ForgejoClient.fromConfig(config);
const users = await client.searchUsers(options.query);
Audit: fix auth/config bugs, add issue/pr create, tests and docs Fixes found during a full audit of the CLI: - auth logout: remote token revocation always failed with 401 because Forgejo only accepts Basic auth on the token endpoints. Logout now asks for (or accepts) the account password, supports --password, --password-file and --local-only, and clearly reports when the token is left active. - Silent password prompt actually echoed the password on a TTY: overriding rl.write does not suppress readline echo. Switched to the callback readline module and mute _writeToOutput instead (the readline/promises interface does not honor that hook). - Global --config flag was silently ignored: config paths were resolved at require time, before the preAction hook set STOKE_CONFIG_FILE. Paths are now resolved lazily on every access. - XDG_CONFIG_HOME handling put the config in $XDG_CONFIG_HOME/.config/stoke; per the XDG spec it now resolves to $XDG_CONFIG_HOME/stoke. - repo create: --auto-init defaulted to true with no way to disable it; added --no-auto-init. - repo import/import-batch: a GitHub token was required even for non-GitHub services (e.g. --service git), making those imports fail without gh/GITHUB_TOKEN. Tokens are now only auto-resolved for the github service; batch imports resolve per entry and memoize. - Branding leftovers: 'Run: forgejo auth login' hint and forgejo-cli/1.0.0 User-Agent now say stoke (UA tracks pkg.version). - Added request timeouts (30s default, 10m for migrations). - --limit and --team-id are validated as integers instead of silently misbehaving on garbage (NaN made -l show all results). New commands (per the repo's every-operation-becomes-a-command design): - stoke issue create (title/body/body-file/assignees) - stoke pr create (head/base/title/body/body-file) Tests and metadata: - New test suite on the built-in node:test runner (25 tests) covering config resolution/persistence, the API client with a mocked fetch, and end-to-end CLI behavior. npm test previously matched no files. - package.json: engines >=22.12.0 (required by commander@15 — the README claimed Node 18), repository, keywords, author; version 1.1.0. - README: corrected Node requirement, documented repo rename (was missing), issue create, pr create, logout options and revocation caveat, --no-auto-init, XDG behavior, import token rules, testing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 19:28:23 +00:00
const display = options.limit > 0 ? users.slice(0, options.limit) : users;
if (!display.length) {
console.log('No users found.');
return;
}
for (const u of display) {
const name = u.full_name ? ` (${u.full_name})` : '';
console.log(`${u.login}${name}`);
}
if (users.length > display.length) {
console.log(`...and ${users.length - display.length} more (use -l 0 for all).`);
}
} catch (err) {
console.error(`Failed to list users: ${err.message}`);
process.exit(1);
}
});
user
.command('show')
.description('Show a single user profile')
.requiredOption('-u, --user <username>', 'username to look up')
.action(async (options) => {
try {
const config = loadConfig();
const client = ForgejoClient.fromConfig(config);
const u = await client.getUser(options.user);
console.log(`Login: ${u.login}`);
console.log(`Full name: ${u.full_name || '-'}`);
console.log(`Email: ${u.email || '-'}`);
console.log(`URL: ${u.html_url}`);
} catch (err) {
console.error(`Failed to show user: ${err.message}`);
if (err.status) console.error(`HTTP status: ${err.status}`);
process.exit(1);
}
});
program.parseAsync(process.argv).catch((err) => {
console.error(err);
process.exit(1);
});