@saga-ai/cli 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +11 -7
  2. package/dist/cli.cjs +313 -26
  3. package/package.json +10 -9
  4. package/dist/cli.js +0 -3063
package/README.md CHANGED
@@ -2,16 +2,20 @@
2
2
 
3
3
  Command-line interface for SAGA (Structured Autonomous Goal Achievement) - a structured development workflow that combines human-guided epic planning with autonomous story execution.
4
4
 
5
- ## Installation
6
-
7
- ```bash
8
- npm install -g @saga-ai/cli
9
- ```
10
-
11
5
  ## Requirements
12
6
 
13
7
  - Node.js >= 18.0.0
14
- - [Claude CLI](https://docs.anthropic.com/en/docs/claude-cli) (for `implement` command)
8
+ - [Claude Code](https://docs.anthropic.com/en/docs/claude-code) (for `implement` command)
9
+
10
+ ## Usage
11
+
12
+ Run commands using npx (no global installation required):
13
+
14
+ ```bash
15
+ npx @saga-ai/cli init
16
+ npx @saga-ai/cli implement <story-slug>
17
+ npx @saga-ai/cli dashboard
18
+ ```
15
19
 
16
20
  ## Commands
17
21
 
package/dist/cli.cjs CHANGED
@@ -26,9 +26,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
26
  mod
27
27
  ));
28
28
 
29
- // node_modules/commander/lib/error.js
29
+ // node_modules/.pnpm/commander@12.1.0/node_modules/commander/lib/error.js
30
30
  var require_error = __commonJS({
31
- "node_modules/commander/lib/error.js"(exports2) {
31
+ "node_modules/.pnpm/commander@12.1.0/node_modules/commander/lib/error.js"(exports2) {
32
32
  var CommanderError2 = class extends Error {
33
33
  /**
34
34
  * Constructs the CommanderError class
@@ -61,9 +61,9 @@ var require_error = __commonJS({
61
61
  }
62
62
  });
63
63
 
64
- // node_modules/commander/lib/argument.js
64
+ // node_modules/.pnpm/commander@12.1.0/node_modules/commander/lib/argument.js
65
65
  var require_argument = __commonJS({
66
- "node_modules/commander/lib/argument.js"(exports2) {
66
+ "node_modules/.pnpm/commander@12.1.0/node_modules/commander/lib/argument.js"(exports2) {
67
67
  var { InvalidArgumentError: InvalidArgumentError2 } = require_error();
68
68
  var Argument2 = class {
69
69
  /**
@@ -188,9 +188,9 @@ var require_argument = __commonJS({
188
188
  }
189
189
  });
190
190
 
191
- // node_modules/commander/lib/help.js
191
+ // node_modules/.pnpm/commander@12.1.0/node_modules/commander/lib/help.js
192
192
  var require_help = __commonJS({
193
- "node_modules/commander/lib/help.js"(exports2) {
193
+ "node_modules/.pnpm/commander@12.1.0/node_modules/commander/lib/help.js"(exports2) {
194
194
  var { humanReadableArgName } = require_argument();
195
195
  var Help2 = class {
196
196
  constructor() {
@@ -602,9 +602,9 @@ var require_help = __commonJS({
602
602
  }
603
603
  });
604
604
 
605
- // node_modules/commander/lib/option.js
605
+ // node_modules/.pnpm/commander@12.1.0/node_modules/commander/lib/option.js
606
606
  var require_option = __commonJS({
607
- "node_modules/commander/lib/option.js"(exports2) {
607
+ "node_modules/.pnpm/commander@12.1.0/node_modules/commander/lib/option.js"(exports2) {
608
608
  var { InvalidArgumentError: InvalidArgumentError2 } = require_error();
609
609
  var Option2 = class {
610
610
  /**
@@ -874,9 +874,9 @@ var require_option = __commonJS({
874
874
  }
875
875
  });
876
876
 
877
- // node_modules/commander/lib/suggestSimilar.js
877
+ // node_modules/.pnpm/commander@12.1.0/node_modules/commander/lib/suggestSimilar.js
878
878
  var require_suggestSimilar = __commonJS({
879
- "node_modules/commander/lib/suggestSimilar.js"(exports2) {
879
+ "node_modules/.pnpm/commander@12.1.0/node_modules/commander/lib/suggestSimilar.js"(exports2) {
880
880
  var maxDistance = 3;
881
881
  function editDistance(a, b) {
882
882
  if (Math.abs(a.length - b.length) > maxDistance)
@@ -954,9 +954,9 @@ var require_suggestSimilar = __commonJS({
954
954
  }
955
955
  });
956
956
 
957
- // node_modules/commander/lib/command.js
957
+ // node_modules/.pnpm/commander@12.1.0/node_modules/commander/lib/command.js
958
958
  var require_command = __commonJS({
959
- "node_modules/commander/lib/command.js"(exports2) {
959
+ "node_modules/.pnpm/commander@12.1.0/node_modules/commander/lib/command.js"(exports2) {
960
960
  var EventEmitter = require("node:events").EventEmitter;
961
961
  var childProcess = require("node:child_process");
962
962
  var path = require("node:path");
@@ -2997,9 +2997,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
2997
2997
  }
2998
2998
  });
2999
2999
 
3000
- // node_modules/commander/index.js
3000
+ // node_modules/.pnpm/commander@12.1.0/node_modules/commander/index.js
3001
3001
  var require_commander = __commonJS({
3002
- "node_modules/commander/index.js"(exports2) {
3002
+ "node_modules/.pnpm/commander@12.1.0/node_modules/commander/index.js"(exports2) {
3003
3003
  var { Argument: Argument2 } = require_argument();
3004
3004
  var { Command: Command2 } = require_command();
3005
3005
  var { CommanderError: CommanderError2, InvalidArgumentError: InvalidArgumentError2 } = require_error();
@@ -3019,7 +3019,7 @@ var require_commander = __commonJS({
3019
3019
  }
3020
3020
  });
3021
3021
 
3022
- // node_modules/commander/esm.mjs
3022
+ // node_modules/.pnpm/commander@12.1.0/node_modules/commander/esm.mjs
3023
3023
  var import_index = __toESM(require_commander(), 1);
3024
3024
  var {
3025
3025
  program,
@@ -3083,6 +3083,62 @@ Make sure the path points to a SAGA project root.`
3083
3083
 
3084
3084
  // src/commands/init.ts
3085
3085
  var WORKTREES_PATTERN = ".saga/worktrees/";
3086
+ function runInitDryRun(targetPath) {
3087
+ const sagaDir = (0, import_node_path2.join)(targetPath, ".saga");
3088
+ const sagaExists = (0, import_node_fs2.existsSync)(sagaDir);
3089
+ const directories = [
3090
+ { name: "epics", path: (0, import_node_path2.join)(sagaDir, "epics") },
3091
+ { name: "archive", path: (0, import_node_path2.join)(sagaDir, "archive") },
3092
+ { name: "worktrees", path: (0, import_node_path2.join)(sagaDir, "worktrees") }
3093
+ ].map((dir) => ({
3094
+ path: dir.path,
3095
+ exists: (0, import_node_fs2.existsSync)(dir.path),
3096
+ action: (0, import_node_fs2.existsSync)(dir.path) ? "exists (skip)" : "will create"
3097
+ }));
3098
+ const gitignorePath = (0, import_node_path2.join)(targetPath, ".gitignore");
3099
+ const gitignoreExists = (0, import_node_fs2.existsSync)(gitignorePath);
3100
+ let hasPattern = false;
3101
+ if (gitignoreExists) {
3102
+ const content = (0, import_node_fs2.readFileSync)(gitignorePath, "utf-8");
3103
+ hasPattern = content.includes(WORKTREES_PATTERN);
3104
+ }
3105
+ let gitignoreAction;
3106
+ if (!gitignoreExists) {
3107
+ gitignoreAction = "will create with worktrees pattern";
3108
+ } else if (hasPattern) {
3109
+ gitignoreAction = "already has pattern (skip)";
3110
+ } else {
3111
+ gitignoreAction = "will append worktrees pattern";
3112
+ }
3113
+ return {
3114
+ targetPath,
3115
+ sagaExists,
3116
+ directories,
3117
+ gitignore: {
3118
+ path: gitignorePath,
3119
+ exists: gitignoreExists,
3120
+ hasPattern,
3121
+ action: gitignoreAction
3122
+ }
3123
+ };
3124
+ }
3125
+ function printInitDryRunResults(result) {
3126
+ console.log("Dry Run - Init Command");
3127
+ console.log("======================\n");
3128
+ console.log(`Target directory: ${result.targetPath}`);
3129
+ console.log(`SAGA project exists: ${result.sagaExists ? "yes" : "no"}
3130
+ `);
3131
+ console.log("Directories:");
3132
+ for (const dir of result.directories) {
3133
+ const icon = dir.exists ? "-" : "+";
3134
+ console.log(` ${icon} ${dir.path}`);
3135
+ console.log(` Action: ${dir.action}`);
3136
+ }
3137
+ console.log("\nGitignore:");
3138
+ console.log(` Path: ${result.gitignore.path}`);
3139
+ console.log(` Action: ${result.gitignore.action}`);
3140
+ console.log("\nDry run complete. No changes made.");
3141
+ }
3086
3142
  function resolveTargetPath(explicitPath) {
3087
3143
  if (explicitPath) {
3088
3144
  return explicitPath;
@@ -3138,6 +3194,11 @@ async function initCommand(options) {
3138
3194
  }
3139
3195
  }
3140
3196
  const targetPath = resolveTargetPath(options.path);
3197
+ if (options.dryRun) {
3198
+ const dryRunResult = runInitDryRun(targetPath);
3199
+ printInitDryRunResults(dryRunResult);
3200
+ return;
3201
+ }
3141
3202
  createDirectoryStructure(targetPath);
3142
3203
  updateGitignore(targetPath);
3143
3204
  console.log(`Initialized .saga/ at ${targetPath}`);
@@ -3228,6 +3289,135 @@ This may indicate an incomplete story setup.`
3228
3289
  function getSkillRoot(pluginRoot) {
3229
3290
  return (0, import_node_path3.join)(pluginRoot, "skills", "execute-story");
3230
3291
  }
3292
+ function checkCommandExists(command) {
3293
+ try {
3294
+ const result = (0, import_node_child_process.spawnSync)("which", [command], { encoding: "utf-8" });
3295
+ if (result.status === 0 && result.stdout.trim()) {
3296
+ return { exists: true, path: result.stdout.trim() };
3297
+ }
3298
+ return { exists: false };
3299
+ } catch {
3300
+ return { exists: false };
3301
+ }
3302
+ }
3303
+ function runDryRun(storyInfo, projectPath, pluginRoot) {
3304
+ const checks = [];
3305
+ let allPassed = true;
3306
+ if (pluginRoot) {
3307
+ checks.push({
3308
+ name: "SAGA_PLUGIN_ROOT",
3309
+ path: pluginRoot,
3310
+ passed: true
3311
+ });
3312
+ } else {
3313
+ checks.push({
3314
+ name: "SAGA_PLUGIN_ROOT",
3315
+ passed: false,
3316
+ error: "Environment variable not set"
3317
+ });
3318
+ allPassed = false;
3319
+ }
3320
+ const claudeCheck = checkCommandExists("claude");
3321
+ checks.push({
3322
+ name: "claude CLI",
3323
+ path: claudeCheck.path,
3324
+ passed: claudeCheck.exists,
3325
+ error: claudeCheck.exists ? void 0 : "Command not found in PATH"
3326
+ });
3327
+ if (!claudeCheck.exists) allPassed = false;
3328
+ if (pluginRoot) {
3329
+ const skillRoot = getSkillRoot(pluginRoot);
3330
+ const workerPromptPath = (0, import_node_path3.join)(skillRoot, WORKER_PROMPT_RELATIVE);
3331
+ if ((0, import_node_fs3.existsSync)(workerPromptPath)) {
3332
+ checks.push({
3333
+ name: "Worker prompt",
3334
+ path: workerPromptPath,
3335
+ passed: true
3336
+ });
3337
+ } else {
3338
+ checks.push({
3339
+ name: "Worker prompt",
3340
+ path: workerPromptPath,
3341
+ passed: false,
3342
+ error: "File not found"
3343
+ });
3344
+ allPassed = false;
3345
+ }
3346
+ }
3347
+ checks.push({
3348
+ name: "Story found",
3349
+ path: `${storyInfo.storySlug} (epic: ${storyInfo.epicSlug})`,
3350
+ passed: true
3351
+ });
3352
+ if ((0, import_node_fs3.existsSync)(storyInfo.worktreePath)) {
3353
+ checks.push({
3354
+ name: "Worktree exists",
3355
+ path: storyInfo.worktreePath,
3356
+ passed: true
3357
+ });
3358
+ } else {
3359
+ checks.push({
3360
+ name: "Worktree exists",
3361
+ path: storyInfo.worktreePath,
3362
+ passed: false,
3363
+ error: "Directory not found"
3364
+ });
3365
+ allPassed = false;
3366
+ }
3367
+ if ((0, import_node_fs3.existsSync)(storyInfo.worktreePath)) {
3368
+ const storyMdPath = computeStoryPath(
3369
+ storyInfo.worktreePath,
3370
+ storyInfo.epicSlug,
3371
+ storyInfo.storySlug
3372
+ );
3373
+ if ((0, import_node_fs3.existsSync)(storyMdPath)) {
3374
+ checks.push({
3375
+ name: "story.md in worktree",
3376
+ path: storyMdPath,
3377
+ passed: true
3378
+ });
3379
+ } else {
3380
+ checks.push({
3381
+ name: "story.md in worktree",
3382
+ path: storyMdPath,
3383
+ passed: false,
3384
+ error: "File not found"
3385
+ });
3386
+ allPassed = false;
3387
+ }
3388
+ }
3389
+ return {
3390
+ success: allPassed,
3391
+ checks,
3392
+ story: {
3393
+ epicSlug: storyInfo.epicSlug,
3394
+ storySlug: storyInfo.storySlug,
3395
+ worktreePath: storyInfo.worktreePath
3396
+ }
3397
+ };
3398
+ }
3399
+ function printDryRunResults(result) {
3400
+ console.log("Dry Run Validation");
3401
+ console.log("==================\n");
3402
+ for (const check of result.checks) {
3403
+ const icon = check.passed ? "\u2713" : "\u2717";
3404
+ const status = check.passed ? "OK" : "FAILED";
3405
+ if (check.passed) {
3406
+ console.log(`${icon} ${check.name}: ${check.path || status}`);
3407
+ } else {
3408
+ console.log(`${icon} ${check.name}: ${check.error}`);
3409
+ if (check.path) {
3410
+ console.log(` Expected: ${check.path}`);
3411
+ }
3412
+ }
3413
+ }
3414
+ console.log("");
3415
+ if (result.success) {
3416
+ console.log("Dry run complete. All checks passed. Ready to implement.");
3417
+ } else {
3418
+ console.log("Dry run failed. Please fix the issues above before running implement.");
3419
+ }
3420
+ }
3231
3421
  function loadWorkerPrompt(pluginRoot) {
3232
3422
  const skillRoot = getSkillRoot(pluginRoot);
3233
3423
  const promptPath = (0, import_node_path3.join)(skillRoot, WORKER_PROMPT_RELATIVE);
@@ -3236,10 +3426,8 @@ function loadWorkerPrompt(pluginRoot) {
3236
3426
  }
3237
3427
  return (0, import_node_fs3.readFileSync)(promptPath, "utf-8");
3238
3428
  }
3239
- function buildScopeSettings(pluginRoot) {
3240
- const skillRoot = getSkillRoot(pluginRoot);
3241
- const validatorPath = (0, import_node_path3.join)(skillRoot, "scripts", "scope_validator.py");
3242
- const hookCommand = `python3 ${validatorPath}`;
3429
+ function buildScopeSettings() {
3430
+ const hookCommand = "npx @saga-ai/cli scope-validator";
3243
3431
  return {
3244
3432
  hooks: {
3245
3433
  PreToolUse: [
@@ -3337,7 +3525,7 @@ function runLoop(epicSlug, storySlug, maxCycles, maxTime, model, projectDir, plu
3337
3525
  storySlug
3338
3526
  };
3339
3527
  }
3340
- const settings = buildScopeSettings(pluginRoot);
3528
+ const settings = buildScopeSettings();
3341
3529
  const startTime = Date.now();
3342
3530
  let cycles = 0;
3343
3531
  const summaries = [];
@@ -3420,13 +3608,18 @@ Searched in: ${(0, import_node_path3.join)(projectPath, ".saga", "epics")}`);
3420
3608
  console.error("\nMake sure the story exists and has a story.md file.");
3421
3609
  process.exit(1);
3422
3610
  }
3611
+ const pluginRoot = process.env.SAGA_PLUGIN_ROOT;
3612
+ if (options.dryRun) {
3613
+ const dryRunResult = runDryRun(storyInfo, projectPath, pluginRoot);
3614
+ printDryRunResults(dryRunResult);
3615
+ process.exit(dryRunResult.success ? 0 : 1);
3616
+ }
3423
3617
  if (!(0, import_node_fs3.existsSync)(storyInfo.worktreePath)) {
3424
3618
  console.error(`Error: Worktree not found at ${storyInfo.worktreePath}`);
3425
3619
  console.error("\nThe story worktree has not been created yet.");
3426
3620
  console.error("Make sure the story was properly generated with /generate-stories.");
3427
3621
  process.exit(1);
3428
3622
  }
3429
- const pluginRoot = process.env.SAGA_PLUGIN_ROOT;
3430
3623
  if (!pluginRoot) {
3431
3624
  console.error("Error: SAGA_PLUGIN_ROOT environment variable is not set.");
3432
3625
  console.error("\nThis variable is required for the implementation script.");
@@ -3476,23 +3669,114 @@ async function dashboardCommand(options) {
3476
3669
  console.log("Note: Dashboard server implementation pending (Backend Server story)");
3477
3670
  }
3478
3671
 
3672
+ // src/commands/scope-validator.ts
3673
+ function getFilePathFromInput(toolInput) {
3674
+ try {
3675
+ const data = JSON.parse(toolInput);
3676
+ return data.file_path || data.path || null;
3677
+ } catch {
3678
+ return null;
3679
+ }
3680
+ }
3681
+ function normalizePath(path) {
3682
+ if (path.startsWith("./")) {
3683
+ return path.slice(2);
3684
+ }
3685
+ return path;
3686
+ }
3687
+ function isArchiveAccess(path) {
3688
+ return path.includes(".saga/archive");
3689
+ }
3690
+ function checkStoryAccess(path, allowedEpic, allowedStory) {
3691
+ if (!path.includes(".saga/epics/")) {
3692
+ return true;
3693
+ }
3694
+ const parts = path.split("/");
3695
+ const epicsIdx = parts.indexOf("epics");
3696
+ if (epicsIdx === -1) {
3697
+ return true;
3698
+ }
3699
+ if (parts.length <= epicsIdx + 1) {
3700
+ return true;
3701
+ }
3702
+ const pathEpic = parts[epicsIdx + 1];
3703
+ if (parts.length > epicsIdx + 3 && parts[epicsIdx + 2] === "stories") {
3704
+ const pathStory = parts[epicsIdx + 3];
3705
+ return pathEpic === allowedEpic && pathStory === allowedStory;
3706
+ } else {
3707
+ return pathEpic === allowedEpic;
3708
+ }
3709
+ }
3710
+ function printScopeViolation(filePath, epicSlug, storySlug, reason) {
3711
+ console.error(`SCOPE VIOLATION: ${reason}
3712
+
3713
+ Attempted path: ${filePath}
3714
+
3715
+ Your scope is limited to:
3716
+ Epic: ${epicSlug}
3717
+ Story: ${storySlug}
3718
+ Allowed: .saga/epics/${epicSlug}/stories/${storySlug}/
3719
+
3720
+ To access other stories, start a new /implement session for that story.`);
3721
+ }
3722
+ async function scopeValidatorCommand() {
3723
+ const epicSlug = process.env.SAGA_EPIC_SLUG || "";
3724
+ const storySlug = process.env.SAGA_STORY_SLUG || "";
3725
+ if (!epicSlug || !storySlug) {
3726
+ console.error(
3727
+ "ERROR: scope-validator requires SAGA_EPIC_SLUG and SAGA_STORY_SLUG environment variables"
3728
+ );
3729
+ process.exit(2);
3730
+ }
3731
+ const chunks = [];
3732
+ for await (const chunk of process.stdin) {
3733
+ chunks.push(chunk);
3734
+ }
3735
+ const toolInput = Buffer.concat(chunks).toString("utf-8");
3736
+ const filePath = getFilePathFromInput(toolInput);
3737
+ if (!filePath) {
3738
+ process.exit(0);
3739
+ }
3740
+ const normPath = normalizePath(filePath);
3741
+ if (isArchiveAccess(normPath)) {
3742
+ printScopeViolation(
3743
+ filePath,
3744
+ epicSlug,
3745
+ storySlug,
3746
+ "Access to archive folder blocked\nReason: The archive folder contains completed stories and is read-only during execution."
3747
+ );
3748
+ process.exit(2);
3749
+ }
3750
+ if (!checkStoryAccess(normPath, epicSlug, storySlug)) {
3751
+ printScopeViolation(
3752
+ filePath,
3753
+ epicSlug,
3754
+ storySlug,
3755
+ "Access to other story blocked\nReason: Workers can only access their assigned story's files."
3756
+ );
3757
+ process.exit(2);
3758
+ }
3759
+ process.exit(0);
3760
+ }
3761
+
3479
3762
  // src/cli.ts
3480
3763
  var packageJsonPath = (0, import_node_path4.join)(__dirname, "..", "package.json");
3481
3764
  var packageJson = JSON.parse((0, import_node_fs4.readFileSync)(packageJsonPath, "utf-8"));
3482
3765
  var program2 = new Command();
3483
- program2.name("saga").description("CLI for SAGA - Structured Autonomous Goal Achievement").version(packageJson.version);
3766
+ program2.name("saga").description("CLI for SAGA - Structured Autonomous Goal Achievement").version(packageJson.version).addHelpCommand("help [command]", "Display help for a command");
3484
3767
  program2.option("-p, --path <dir>", "Path to SAGA project directory (overrides auto-discovery)");
3485
- program2.command("init").description("Initialize .saga/ directory structure").action(async () => {
3768
+ program2.command("init").description("Initialize .saga/ directory structure").option("--dry-run", "Show what would be created without making changes").action(async (options) => {
3486
3769
  const globalOpts = program2.opts();
3487
- await initCommand({ path: globalOpts.path });
3770
+ await initCommand({ path: globalOpts.path, dryRun: options.dryRun });
3488
3771
  });
3489
- program2.command("implement <story-slug>").description("Run story implementation").option("--max-cycles <n>", "Maximum number of implementation cycles", parseInt).option("--max-time <n>", "Maximum time in minutes", parseInt).option("--model <name>", "Model to use for implementation").action(async (storySlug, options) => {
3772
+ program2.command("implement <story-slug>").description("Run story implementation").option("--max-cycles <n>", "Maximum number of implementation cycles", parseInt).option("--max-time <n>", "Maximum time in minutes", parseInt).option("--model <name>", "Model to use for implementation").option("--dry-run", "Validate dependencies without running implementation").action(async (storySlug, options) => {
3490
3773
  const globalOpts = program2.opts();
3491
3774
  await implementCommand(storySlug, {
3492
3775
  path: globalOpts.path,
3493
3776
  maxCycles: options.maxCycles,
3494
3777
  maxTime: options.maxTime,
3495
- model: options.model
3778
+ model: options.model,
3779
+ dryRun: options.dryRun
3496
3780
  });
3497
3781
  });
3498
3782
  program2.command("dashboard").description("Start the dashboard server").option("--port <n>", "Port to run the server on (default: 3847)", parseInt).action(async (options) => {
@@ -3502,6 +3786,9 @@ program2.command("dashboard").description("Start the dashboard server").option("
3502
3786
  port: options.port
3503
3787
  });
3504
3788
  });
3789
+ program2.command("scope-validator").description("Validate file operations against story scope (internal)").action(async () => {
3790
+ await scopeValidatorCommand();
3791
+ });
3505
3792
  program2.on("command:*", (operands) => {
3506
3793
  console.error(`error: unknown command '${operands[0]}'`);
3507
3794
  process.exit(1);
package/package.json CHANGED
@@ -1,18 +1,12 @@
1
1
  {
2
2
  "name": "@saga-ai/cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "CLI for SAGA - Structured Autonomous Goal Achievement",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "saga": "./dist/cli.cjs"
8
8
  },
9
9
  "main": "./dist/cli.cjs",
10
- "scripts": {
11
- "build": "esbuild src/cli.ts --bundle --platform=node --outfile=dist/cli.cjs --format=cjs --banner:js='#!/usr/bin/env node'",
12
- "dev": "npm run build && node dist/cli.js",
13
- "test": "vitest run",
14
- "test:watch": "vitest"
15
- },
16
10
  "keywords": [
17
11
  "claude",
18
12
  "saga",
@@ -43,5 +37,12 @@
43
37
  "files": [
44
38
  "dist/",
45
39
  "README.md"
46
- ]
47
- }
40
+ ],
41
+ "scripts": {
42
+ "build": "esbuild src/cli.ts --bundle --platform=node --outfile=dist/cli.cjs --format=cjs --banner:js='#!/usr/bin/env node'",
43
+ "dev": "npm run build && node dist/cli.js",
44
+ "test": "vitest run",
45
+ "test:watch": "vitest",
46
+ "publish:npm": "pnpm build && pnpm test && pnpm publish --access public"
47
+ }
48
+ }