@shepai/cli 1.54.0 → 1.55.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 (91) hide show
  1. package/README.md +22 -8
  2. package/dist/packages/core/src/application/ports/output/services/daemon-service.interface.d.ts +49 -0
  3. package/dist/packages/core/src/application/ports/output/services/daemon-service.interface.d.ts.map +1 -0
  4. package/dist/packages/core/src/application/ports/output/services/daemon-service.interface.js +7 -0
  5. package/dist/packages/core/src/application/ports/output/services/index.d.ts +1 -0
  6. package/dist/packages/core/src/application/ports/output/services/index.d.ts.map +1 -1
  7. package/dist/packages/core/src/infrastructure/di/container.d.ts.map +1 -1
  8. package/dist/packages/core/src/infrastructure/di/container.js +2 -0
  9. package/dist/packages/core/src/infrastructure/persistence/sqlite/mappers/feature.mapper.js +11 -11
  10. package/dist/packages/core/src/infrastructure/services/browser-opener.service.d.ts +1 -0
  11. package/dist/packages/core/src/infrastructure/services/browser-opener.service.d.ts.map +1 -1
  12. package/dist/packages/core/src/infrastructure/services/browser-opener.service.js +4 -0
  13. package/dist/packages/core/src/infrastructure/services/daemon/daemon-pid.service.d.ts +16 -0
  14. package/dist/packages/core/src/infrastructure/services/daemon/daemon-pid.service.d.ts.map +1 -0
  15. package/dist/packages/core/src/infrastructure/services/daemon/daemon-pid.service.js +72 -0
  16. package/dist/packages/core/src/infrastructure/services/filesystem/shep-directory.service.d.ts +7 -0
  17. package/dist/packages/core/src/infrastructure/services/filesystem/shep-directory.service.d.ts.map +1 -1
  18. package/dist/packages/core/src/infrastructure/services/filesystem/shep-directory.service.js +9 -0
  19. package/dist/src/presentation/cli/commands/_serve.command.d.ts +32 -0
  20. package/dist/src/presentation/cli/commands/_serve.command.d.ts.map +1 -0
  21. package/dist/src/presentation/cli/commands/_serve.command.js +91 -0
  22. package/dist/src/presentation/cli/commands/daemon/start-daemon.d.ts +27 -0
  23. package/dist/src/presentation/cli/commands/daemon/start-daemon.d.ts.map +1 -0
  24. package/dist/src/presentation/cli/commands/daemon/start-daemon.js +135 -0
  25. package/dist/src/presentation/cli/commands/start.command.d.ts +15 -0
  26. package/dist/src/presentation/cli/commands/start.command.d.ts.map +1 -0
  27. package/dist/src/presentation/cli/commands/start.command.js +33 -0
  28. package/dist/src/presentation/cli/commands/status.command.d.ts +20 -0
  29. package/dist/src/presentation/cli/commands/status.command.d.ts.map +1 -0
  30. package/dist/src/presentation/cli/commands/status.command.js +119 -0
  31. package/dist/src/presentation/cli/commands/stop.command.d.ts +22 -0
  32. package/dist/src/presentation/cli/commands/stop.command.d.ts.map +1 -0
  33. package/dist/src/presentation/cli/commands/stop.command.js +79 -0
  34. package/dist/src/presentation/cli/index.d.ts +5 -2
  35. package/dist/src/presentation/cli/index.d.ts.map +1 -1
  36. package/dist/src/presentation/cli/index.js +23 -4
  37. package/dist/tsconfig.build.tsbuildinfo +1 -1
  38. package/package.json +1 -1
  39. package/web/.next/BUILD_ID +1 -1
  40. package/web/.next/build-manifest.json +2 -2
  41. package/web/.next/cache/.previewinfo +1 -1
  42. package/web/.next/cache/.rscinfo +1 -1
  43. package/web/.next/cache/.tsbuildinfo +1 -1
  44. package/web/.next/cache/config.json +3 -3
  45. package/web/.next/fallback-build-manifest.json +2 -2
  46. package/web/.next/prerender-manifest.json +3 -3
  47. package/web/.next/required-server-files.js +1 -1
  48. package/web/.next/required-server-files.json +1 -1
  49. package/web/.next/server/app/_global-error.html +2 -2
  50. package/web/.next/server/app/_global-error.rsc +1 -1
  51. package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  52. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  53. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  54. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  55. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  56. package/web/.next/server/app/page/server-reference-manifest.json +14 -14
  57. package/web/.next/server/app/page_client-reference-manifest.js +1 -1
  58. package/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js +2 -2
  59. package/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js.map +1 -1
  60. package/web/.next/server/chunks/ssr/[root-of-the-server]__2d1a8e37._.js +2 -2
  61. package/web/.next/server/chunks/ssr/[root-of-the-server]__2d1a8e37._.js.map +1 -1
  62. package/web/.next/server/chunks/ssr/[root-of-the-server]__6e8b5181._.js +1 -1
  63. package/web/.next/server/pages/500.html +2 -2
  64. package/web/.next/server/server-reference-manifest.js +1 -1
  65. package/web/.next/server/server-reference-manifest.json +15 -15
  66. package/web/.next/standalone/src/presentation/web/.next/BUILD_ID +1 -1
  67. package/web/.next/standalone/src/presentation/web/.next/build-manifest.json +2 -2
  68. package/web/.next/standalone/src/presentation/web/.next/prerender-manifest.json +3 -3
  69. package/web/.next/standalone/src/presentation/web/.next/required-server-files.json +1 -1
  70. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.html +2 -2
  71. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.rsc +1 -1
  72. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  73. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  74. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  75. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  76. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  77. package/web/.next/standalone/src/presentation/web/.next/server/app/page/server-reference-manifest.json +14 -14
  78. package/web/.next/standalone/src/presentation/web/.next/server/app/page_client-reference-manifest.js +1 -1
  79. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js +2 -2
  80. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__2d1a8e37._.js +2 -2
  81. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__6e8b5181._.js +1 -1
  82. package/web/.next/standalone/src/presentation/web/.next/server/pages/500.html +2 -2
  83. package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.js +1 -1
  84. package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.json +15 -15
  85. package/web/.next/standalone/src/presentation/web/server.js +1 -1
  86. package/web/.next/static/chunks/{702384d364482e4b.js → 7d66f03293ffca30.js} +2 -2
  87. package/web/.next/trace +1 -1
  88. package/web/.next/trace-build +1 -1
  89. /package/web/.next/static/{RwzxYU9D2fPc_4j5RYu0v → nEAJoHmU4elsBOt8hrgha}/_buildManifest.js +0 -0
  90. /package/web/.next/static/{RwzxYU9D2fPc_4j5RYu0v → nEAJoHmU4elsBOt8hrgha}/_clientMiddlewareManifest.json +0 -0
  91. /package/web/.next/static/{RwzxYU9D2fPc_4j5RYu0v → nEAJoHmU4elsBOt8hrgha}/_ssgManifest.js +0 -0
package/README.md CHANGED
@@ -198,7 +198,7 @@ Running AI agents is easy. **Managing many of them through a full SDLC is the ha
198
198
 
199
199
  ## Features
200
200
 
201
- > 🚧 **Under active development** — Web UI screenshots are from the design spec. Core CLI (`feat new`, `feat ls`, `feat show`, `settings`, `ui`) works today. Some commands (`mem`, `start`) are still WIP.
201
+ > 🚧 **Under active development** — Web UI screenshots are from the design spec. Core CLI (`feat new`, `feat ls`, `feat show`, `settings`, `ui`, `start`, `stop`, `status`) works today. The `mem` command is still WIP.
202
202
 
203
203
  <table>
204
204
  <tr>
@@ -338,25 +338,39 @@ Running AI agents is easy. **Managing many of them through a full SDLC is the ha
338
338
  ## Quick Start
339
339
 
340
340
  ```bash
341
- # Navigate to any repo and create a feature
342
- cd ~/projects/your-app
343
- shep feat new "Add user authentication with OAuth 2.0"
341
+ # 1. Install
342
+ npm i -g @shepai/cli
343
+
344
+ # 2. Run Shep — completes setup on first run, then opens the web UI
345
+ shep
346
+
347
+ # 3. Browser opens at http://localhost:4050 — you're in
348
+ ```
344
349
 
345
- # Open the control center to watch progress
346
- shep ui
350
+ ## Daemon Management
351
+
352
+ `shep` runs the web UI as a background daemon so your terminal stays free. Use these commands to manage it:
353
+
354
+ ```bash
355
+ shep start # Start (or re-open) the web UI daemon
356
+ shep stop # Gracefully stop the daemon
357
+ shep status # Show live PID, port, uptime, CPU%, and memory
347
358
  ```
348
359
 
349
360
  ## CLI
350
361
 
351
362
  ```
352
- shep start Start background service
363
+ shep Start daemon (onboarding on first run)
364
+ shep start [--port <number>] Start the web UI as a background daemon
365
+ shep stop Stop the running web UI daemon
366
+ shep status Show daemon status and live metrics
353
367
  shep feat new <description> Create a new feature
354
368
  shep feat new --interactive Interactive mode with human checkpoints
355
369
  shep feat new --allow-all Full autonomous mode
356
370
  shep feat ls List features across repos
357
371
  shep feat show <id> Show feature details
358
372
  shep feat preview <id> Preview feature dev environment
359
- shep ui Open the web control center
373
+ shep ui Start the web UI in the foreground
360
374
  shep settings show Display current configuration
361
375
  shep settings agent Configure AI coding agent
362
376
  shep mem add <text> --global|--repo Add agent memory
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Daemon Service Interface
3
+ *
4
+ * Output port for managing the daemon state file (~/.shep/daemon.json).
5
+ * Infrastructure layer provides the concrete DaemonPidService implementation.
6
+ */
7
+ /**
8
+ * Shape of the daemon state persisted to ~/.shep/daemon.json.
9
+ */
10
+ export interface DaemonState {
11
+ /** PID of the running daemon process */
12
+ pid: number;
13
+ /** Port the daemon web server is listening on */
14
+ port: number;
15
+ /** ISO 8601 timestamp of when the daemon was started */
16
+ startedAt: string;
17
+ }
18
+ /**
19
+ * Port interface for reading and writing daemon lifecycle state.
20
+ *
21
+ * Implementations must:
22
+ * - Write atomically (write-to-temp + rename) to prevent corruption
23
+ * - Return null from read() when no daemon.json exists (ENOENT)
24
+ * - Swallow ENOENT in delete() — safe to call when file is absent
25
+ * - Validate pid before any process.kill() call
26
+ */
27
+ export interface IDaemonService {
28
+ /**
29
+ * Read the daemon state from daemon.json.
30
+ * Returns null if the file does not exist.
31
+ */
32
+ read(): Promise<DaemonState | null>;
33
+ /**
34
+ * Atomically write daemon state to daemon.json.
35
+ * Uses write-to-temp + fs.rename to prevent partial writes.
36
+ */
37
+ write(data: DaemonState): Promise<void>;
38
+ /**
39
+ * Delete daemon.json. Safe to call when file is absent (ENOENT swallowed).
40
+ */
41
+ delete(): Promise<void>;
42
+ /**
43
+ * Check whether a process with the given PID is alive.
44
+ * Uses process.kill(pid, 0) — returns true if alive, false on ESRCH.
45
+ * Validates that pid is a positive finite integer before checking.
46
+ */
47
+ isAlive(pid: number): boolean;
48
+ }
49
+ //# sourceMappingURL=daemon-service.interface.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon-service.interface.d.ts","sourceRoot":"","sources":["../../../../../../../../packages/core/src/application/ports/output/services/daemon-service.interface.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,wCAAwC;IACxC,GAAG,EAAE,MAAM,CAAC;IACZ,iDAAiD;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,wDAAwD;IACxD,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,IAAI,IAAI,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAEpC;;;OAGG;IACH,KAAK,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAExC;;OAEG;IACH,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAExB;;;;OAIG;IACH,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CAC/B"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Daemon Service Interface
3
+ *
4
+ * Output port for managing the daemon state file (~/.shep/daemon.json).
5
+ * Infrastructure layer provides the concrete DaemonPidService implementation.
6
+ */
7
+ export {};
@@ -15,4 +15,5 @@ export type { INotificationService } from './notification-service.interface.js';
15
15
  export type { IGitPrService, CiStatus, CiStatusResult, DiffSummary, MergeStrategy, PrCreateResult, } from './git-pr-service.interface.js';
16
16
  export { GitPrError, GitPrErrorCode } from './git-pr-service.interface.js';
17
17
  export type { IIdeLauncherService, LaunchIdeInput, LaunchIdeResult, LaunchIdeSuccess, LaunchIdeFailed, } from './ide-launcher-service.interface.js';
18
+ export type { IDaemonService, DaemonState } from './daemon-service.interface.js';
18
19
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../../../packages/core/src/application/ports/output/services/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,YAAY,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,uCAAuC,CAAC;AAClG,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,wBAAwB,EACxB,4BAA4B,GAC7B,MAAM,uCAAuC,CAAC;AAC/C,YAAY,EACV,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,iCAAiC,CAAC;AACzC,YAAY,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACtE,YAAY,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAC3E,YAAY,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AACtF,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACnF,YAAY,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AACzE,YAAY,EAAE,oBAAoB,EAAE,MAAM,qCAAqC,CAAC;AAChF,YAAY,EACV,aAAa,EACb,QAAQ,EACR,cAAc,EACd,WAAW,EACX,aAAa,EACb,cAAc,GACf,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC3E,YAAY,EACV,mBAAmB,EACnB,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,eAAe,GAChB,MAAM,qCAAqC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../../../packages/core/src/application/ports/output/services/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,YAAY,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,uCAAuC,CAAC;AAClG,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,wBAAwB,EACxB,4BAA4B,GAC7B,MAAM,uCAAuC,CAAC;AAC/C,YAAY,EACV,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,iCAAiC,CAAC;AACzC,YAAY,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACtE,YAAY,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAC3E,YAAY,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AACtF,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACnF,YAAY,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AACzE,YAAY,EAAE,oBAAoB,EAAE,MAAM,qCAAqC,CAAC;AAChF,YAAY,EACV,aAAa,EACb,QAAQ,EACR,cAAc,EACd,WAAW,EACX,aAAa,EACb,cAAc,GACf,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC3E,YAAY,EACV,mBAAmB,EACnB,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,eAAe,GAChB,MAAM,qCAAqC,CAAC;AAC7C,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"container.d.ts","sourceRoot":"","sources":["../../../../../../packages/core/src/infrastructure/di/container.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,kBAAkB,CAAC;AAC1B,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AA2GrC;;;;;;GAMG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,OAAO,SAAS,CAAC,CAsPrE;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,OAAO,CAEhD;AAED;;;GAGG;AACH,OAAO,EAAE,SAAS,EAAE,CAAC"}
1
+ {"version":3,"file":"container.d.ts","sourceRoot":"","sources":["../../../../../../packages/core/src/infrastructure/di/container.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,kBAAkB,CAAC;AAC1B,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AA6GrC;;;;;;GAMG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,OAAO,SAAS,CAAC,CAuPrE;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,OAAO,CAEhD;AAED;;;GAGG;AACH,OAAO,EAAE,SAAS,EAAE,CAAC"}
@@ -24,6 +24,7 @@ import { WorktreeService } from '../services/git/worktree.service.js';
24
24
  import { ToolInstallerServiceImpl } from '../services/tool-installer/tool-installer.service.js';
25
25
  import { GitPrService } from '../services/git/git-pr.service.js';
26
26
  import { JsonDrivenIdeLauncherService } from '../services/ide-launchers/json-driven-ide-launcher.service.js';
27
+ import { DaemonPidService } from '../services/daemon/daemon-pid.service.js';
27
28
  import { AgentExecutorFactory } from '../services/agents/common/agent-executor-factory.service.js';
28
29
  import { AgentExecutorProvider } from '../services/agents/common/agent-executor-provider.service.js';
29
30
  import { StructuredAgentCallerService } from '../services/agents/common/structured-agent-caller.service.js';
@@ -132,6 +133,7 @@ export async function initializeContainer() {
132
133
  container.registerSingleton('IToolInstallerService', ToolInstallerServiceImpl);
133
134
  container.registerSingleton('IGitPrService', GitPrService);
134
135
  container.registerSingleton('IIdeLauncherService', JsonDrivenIdeLauncherService);
136
+ container.registerSingleton('IDaemonService', DaemonPidService);
135
137
  // Register agent infrastructure
136
138
  container.register('IAgentRunRepository', {
137
139
  useFactory: (c) => {
@@ -80,9 +80,9 @@ export function fromDatabase(row) {
80
80
  relatedArtifacts: JSON.parse(row.related_artifacts),
81
81
  createdAt: new Date(row.created_at),
82
82
  updatedAt: new Date(row.updated_at),
83
- ...(row.plan !== null && { plan: JSON.parse(row.plan) }),
84
- ...(row.agent_run_id !== null && { agentRunId: row.agent_run_id }),
85
- ...(row.spec_path !== null && { specPath: row.spec_path }),
83
+ ...(row.plan != null && { plan: JSON.parse(row.plan) }),
84
+ ...(row.agent_run_id != null && { agentRunId: row.agent_run_id }),
85
+ ...(row.spec_path != null && { specPath: row.spec_path }),
86
86
  // Assemble workflow flags from flat columns
87
87
  push: row.push === 1,
88
88
  openPr: row.open_pr === 1,
@@ -91,22 +91,22 @@ export function fromDatabase(row) {
91
91
  allowPlan: row.allow_plan === 1,
92
92
  allowMerge: row.allow_merge === 1,
93
93
  },
94
- ...(row.worktree_path !== null && { worktreePath: row.worktree_path }),
94
+ ...(row.worktree_path != null && { worktreePath: row.worktree_path }),
95
95
  // Repository reference
96
- ...(row.repository_id !== null && { repositoryId: row.repository_id }),
96
+ ...(row.repository_id != null && { repositoryId: row.repository_id }),
97
97
  // Assemble pr from flat columns (only when pr_url exists)
98
- ...(row.pr_url !== null && {
98
+ ...(row.pr_url != null && {
99
99
  pr: {
100
100
  url: row.pr_url,
101
101
  number: row.pr_number,
102
102
  status: row.pr_status,
103
- ...(row.commit_hash !== null && { commitHash: row.commit_hash }),
104
- ...(row.ci_status !== null && { ciStatus: row.ci_status }),
105
- ...(row.ci_fix_attempts !== null && { ciFixAttempts: row.ci_fix_attempts }),
106
- ...(row.ci_fix_history !== null && { ciFixHistory: JSON.parse(row.ci_fix_history) }),
103
+ ...(row.commit_hash != null && { commitHash: row.commit_hash }),
104
+ ...(row.ci_status != null && { ciStatus: row.ci_status }),
105
+ ...(row.ci_fix_attempts != null && { ciFixAttempts: row.ci_fix_attempts }),
106
+ ...(row.ci_fix_history != null && { ciFixHistory: JSON.parse(row.ci_fix_history) }),
107
107
  },
108
108
  }),
109
109
  // Feature dependency
110
- ...(row.parent_id !== null && { parentId: row.parent_id }),
110
+ ...(row.parent_id != null && { parentId: row.parent_id }),
111
111
  };
112
112
  }
@@ -3,6 +3,7 @@ export interface BrowserOpenerDeps {
3
3
  platform: NodeJS.Platform;
4
4
  execFile: (cmd: string, args: readonly string[], callback: (error: Error | null) => void) => ChildProcess;
5
5
  warn: (msg: string) => void;
6
+ isTTY: boolean;
6
7
  }
7
8
  export declare class BrowserOpenerService {
8
9
  private deps;
@@ -1 +1 @@
1
- {"version":3,"file":"browser-opener.service.d.ts","sourceRoot":"","sources":["../../../../../../packages/core/src/infrastructure/services/browser-opener.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0B,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAE/E,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC1B,QAAQ,EAAE,CACR,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,KACpC,YAAY,CAAC;IAClB,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7B;AAeD,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,IAAI,CAAoB;gBAEpB,IAAI,GAAE,OAAO,CAAC,iBAAiB,CAAM;IAIjD,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;CAWxB"}
1
+ {"version":3,"file":"browser-opener.service.d.ts","sourceRoot":"","sources":["../../../../../../packages/core/src/infrastructure/services/browser-opener.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0B,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAE/E,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC1B,QAAQ,EAAE,CACR,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,KACpC,YAAY,CAAC;IAClB,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5B,KAAK,EAAE,OAAO,CAAC;CAChB;AAgBD,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,IAAI,CAAoB;gBAEpB,IAAI,GAAE,OAAO,CAAC,iBAAiB,CAAM;IAIjD,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;CAcxB"}
@@ -4,6 +4,7 @@ const defaultDeps = {
4
4
  execFile: cpExecFile,
5
5
  // eslint-disable-next-line no-console
6
6
  warn: (msg) => console.warn(msg),
7
+ isTTY: !!process.stdout.isTTY,
7
8
  };
8
9
  const PLATFORM_COMMANDS = {
9
10
  darwin: { cmd: 'open', args: (url) => [url] },
@@ -16,6 +17,9 @@ export class BrowserOpenerService {
16
17
  this.deps = { ...defaultDeps, ...deps };
17
18
  }
18
19
  open(url) {
20
+ // Skip browser opening in non-interactive environments (tests, CI, piped output).
21
+ if (!this.deps.isTTY)
22
+ return;
19
23
  const entry = PLATFORM_COMMANDS[this.deps.platform];
20
24
  if (!entry)
21
25
  return;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * DaemonPidService
3
+ *
4
+ * Infrastructure service for managing the daemon state file (~/.shep/daemon.json).
5
+ * Implements IDaemonService output port.
6
+ *
7
+ * Uses atomic write (write-to-temp + rename) to prevent partial-write corruption.
8
+ */
9
+ import type { IDaemonService, DaemonState } from '../../../application/ports/output/services/daemon-service.interface.js';
10
+ export declare class DaemonPidService implements IDaemonService {
11
+ read(): Promise<DaemonState | null>;
12
+ write(data: DaemonState): Promise<void>;
13
+ delete(): Promise<void>;
14
+ isAlive(pid: number): boolean;
15
+ }
16
+ //# sourceMappingURL=daemon-pid.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon-pid.service.d.ts","sourceRoot":"","sources":["../../../../../../../packages/core/src/infrastructure/services/daemon/daemon-pid.service.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,KAAK,EACV,cAAc,EACd,WAAW,EACZ,MAAM,wEAAwE,CAAC;AAGhF,qBACa,gBAAiB,YAAW,cAAc;IAC/C,IAAI,IAAI,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAWnC,KAAK,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAOvC,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAU7B,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;CAS9B"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * DaemonPidService
3
+ *
4
+ * Infrastructure service for managing the daemon state file (~/.shep/daemon.json).
5
+ * Implements IDaemonService output port.
6
+ *
7
+ * Uses atomic write (write-to-temp + rename) to prevent partial-write corruption.
8
+ */
9
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
10
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
11
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
12
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
13
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
14
+ };
15
+ import { readFile, writeFile, unlink, rename } from 'node:fs/promises';
16
+ import { injectable } from 'tsyringe';
17
+ import { getDaemonStatePath } from '../filesystem/shep-directory.service.js';
18
+ let DaemonPidService = class DaemonPidService {
19
+ async read() {
20
+ const filePath = getDaemonStatePath();
21
+ try {
22
+ const raw = await readFile(filePath, 'utf-8');
23
+ return JSON.parse(raw);
24
+ }
25
+ catch (err) {
26
+ if (isEnoent(err))
27
+ return null;
28
+ throw err;
29
+ }
30
+ }
31
+ async write(data) {
32
+ const finalPath = getDaemonStatePath();
33
+ const tmpPath = `${finalPath}.tmp`;
34
+ await writeFile(tmpPath, JSON.stringify(data), 'utf-8');
35
+ await rename(tmpPath, finalPath);
36
+ }
37
+ async delete() {
38
+ const filePath = getDaemonStatePath();
39
+ try {
40
+ await unlink(filePath);
41
+ }
42
+ catch (err) {
43
+ if (isEnoent(err))
44
+ return;
45
+ throw err;
46
+ }
47
+ }
48
+ isAlive(pid) {
49
+ if (!isValidPid(pid))
50
+ return false;
51
+ try {
52
+ process.kill(pid, 0);
53
+ return true;
54
+ }
55
+ catch {
56
+ return false;
57
+ }
58
+ }
59
+ };
60
+ DaemonPidService = __decorate([
61
+ injectable()
62
+ ], DaemonPidService);
63
+ export { DaemonPidService };
64
+ function isEnoent(err) {
65
+ return (typeof err === 'object' &&
66
+ err !== null &&
67
+ 'code' in err &&
68
+ err.code === 'ENOENT');
69
+ }
70
+ function isValidPid(pid) {
71
+ return Number.isFinite(pid) && Number.isInteger(pid) && pid > 0;
72
+ }
@@ -19,6 +19,13 @@ export declare function getShepHomeDir(): string;
19
19
  * @returns Path to the database file
20
20
  */
21
21
  export declare function getShepDbPath(): string;
22
+ /**
23
+ * Gets the path to the daemon state file.
24
+ * Uses SHEP_HOME env var if set (for test isolation), otherwise ~/.shep/daemon.json
25
+ *
26
+ * @returns Path to daemon.json
27
+ */
28
+ export declare function getDaemonStatePath(): string;
22
29
  /**
23
30
  * Ensures the shep home directory exists with correct permissions.
24
31
  * Creates the directory if it doesn't exist.
@@ -1 +1 @@
1
- {"version":3,"file":"shep-directory.service.d.ts","sourceRoot":"","sources":["../../../../../../../packages/core/src/infrastructure/services/filesystem/shep-directory.service.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAeH;;;;;GAKG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAED;;;;GAIG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED;;;;;;;;GAQG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,CAiBzD"}
1
+ {"version":3,"file":"shep-directory.service.d.ts","sourceRoot":"","sources":["../../../../../../../packages/core/src/infrastructure/services/filesystem/shep-directory.service.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAeH;;;;;GAKG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAED;;;;GAIG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED;;;;;;;;GAQG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,CAiBzD"}
@@ -34,6 +34,15 @@ export function getShepHomeDir() {
34
34
  export function getShepDbPath() {
35
35
  return join(resolveShepHomeDir(), 'data');
36
36
  }
37
+ /**
38
+ * Gets the path to the daemon state file.
39
+ * Uses SHEP_HOME env var if set (for test isolation), otherwise ~/.shep/daemon.json
40
+ *
41
+ * @returns Path to daemon.json
42
+ */
43
+ export function getDaemonStatePath() {
44
+ return join(resolveShepHomeDir(), 'daemon.json');
45
+ }
37
46
  /**
38
47
  * Ensures the shep home directory exists with correct permissions.
39
48
  * Creates the directory if it doesn't exist.
@@ -0,0 +1,32 @@
1
+ /**
2
+ * _serve Command (hidden daemon entry point)
3
+ *
4
+ * Runs the web server in-process as a detached daemon child.
5
+ * This command is hidden from --help output and is invoked internally by
6
+ * the startDaemon() helper via child_process.spawn({detached: true}).
7
+ *
8
+ * NOTE: The DI container and settings are already initialized by index.ts
9
+ * bootstrap() before Commander dispatches to this action. This command
10
+ * only needs to start the web server and notification watcher.
11
+ *
12
+ * Lifecycle:
13
+ * 1. Start WebServerService on the provided --port
14
+ * 2. Initialize notification watcher
15
+ * 3. Block until SIGTERM or SIGINT triggers graceful shutdown
16
+ *
17
+ * The shutdown sequence mirrors ui.command.ts:
18
+ * - Set isShuttingDown flag (idempotent — prevents double-shutdown)
19
+ * - Start 5s forceExit timer (unref'd so it doesn't block)
20
+ * - Stop notification watcher
21
+ * - Stop WebServerService
22
+ * - process.exit(0)
23
+ *
24
+ * Usage (internal only):
25
+ * process.execPath _serve --port <N>
26
+ */
27
+ import { Command } from 'commander';
28
+ /**
29
+ * Create the hidden _serve command (daemon child entry point).
30
+ */
31
+ export declare function createServeCommand(): Command;
32
+ //# sourceMappingURL=_serve.command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_serve.command.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/cli/commands/_serve.command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EAAE,OAAO,EAAwB,MAAM,WAAW,CAAC;AAuB1D;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,OAAO,CAwD5C"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * _serve Command (hidden daemon entry point)
3
+ *
4
+ * Runs the web server in-process as a detached daemon child.
5
+ * This command is hidden from --help output and is invoked internally by
6
+ * the startDaemon() helper via child_process.spawn({detached: true}).
7
+ *
8
+ * NOTE: The DI container and settings are already initialized by index.ts
9
+ * bootstrap() before Commander dispatches to this action. This command
10
+ * only needs to start the web server and notification watcher.
11
+ *
12
+ * Lifecycle:
13
+ * 1. Start WebServerService on the provided --port
14
+ * 2. Initialize notification watcher
15
+ * 3. Block until SIGTERM or SIGINT triggers graceful shutdown
16
+ *
17
+ * The shutdown sequence mirrors ui.command.ts:
18
+ * - Set isShuttingDown flag (idempotent — prevents double-shutdown)
19
+ * - Start 5s forceExit timer (unref'd so it doesn't block)
20
+ * - Stop notification watcher
21
+ * - Stop WebServerService
22
+ * - process.exit(0)
23
+ *
24
+ * Usage (internal only):
25
+ * process.execPath _serve --port <N>
26
+ */
27
+ import { Command, InvalidArgumentError } from 'commander';
28
+ import { container } from '../../../../packages/core/src/infrastructure/di/container.js';
29
+ import { setVersionEnvVars } from '../../../../packages/core/src/infrastructure/services/version.service.js';
30
+ import { resolveWebDir } from '../../../../packages/core/src/infrastructure/services/web-server.service.js';
31
+ import { initializeNotificationWatcher, getNotificationWatcher, } from '../../../../packages/core/src/infrastructure/services/notifications/notification-watcher.service.js';
32
+ function parsePort(value) {
33
+ const port = parseInt(value, 10);
34
+ if (isNaN(port) || port < 1024 || port > 65535) {
35
+ throw new InvalidArgumentError('Port must be an integer between 1024 and 65535');
36
+ }
37
+ return port;
38
+ }
39
+ /**
40
+ * Create the hidden _serve command (daemon child entry point).
41
+ */
42
+ export function createServeCommand() {
43
+ const cmd = new Command('_serve')
44
+ .description('Start the web server daemon (internal use only)')
45
+ .helpOption(false)
46
+ .addHelpCommand(false)
47
+ .option('-p, --port <number>', 'Port to listen on', parsePort)
48
+ .action(async (options) => {
49
+ try {
50
+ const port = options.port ?? 4050;
51
+ const { dir, dev } = resolveWebDir();
52
+ // Set version env vars for the web UI
53
+ const versionService = container.resolve('IVersionService');
54
+ setVersionEnvVars(versionService.getVersion());
55
+ // Start the web server
56
+ const service = container.resolve('IWebServerService');
57
+ await service.start(port, dir, dev);
58
+ // Start notification watcher
59
+ const runRepo = container.resolve('IAgentRunRepository');
60
+ const phaseTimingRepo = container.resolve('IPhaseTimingRepository');
61
+ const featureRepo = container.resolve('IFeatureRepository');
62
+ const notificationService = container.resolve('INotificationService');
63
+ initializeNotificationWatcher(runRepo, phaseTimingRepo, featureRepo, notificationService);
64
+ getNotificationWatcher().start();
65
+ // Graceful shutdown handler — identical pattern to ui.command.ts
66
+ let isShuttingDown = false;
67
+ const shutdown = async () => {
68
+ if (isShuttingDown)
69
+ return;
70
+ isShuttingDown = true;
71
+ // Force exit after 5s if graceful shutdown stalls
72
+ const forceExit = setTimeout(() => process.exit(0), 5000);
73
+ forceExit.unref();
74
+ getNotificationWatcher().stop();
75
+ await service.stop();
76
+ process.exit(0);
77
+ };
78
+ process.on('SIGTERM', shutdown);
79
+ process.on('SIGINT', shutdown);
80
+ }
81
+ catch (error) {
82
+ const err = error instanceof Error ? error : new Error(String(error));
83
+ // Write to stderr so it appears in daemon logs if redirected
84
+ process.stderr.write(`[_serve] Fatal error: ${err.message}\n`);
85
+ process.exit(1);
86
+ }
87
+ });
88
+ // Mark hidden so Commander omits it from --help output
89
+ cmd._hidden = true;
90
+ return cmd;
91
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * startDaemon() — Shared daemon-spawn helper
3
+ *
4
+ * Contains the parent-side logic for starting the Shep web UI as a
5
+ * detached background daemon. Used by both:
6
+ * - The default `shep` action (index.ts)
7
+ * - The `shep start` command (start.command.ts)
8
+ *
9
+ * Flow:
10
+ * 1. Resolve available port (respects --port override)
11
+ * 2. Check if daemon is already running (idempotent — print URL and return)
12
+ * 3. Spawn the daemon with execArgv propagated (supports tsx in dev mode)
13
+ * {detached: true, stdio: ['ignore','ignore','pipe']} + child.unref()
14
+ * 4. Wait briefly to confirm the child is alive; surface stderr on crash
15
+ * 5. Write daemon.json atomically via IDaemonService
16
+ * 6. Print formatted URL to stdout
17
+ * 7. Open browser via BrowserOpenerService
18
+ */
19
+ export interface StartDaemonOptions {
20
+ port?: number;
21
+ }
22
+ /**
23
+ * Start the Shep web UI as a detached background daemon.
24
+ * Idempotent: if a daemon is already running, prints the existing URL and returns.
25
+ */
26
+ export declare function startDaemon(opts?: StartDaemonOptions): Promise<void>;
27
+ //# sourceMappingURL=start-daemon.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"start-daemon.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/cli/commands/daemon/start-daemon.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAkBH,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAAC,IAAI,GAAE,kBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,CA0F9E"}