Skip to content
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@fission-ai/openspec",
"version": "1.7.0",
"version": "1.8.0",
"description": "AI-native system for spec-driven development",
"keywords": [
"openspec",
Expand Down Expand Up @@ -76,6 +76,7 @@
"dependencies": {
"@inquirer/core": "^10.3.2",
"@inquirer/prompts": "^7.10.1",
"@toon-format/toon": "^4.1.0",
"chalk": "^5.6.2",
"commander": "^14.0.0",
"cross-spawn": "7.0.6",
Expand Down
8 changes: 8 additions & 0 deletions pnpm-lock.yaml

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

141 changes: 100 additions & 41 deletions src/cli/index.ts

Large diffs are not rendered by default.

14 changes: 8 additions & 6 deletions src/commands/change.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { printJson } from './shared-output.js';
import type { OutputFormat } from '../core/format-output.js';
import { promises as fs } from 'fs';
import path from 'path';
import { JsonConverter } from '../core/converters/json-converter.js';
Expand Down Expand Up @@ -52,7 +54,7 @@ export class ChangeCommand {
* - JSON mode: minimal object with deltas; --deltas-only returns same object with filtered deltas
* Note: --requirements-only is deprecated alias for --deltas-only
*/
async show(changeName?: string, options?: { json?: boolean; requirementsOnly?: boolean; deltasOnly?: boolean; noInteractive?: boolean; rootOutput?: RootOutput }): Promise<void> {
async show(changeName?: string, options?: { json?: boolean; requirementsOnly?: boolean; deltasOnly?: boolean; noInteractive?: boolean; rootOutput?: RootOutput; format?: OutputFormat }): Promise<void> {
const changesPath = this.getChangesPath();

if (!changeName) {
Expand Down Expand Up @@ -127,7 +129,7 @@ export class ChangeCommand {
deltas,
...(options.rootOutput ? { root: options.rootOutput } : {}),
};
console.log(JSON.stringify(output, null, 2));
printJson(output, options?.format ?? (options?.json ? 'json' : 'json-pretty'));
} else {
const content = await fs.readFile(proposalPath, 'utf-8');
console.log(content);
Expand All @@ -139,7 +141,7 @@ export class ChangeCommand {
* - Text default: IDs only; --long prints minimal details (title, counts)
* - JSON: array of { id, title, deltaCount, taskStatus }, sorted by id
*/
async list(options?: { json?: boolean; long?: boolean }): Promise<void> {
async list(options?: { json?: boolean; long?: boolean; format?: OutputFormat }): Promise<void> {
const changesPath = path.join(process.cwd(), 'openspec', 'changes');

// Same directory-based resolution as `openspec list`, the command this
Expand Down Expand Up @@ -185,7 +187,7 @@ export class ChangeCommand {
);

const sorted = changeDetails.sort((a, b) => a.id.localeCompare(b.id));
console.log(JSON.stringify(sorted, null, 2));
printJson(sorted, options?.format ?? (options?.json ? 'json' : 'json-pretty'));
} else {
if (changes.length === 0) {
console.log('No items found');
Expand Down Expand Up @@ -222,7 +224,7 @@ export class ChangeCommand {
}
}

async validate(changeName?: string, options?: { strict?: boolean; json?: boolean; noInteractive?: boolean }): Promise<void> {
async validate(changeName?: string, options?: { strict?: boolean; json?: boolean; noInteractive?: boolean; format?: OutputFormat }): Promise<void> {
const changesPath = path.join(process.cwd(), 'openspec', 'changes');

if (!changeName) {
Expand Down Expand Up @@ -263,7 +265,7 @@ export class ChangeCommand {
});

if (options?.json) {
console.log(JSON.stringify(report, null, 2));
printJson(report, options?.format ?? (options?.json ? 'json' : 'json-pretty'));
} else {
if (report.valid) {
console.log(`Change "${changeName}" is valid`);
Expand Down
12 changes: 8 additions & 4 deletions src/commands/config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { OutputFormat } from '../core/format-output.js';
import { printJson } from './shared-output.js';
import { Command } from 'commander';
import { spawn } from 'node:child_process';
import * as fs from 'node:fs';
Expand Down Expand Up @@ -233,11 +235,13 @@ export function registerConfigCommand(program: Command): void {
.command('list')
.description('Show all current settings')
.option('--json', 'Output as JSON')
.action((options: { json?: boolean }) => {
.option('--json-pretty', 'Output as formatted JSON')
.option('--toon', 'Output in TOON format')
.action((options: { json?: boolean; format?: OutputFormat }) => {
const config = getGlobalConfig();

if (options.json) {
console.log(JSON.stringify(config, null, 2));
printJson(config, options?.format ?? (options?.json ? 'json' : 'json-pretty'));
} else {
// Read raw config to determine which values are explicit vs defaults
const configPath = getGlobalConfigPath();
Expand Down Expand Up @@ -282,7 +286,7 @@ export function registerConfigCommand(program: Command): void {
}

if (typeof value === 'object' && value !== null) {
console.log(JSON.stringify(value));
printJson(value, 'json-pretty');
} else {
console.log(String(value));
}
Expand All @@ -294,7 +298,7 @@ export function registerConfigCommand(program: Command): void {
.description('Set a value (auto-coerce types)')
.option('--string', 'Force value to be stored as string')
.option('--allow-unknown', 'Allow setting unknown keys')
.action((key: string, value: string, options: { string?: boolean; allowUnknown?: boolean }) => {
.action((key: string, value: string, options: { string?: boolean; allowUnknown?: boolean; format?: OutputFormat }) => {
const allowUnknown = Boolean(options.allowUnknown);
const keyValidation = validateConfigKeyPath(key);
// --allow-unknown relaxes the known-key check, but never the prototype-safety check.
Expand Down
2 changes: 2 additions & 0 deletions src/commands/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ export function registerContextCommand(program: Command): void {
new Option('--store-path <path>', 'Removed; register the store and use --store').hideHelp()
)
.option('--json', 'Output the agent brief as JSON')
.option('--json-pretty', 'Output as formatted JSON')
.option('--toon', 'Output in TOON format')
Comment thread
coderabbitai[bot] marked this conversation as resolved.
.option('--code-workspace <path>', 'Also write a VS Code workspace file for the set')
.option('--force', 'Overwrite an existing --code-workspace file')
.action(
Expand Down
2 changes: 2 additions & 0 deletions src/commands/doctor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,8 @@ export function registerDoctorCommand(program: Command): void {
new Option('--store-path <path>', 'Removed; register the store and use --store').hideHelp()
)
.option('--json', 'Output as JSON')
.option('--json-pretty', 'Output as formatted JSON')
.option('--toon', 'Output in TOON format')
.action(async (options: { store?: string; storePath?: string; json?: boolean }) => {
try {
const root = await resolveRootForCommand(
Expand Down
Loading