@shepai/cli 1.34.0 → 1.35.0

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 (53) hide show
  1. package/dist/src/presentation/cli/commands/agent/logs.command.d.ts.map +1 -1
  2. package/dist/src/presentation/cli/commands/agent/logs.command.js +9 -124
  3. package/dist/src/presentation/cli/commands/feat/index.d.ts.map +1 -1
  4. package/dist/src/presentation/cli/commands/feat/index.js +3 -1
  5. package/dist/src/presentation/cli/commands/feat/logs.command.d.ts +15 -0
  6. package/dist/src/presentation/cli/commands/feat/logs.command.d.ts.map +1 -0
  7. package/dist/src/presentation/cli/commands/feat/logs.command.js +52 -0
  8. package/dist/src/presentation/cli/commands/log-viewer.d.ts +23 -0
  9. package/dist/src/presentation/cli/commands/log-viewer.d.ts.map +1 -0
  10. package/dist/src/presentation/cli/commands/log-viewer.js +135 -0
  11. package/dist/tsconfig.build.tsbuildinfo +1 -1
  12. package/package.json +1 -1
  13. package/web/.next/BUILD_ID +1 -1
  14. package/web/.next/build-manifest.json +2 -2
  15. package/web/.next/cache/.previewinfo +1 -1
  16. package/web/.next/cache/.rscinfo +1 -1
  17. package/web/.next/cache/config.json +3 -3
  18. package/web/.next/fallback-build-manifest.json +2 -2
  19. package/web/.next/prerender-manifest.json +3 -3
  20. package/web/.next/required-server-files.js +1 -1
  21. package/web/.next/required-server-files.json +1 -1
  22. package/web/.next/server/app/_global-error.html +2 -2
  23. package/web/.next/server/app/_global-error.rsc +1 -1
  24. package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  25. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  26. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  27. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  28. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  29. package/web/.next/server/chunks/ssr/[root-of-the-server]__f12b60be._.js +1 -1
  30. package/web/.next/server/pages/500.html +2 -2
  31. package/web/.next/server/server-reference-manifest.js +1 -1
  32. package/web/.next/server/server-reference-manifest.json +1 -1
  33. package/web/.next/standalone/src/presentation/web/.next/BUILD_ID +1 -1
  34. package/web/.next/standalone/src/presentation/web/.next/build-manifest.json +2 -2
  35. package/web/.next/standalone/src/presentation/web/.next/prerender-manifest.json +3 -3
  36. package/web/.next/standalone/src/presentation/web/.next/required-server-files.json +1 -1
  37. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.html +2 -2
  38. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.rsc +1 -1
  39. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  40. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  41. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  42. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  43. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  44. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__f12b60be._.js +1 -1
  45. package/web/.next/standalone/src/presentation/web/.next/server/pages/500.html +2 -2
  46. package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.js +1 -1
  47. package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.json +1 -1
  48. package/web/.next/standalone/src/presentation/web/server.js +1 -1
  49. package/web/.next/trace +1 -1
  50. package/web/.next/trace-build +1 -1
  51. /package/web/.next/static/{HHW4FOl5T41Xe5-ljK9pc → KCLqz5CyrZQQf9AmDGbe5}/_buildManifest.js +0 -0
  52. /package/web/.next/static/{HHW4FOl5T41Xe5-ljK9pc → KCLqz5CyrZQQf9AmDGbe5}/_clientMiddlewareManifest.json +0 -0
  53. /package/web/.next/static/{HHW4FOl5T41Xe5-ljK9pc → KCLqz5CyrZQQf9AmDGbe5}/_ssgManifest.js +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"logs.command.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/cli/commands/agent/logs.command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAgDpC,wBAAgB,iBAAiB,IAAI,OAAO,CAwH3C"}
1
+ {"version":3,"file":"logs.command.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/cli/commands/agent/logs.command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,wBAAgB,iBAAiB,IAAI,OAAO,CAgC3C"}
@@ -11,41 +11,9 @@
11
11
  import { Command } from 'commander';
12
12
  import { join } from 'node:path';
13
13
  import { homedir } from 'node:os';
14
- import { closeSync, createReadStream, existsSync, openSync, readFileSync, readSync, statSync, watch, } from 'node:fs';
15
- import { open } from 'node:fs/promises';
16
- import { colors, messages } from '../../ui/index.js';
14
+ import { messages } from '../../ui/index.js';
17
15
  import { resolveAgentRun } from './resolve-run.js';
18
- /** Read the last N lines from a file without loading it all into memory */
19
- function readTailLines(filePath, n) {
20
- const stat = statSync(filePath);
21
- // For small files (< 64KB), just read the whole thing
22
- if (stat.size < 65536) {
23
- const content = readFileSync(filePath, 'utf-8');
24
- const lines = content.split('\n');
25
- return lines.slice(Math.max(0, lines.length - n)).join('\n');
26
- }
27
- // For large files, read from the end in chunks
28
- const fd = openSync(filePath, 'r');
29
- const chunkSize = Math.min(stat.size, 8192);
30
- let found = 0;
31
- let pos = stat.size;
32
- let tail = '';
33
- const buf = Buffer.alloc(chunkSize);
34
- while (pos > 0 && found <= n) {
35
- const readSize = Math.min(chunkSize, pos);
36
- pos -= readSize;
37
- readSync(fd, buf, 0, readSize, pos);
38
- const chunk = buf.toString('utf-8', 0, readSize);
39
- tail = chunk + tail;
40
- for (const ch of chunk) {
41
- if (ch === '\n')
42
- found++;
43
- }
44
- }
45
- closeSync(fd);
46
- const lines = tail.split('\n');
47
- return lines.slice(Math.max(0, lines.length - n)).join('\n');
48
- }
16
+ import { viewLog } from '../log-viewer.js';
49
17
  export function createLogsCommand() {
50
18
  return new Command('logs')
51
19
  .description('View agent run logs')
@@ -61,97 +29,14 @@ export function createLogsCommand() {
61
29
  return;
62
30
  }
63
31
  const logPath = join(homedir(), '.shep', 'logs', `worker-${resolved.run.id}.log`);
64
- if (!existsSync(logPath)) {
65
- messages.error(`No log file found for run ${resolved.run.id.substring(0, 8)}`);
66
- messages.info(`Expected: ${logPath}`);
32
+ const ok = await viewLog({
33
+ logPath,
34
+ follow: opts.follow,
35
+ lines: parseInt(opts.lines, 10),
36
+ label: `run ${resolved.run.id.substring(0, 8)}`,
37
+ });
38
+ if (!ok) {
67
39
  process.exitCode = 1;
68
- return;
69
- }
70
- const stat = statSync(logPath);
71
- if (stat.size === 0) {
72
- messages.info(`Log file is empty for run ${colors.accent(resolved.run.id.substring(0, 8))}`);
73
- if (!opts.follow)
74
- return;
75
- // In follow mode, continue — the file will grow
76
- }
77
- const requestedLines = parseInt(opts.lines, 10);
78
- if (opts.follow) {
79
- // Print existing content first
80
- if (stat.size > 0) {
81
- if (requestedLines > 0) {
82
- process.stdout.write(readTailLines(logPath, requestedLines));
83
- if (!process.stdout.destroyed)
84
- process.stdout.write('\n');
85
- }
86
- else {
87
- // Stream existing content without loading entire file into memory
88
- await new Promise((resolve, reject) => {
89
- const stream = createReadStream(logPath, { encoding: 'utf-8' });
90
- stream.pipe(process.stdout, { end: false });
91
- stream.on('end', resolve);
92
- stream.on('error', reject);
93
- });
94
- }
95
- }
96
- // Follow new content using fs.watch (event-driven, no polling)
97
- let position = stat.size;
98
- const handle = await open(logPath, 'r');
99
- // Reusable 4KB buffer for incremental reads
100
- const readBuf = Buffer.alloc(4096);
101
- const readNewContent = async () => {
102
- try {
103
- const currentStat = statSync(logPath);
104
- while (position < currentStat.size) {
105
- const bytesToRead = Math.min(readBuf.length, currentStat.size - position);
106
- const { bytesRead } = await handle.read(readBuf, 0, bytesToRead, position);
107
- if (bytesRead === 0)
108
- break;
109
- process.stdout.write(readBuf.toString('utf-8', 0, bytesRead));
110
- position += bytesRead;
111
- }
112
- }
113
- catch {
114
- // File may have been deleted/rotated
115
- }
116
- };
117
- // Use fs.watch for efficient OS-level file change notifications
118
- const watcher = watch(logPath, { persistent: true }, () => {
119
- readNewContent();
120
- });
121
- // Fallback poll every 2s in case fs.watch misses events (NFS, etc.)
122
- const fallbackInterval = setInterval(readNewContent, 2000);
123
- const cleanup = async () => {
124
- watcher.close();
125
- clearInterval(fallbackInterval);
126
- await handle.close();
127
- };
128
- process.on('SIGINT', async () => {
129
- await cleanup();
130
- process.exit(0);
131
- });
132
- process.on('SIGTERM', async () => {
133
- await cleanup();
134
- process.exit(0);
135
- });
136
- }
137
- else {
138
- // Print mode
139
- if (requestedLines > 0) {
140
- process.stdout.write(readTailLines(logPath, requestedLines));
141
- process.stdout.write('\n');
142
- }
143
- else {
144
- // Stream the file — don't load it all into memory
145
- await new Promise((resolve, reject) => {
146
- const stream = createReadStream(logPath, { encoding: 'utf-8' });
147
- stream.pipe(process.stdout, { end: false });
148
- stream.on('end', () => {
149
- process.stdout.write('\n');
150
- resolve();
151
- });
152
- stream.on('error', reject);
153
- });
154
- }
155
40
  }
156
41
  }
157
42
  catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/cli/commands/feat/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAUpC;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAW3C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/cli/commands/feat/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAWpC;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAY3C"}
@@ -18,6 +18,7 @@ import { createResumeCommand } from './resume.command.js';
18
18
  import { createReviewCommand } from './review.command.js';
19
19
  import { createApproveCommand } from './approve.command.js';
20
20
  import { createRejectCommand } from './reject.command.js';
21
+ import { createLogsCommand } from './logs.command.js';
21
22
  /**
22
23
  * Create the feat command group
23
24
  */
@@ -31,5 +32,6 @@ export function createFeatCommand() {
31
32
  .addCommand(createResumeCommand())
32
33
  .addCommand(createReviewCommand())
33
34
  .addCommand(createApproveCommand())
34
- .addCommand(createRejectCommand());
35
+ .addCommand(createRejectCommand())
36
+ .addCommand(createLogsCommand());
35
37
  }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Feature Logs Command
3
+ *
4
+ * View log output for a feature's agent run with efficient streaming.
5
+ * Resolves the feature by ID, finds its associated agent run, then
6
+ * displays the worker log file.
7
+ *
8
+ * Usage:
9
+ * shep feat logs <id> # Print full log
10
+ * shep feat logs -f <id> # Follow (tail -f) using fs.watch
11
+ * shep feat logs -n 50 <id> # Last 50 lines
12
+ */
13
+ import { Command } from 'commander';
14
+ export declare function createLogsCommand(): Command;
15
+ //# sourceMappingURL=logs.command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logs.command.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/cli/commands/feat/logs.command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQpC,wBAAgB,iBAAiB,IAAI,OAAO,CAkC3C"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Feature Logs Command
3
+ *
4
+ * View log output for a feature's agent run with efficient streaming.
5
+ * Resolves the feature by ID, finds its associated agent run, then
6
+ * displays the worker log file.
7
+ *
8
+ * Usage:
9
+ * shep feat logs <id> # Print full log
10
+ * shep feat logs -f <id> # Follow (tail -f) using fs.watch
11
+ * shep feat logs -n 50 <id> # Last 50 lines
12
+ */
13
+ import { Command } from 'commander';
14
+ import { join } from 'node:path';
15
+ import { homedir } from 'node:os';
16
+ import { container } from '../../../../../packages/core/src/infrastructure/di/container.js';
17
+ import { ShowFeatureUseCase } from '../../../../../packages/core/src/application/use-cases/features/show-feature.use-case.js';
18
+ import { messages } from '../../ui/index.js';
19
+ import { viewLog } from '../log-viewer.js';
20
+ export function createLogsCommand() {
21
+ return new Command('logs')
22
+ .description('View feature agent logs')
23
+ .argument('<id>', 'Feature ID (or prefix)')
24
+ .option('-f, --follow', 'Follow log output (like tail -f)')
25
+ .option('-n, --lines <count>', 'Number of lines to show from the end', '0')
26
+ .action(async (id, opts) => {
27
+ try {
28
+ const useCase = container.resolve(ShowFeatureUseCase);
29
+ const feature = await useCase.execute(id);
30
+ if (!feature.agentRunId) {
31
+ messages.error(`Feature "${feature.name}" has no agent run`);
32
+ process.exitCode = 1;
33
+ return;
34
+ }
35
+ const logPath = join(homedir(), '.shep', 'logs', `worker-${feature.agentRunId}.log`);
36
+ const ok = await viewLog({
37
+ logPath,
38
+ follow: opts.follow,
39
+ lines: parseInt(opts.lines, 10),
40
+ label: `feature "${feature.name}"`,
41
+ });
42
+ if (!ok) {
43
+ process.exitCode = 1;
44
+ }
45
+ }
46
+ catch (error) {
47
+ const err = error instanceof Error ? error : new Error(String(error));
48
+ messages.error('Failed to read feature logs', err);
49
+ process.exitCode = 1;
50
+ }
51
+ });
52
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Shared Log Viewer
3
+ *
4
+ * Displays a log file with support for follow mode and tail lines.
5
+ * Used by both `shep agent logs` and `shep feat logs`.
6
+ */
7
+ export interface LogViewerOptions {
8
+ /** Absolute path to the log file */
9
+ logPath: string;
10
+ /** Follow log output (like tail -f) */
11
+ follow?: boolean;
12
+ /** Number of lines to show from the end (0 = all) */
13
+ lines: number;
14
+ /** Label for error messages (e.g. "run abc123", "feature my-feat") */
15
+ label: string;
16
+ }
17
+ /**
18
+ * Display a log file to stdout with optional follow mode and tail lines.
19
+ *
20
+ * @returns false if the log file doesn't exist or is empty (in non-follow mode)
21
+ */
22
+ export declare function viewLog(opts: LogViewerOptions): Promise<boolean>;
23
+ //# sourceMappingURL=log-viewer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log-viewer.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/cli/commands/log-viewer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAeH,MAAM,WAAW,gBAAgB;IAC/B,oCAAoC;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,uCAAuC;IACvC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,qDAAqD;IACrD,KAAK,EAAE,MAAM,CAAC;IACd,sEAAsE;IACtE,KAAK,EAAE,MAAM,CAAC;CACf;AAiCD;;;;GAIG;AACH,wBAAsB,OAAO,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC,CA6FtE"}
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Shared Log Viewer
3
+ *
4
+ * Displays a log file with support for follow mode and tail lines.
5
+ * Used by both `shep agent logs` and `shep feat logs`.
6
+ */
7
+ import { closeSync, createReadStream, existsSync, openSync, readFileSync, readSync, statSync, watch, } from 'node:fs';
8
+ import { open } from 'node:fs/promises';
9
+ import { messages } from '../ui/index.js';
10
+ /** Read the last N lines from a file without loading it all into memory */
11
+ function readTailLines(filePath, n) {
12
+ const stat = statSync(filePath);
13
+ // For small files (< 64KB), just read the whole thing
14
+ if (stat.size < 65536) {
15
+ const content = readFileSync(filePath, 'utf-8');
16
+ const lines = content.split('\n');
17
+ return lines.slice(Math.max(0, lines.length - n)).join('\n');
18
+ }
19
+ // For large files, read from the end in chunks
20
+ const fd = openSync(filePath, 'r');
21
+ const chunkSize = Math.min(stat.size, 8192);
22
+ let found = 0;
23
+ let pos = stat.size;
24
+ let tail = '';
25
+ const buf = Buffer.alloc(chunkSize);
26
+ while (pos > 0 && found <= n) {
27
+ const readSize = Math.min(chunkSize, pos);
28
+ pos -= readSize;
29
+ readSync(fd, buf, 0, readSize, pos);
30
+ const chunk = buf.toString('utf-8', 0, readSize);
31
+ tail = chunk + tail;
32
+ for (const ch of chunk) {
33
+ if (ch === '\n')
34
+ found++;
35
+ }
36
+ }
37
+ closeSync(fd);
38
+ const lines = tail.split('\n');
39
+ return lines.slice(Math.max(0, lines.length - n)).join('\n');
40
+ }
41
+ /**
42
+ * Display a log file to stdout with optional follow mode and tail lines.
43
+ *
44
+ * @returns false if the log file doesn't exist or is empty (in non-follow mode)
45
+ */
46
+ export async function viewLog(opts) {
47
+ const { logPath, follow, lines: requestedLines, label } = opts;
48
+ if (!existsSync(logPath)) {
49
+ messages.error(`No log file found for ${label}`);
50
+ messages.info(`Expected: ${logPath}`);
51
+ return false;
52
+ }
53
+ const stat = statSync(logPath);
54
+ if (stat.size === 0) {
55
+ messages.info(`Log file is empty for ${label}`);
56
+ if (!follow)
57
+ return false;
58
+ // In follow mode, continue — the file will grow
59
+ }
60
+ if (follow) {
61
+ // Print existing content first
62
+ if (stat.size > 0) {
63
+ if (requestedLines > 0) {
64
+ process.stdout.write(readTailLines(logPath, requestedLines));
65
+ if (!process.stdout.destroyed)
66
+ process.stdout.write('\n');
67
+ }
68
+ else {
69
+ await new Promise((resolve, reject) => {
70
+ const stream = createReadStream(logPath, { encoding: 'utf-8' });
71
+ stream.pipe(process.stdout, { end: false });
72
+ stream.on('end', resolve);
73
+ stream.on('error', reject);
74
+ });
75
+ }
76
+ }
77
+ // Follow new content using fs.watch (event-driven, no polling)
78
+ let position = stat.size;
79
+ const handle = await open(logPath, 'r');
80
+ const readBuf = Buffer.alloc(4096);
81
+ const readNewContent = async () => {
82
+ try {
83
+ const currentStat = statSync(logPath);
84
+ while (position < currentStat.size) {
85
+ const bytesToRead = Math.min(readBuf.length, currentStat.size - position);
86
+ const { bytesRead } = await handle.read(readBuf, 0, bytesToRead, position);
87
+ if (bytesRead === 0)
88
+ break;
89
+ process.stdout.write(readBuf.toString('utf-8', 0, bytesRead));
90
+ position += bytesRead;
91
+ }
92
+ }
93
+ catch {
94
+ // File may have been deleted/rotated
95
+ }
96
+ };
97
+ const watcher = watch(logPath, { persistent: true }, () => {
98
+ readNewContent();
99
+ });
100
+ // Fallback poll every 2s in case fs.watch misses events (NFS, etc.)
101
+ const fallbackInterval = setInterval(readNewContent, 2000);
102
+ const cleanup = async () => {
103
+ watcher.close();
104
+ clearInterval(fallbackInterval);
105
+ await handle.close();
106
+ };
107
+ process.on('SIGINT', async () => {
108
+ await cleanup();
109
+ process.exit(0);
110
+ });
111
+ process.on('SIGTERM', async () => {
112
+ await cleanup();
113
+ process.exit(0);
114
+ });
115
+ }
116
+ else {
117
+ // Print mode
118
+ if (requestedLines > 0) {
119
+ process.stdout.write(readTailLines(logPath, requestedLines));
120
+ process.stdout.write('\n');
121
+ }
122
+ else {
123
+ await new Promise((resolve, reject) => {
124
+ const stream = createReadStream(logPath, { encoding: 'utf-8' });
125
+ stream.pipe(process.stdout, { end: false });
126
+ stream.on('end', () => {
127
+ process.stdout.write('\n');
128
+ resolve();
129
+ });
130
+ stream.on('error', reject);
131
+ });
132
+ }
133
+ }
134
+ return true;
135
+ }