Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions bin/shotkit.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ async function main() {
process.stdout.write(USAGE);
return;
}
if (opts.errors.length) {
const msg = opts.errors.join('; ');
if (opts.json) process.stderr.write(JSON.stringify({ ok: false, error: msg, code: 2 }) + '\n');
else console.error(`[shotkit] ${msg}\n\n${USAGE}`);
process.exit(2);
}
const cwd = path.resolve(process.cwd(), opts.path || '.');
const configPath = resolveConfigPath(opts.config, cwd);
if (!configPath || !fs.existsSync(configPath)) {
Expand Down
25 changes: 23 additions & 2 deletions src/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Exit codes: 0 ok · 1 runtime failure · 2 usage / no config found
function parseArgs(argv) {
const opts = {
scenes: [],
errors: [],
noVideo: false,
noBuild: false,
liveGt: false,
Expand All @@ -53,8 +54,26 @@ function parseArgs(argv) {
};
for (let i = 0; i < argv.length; i++) {
const a = argv[i];
if (a === '--scene') opts.scenes.push(...(argv[++i] || '').split(',').filter(Boolean));
else if (a === '--config') opts.config = argv[++i];
if (a === '--scene') {
const value = argv[++i];
if (!value || value.startsWith('-')) {
opts.errors.push('--scene requires a scene name');
if (value && value.startsWith('-')) i--;
} else {
const scenes = value.split(',').filter(Boolean);
if (scenes.length) opts.scenes.push(...scenes);
else opts.errors.push('--scene requires a scene name');
}
}
else if (a === '--config') {
const value = argv[++i];
if (!value || value.startsWith('-')) {
opts.errors.push('--config requires a config path');
if (value && value.startsWith('-')) i--;
} else {
opts.config = value;
}
}
else if (a === '--json') opts.json = true;
else if (a === '--mp4') opts.mp4 = true;
else if (a === '--no-video') opts.noVideo = true;
Expand All @@ -63,6 +82,8 @@ function parseArgs(argv) {
else if (a === '--freeze') opts.freeze = true;
else if (a === '-h' || a === '--help') opts.help = true;
else if (!a.startsWith('-') && opts.path === null) opts.path = a;
else if (!a.startsWith('-')) opts.errors.push(`unexpected positional argument: ${a}`);
else opts.errors.push(`unknown option: ${a}`);
}
return opts;
}
Expand Down
14 changes: 11 additions & 3 deletions test/cli.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const { parseArgs, resolveConfigPath, USAGE } = require('../src/cli');

describe('parseArgs', () => {
test('defaults', () => {
expect(parseArgs([])).toMatchObject({ scenes: [], json: false, noVideo: false, help: false, path: null });
expect(parseArgs([])).toMatchObject({ scenes: [], errors: [], json: false, noVideo: false, help: false, path: null });
});

test('positional path + flags', () => {
Expand All @@ -19,14 +19,22 @@ describe('parseArgs', () => {
expect(parseArgs(['--scene', 'a,b', '--scene', 'c']).scenes).toEqual(['a', 'b', 'c']);
});

test('usage errors are explicit for missing values and unknown options', () => {
expect(parseArgs(['--scene']).errors).toEqual(['--scene requires a scene name']);
expect(parseArgs(['--config', '--json']).errors).toEqual(['--config requires a config path']);
expect(parseArgs(['--wat']).errors).toEqual(['unknown option: --wat']);
});

test('--config consumes its value (not mistaken for the positional)', () => {
const o = parseArgs(['--config', 'x.js', 'repo']);
expect(o.config).toBe('x.js');
expect(o.path).toBe('repo');
});

test('only the first non-flag token becomes the path', () => {
expect(parseArgs(['a', 'b']).path).toBe('a');
test('only one positional path is accepted', () => {
const opts = parseArgs(['a', 'b']);
expect(opts.path).toBe('a');
expect(opts.errors).toEqual(['unexpected positional argument: b']);
});

test('USAGE documents the agent contract', () => {
Expand Down
20 changes: 20 additions & 0 deletions test/package-boundary.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const pkg = require('../package.json');

describe('npm package boundary', () => {
test('ships the public capture and handoff surface only', () => {
expect(pkg.files).toEqual([
'src',
'bin',
'skills/capture',
'docs/handoff-conventions.md',
'schemas',
]);
});

test('keeps repo-internal research and application planning out of the tarball', () => {
expect(pkg.files).not.toContain('skills/research-to-product-fit');
expect(pkg.files).not.toContain('research-runs');
expect(pkg.files).not.toContain('examples');
expect(pkg.files).not.toContain('docs');
});
});