runwork 0.3.0 → 0.4.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.
@@ -1,2 +1,2 @@
1
1
  // Auto-generated by scripts/embed-types.ts -- do not edit
2
- export const VERSION = "0.3.0";
2
+ export const VERSION = "0.4.0";
package/dist/index.js CHANGED
@@ -9,13 +9,17 @@ import { upgradeCommand } from './commands/upgrade.js';
9
9
  import { logoutCommand } from './commands/logout.js';
10
10
  import { integrationsCommand } from './commands/integrations.js';
11
11
  import { openCommand } from './commands/open.js';
12
+ import { infoCommand } from './commands/info.js';
12
13
  import { handleGitCredentialRequest } from './git/credentials.js';
13
14
  import { VERSION } from './generated/version.js';
14
15
  const program = new Command();
15
16
  program
16
17
  .name('runwork')
17
18
  .description('Runwork CLI - local development for Runwork apps')
18
- .version(VERSION);
19
+ .version(VERSION)
20
+ .option('--json', 'Output as JSON (auto-enabled when stdout is not a TTY)');
21
+ // Info first -- it's the agent discovery entry point
22
+ program.addCommand(infoCommand);
19
23
  program.addCommand(loginCommand);
20
24
  program.addCommand(initCommand);
21
25
  program.addCommand(cloneCommand);
@@ -26,6 +30,7 @@ program.addCommand(upgradeCommand);
26
30
  program.addCommand(logoutCommand);
27
31
  program.addCommand(integrationsCommand);
28
32
  program.addCommand(openCommand);
33
+ program.addHelpText('after', '\nFor AI agents: run "runwork info --json" to discover app context and available commands.');
29
34
  const credentialHelper = program
30
35
  .command('git-credential-helper', { hidden: true })
31
36
  .argument('<action>', 'Credential action (get/store/erase)')
@@ -7,6 +7,7 @@ interface TailerOptions {
7
7
  toFile: boolean;
8
8
  intervalMs?: number;
9
9
  getFilter?: () => 'all' | 'events' | 'runtime';
10
+ useJson?: boolean;
10
11
  }
11
12
  export declare function formatLogLine(tag: string, line: string): string;
12
13
  export declare function formatRelativeTime(eventTimestamp?: string): string;
@@ -54,7 +54,7 @@ export function startLogTailer(options) {
54
54
  let seenEventIds = new Set();
55
55
  let stopped = false;
56
56
  let timer;
57
- function writeLineFiltered(line, tag) {
57
+ function writeLineFiltered(line, tag, jsonData) {
58
58
  // Always write to file (stripped of ANSI codes)
59
59
  if (toFile) {
60
60
  try {
@@ -64,9 +64,14 @@ export function startLogTailer(options) {
64
64
  // Silently ignore file write errors
65
65
  }
66
66
  }
67
- // Filter for terminal
68
67
  if (!toTerminal)
69
68
  return;
69
+ // JSON mode: emit NDJSON line
70
+ if (options.useJson && jsonData) {
71
+ process.stdout.write(JSON.stringify(jsonData) + '\n');
72
+ return;
73
+ }
74
+ // Human mode: apply filter and print colored line
70
75
  const filter = options.getFilter?.() || 'all';
71
76
  if (filter === 'events' && tag !== 'EVENT')
72
77
  return;
@@ -91,7 +96,9 @@ export function startLogTailer(options) {
91
96
  const newContent = logs.stdout.slice(lastStdoutLength);
92
97
  const lines = newContent.split('\n').filter(Boolean);
93
98
  for (const line of lines) {
94
- writeLineFiltered(formatLogLine('RUNTIME', line), 'RUNTIME');
99
+ writeLineFiltered(formatLogLine('RUNTIME', line), 'RUNTIME', {
100
+ event: 'log', type: 'runtime', message: line, timestamp: new Date().toISOString(),
101
+ });
95
102
  }
96
103
  lastStdoutLength = logs.stdout.length;
97
104
  }
@@ -99,7 +106,9 @@ export function startLogTailer(options) {
99
106
  const newContent = logs.stderr.slice(lastStderrLength);
100
107
  const lines = newContent.split('\n').filter(Boolean);
101
108
  for (const line of lines) {
102
- writeLineFiltered(formatLogLine('ERROR', line), 'ERROR');
109
+ writeLineFiltered(formatLogLine('ERROR', line), 'ERROR', {
110
+ event: 'log', type: 'error', message: line, timestamp: new Date().toISOString(),
111
+ });
103
112
  }
104
113
  lastStderrLength = logs.stderr.length;
105
114
  }
@@ -135,7 +144,9 @@ export function startLogTailer(options) {
135
144
  }
136
145
  }
137
146
  const relTime = gray(formatRelativeTime(event.timestamp));
138
- writeLineFiltered(` ${relTime} ${yellow('EVENT ')} ${event.type}: ${detail}`, 'EVENT');
147
+ writeLineFiltered(` ${relTime} ${yellow('EVENT ')} ${event.type}: ${detail}`, 'EVENT', {
148
+ event: 'log', type: 'event', eventType: event.type, detail, metadata: event.metadata || {}, timestamp: event.timestamp,
149
+ });
139
150
  seenEventIds.add(event.id);
140
151
  }
141
152
  // Cap the set size to prevent unbounded growth (keep last 200 IDs)
package/dist/types.d.ts CHANGED
@@ -26,3 +26,61 @@ export interface WorkspaceInfo {
26
26
  id: string;
27
27
  name: string;
28
28
  }
29
+ export interface RegistryEntity {
30
+ entityName: string;
31
+ appId: string;
32
+ appName: string;
33
+ schema?: Record<string, unknown>;
34
+ deploymentMode?: 'preview' | 'production';
35
+ }
36
+ export interface RegistrySchedule {
37
+ name: string;
38
+ appId: string;
39
+ appName: string;
40
+ schedule?: string;
41
+ description?: string;
42
+ deploymentMode?: 'preview' | 'production';
43
+ }
44
+ export interface RegistryWorkflow {
45
+ name: string;
46
+ appId: string;
47
+ appName: string;
48
+ description?: string;
49
+ deploymentMode?: 'preview' | 'production';
50
+ }
51
+ export interface RegistryAgent {
52
+ name: string;
53
+ appId: string;
54
+ appName: string;
55
+ type?: string;
56
+ description?: string;
57
+ deploymentMode?: 'preview' | 'production';
58
+ }
59
+ export interface RegistryEndpoint {
60
+ appId: string;
61
+ appName: string;
62
+ path: string;
63
+ method: string;
64
+ description?: string;
65
+ deploymentMode?: 'preview' | 'production';
66
+ }
67
+ export interface RegistryComponent {
68
+ componentName: string;
69
+ appId: string;
70
+ appName: string;
71
+ deploymentMode?: 'preview' | 'production';
72
+ }
73
+ export interface RegistryFileStorage {
74
+ appId: string;
75
+ appName: string;
76
+ deploymentMode?: 'preview' | 'production';
77
+ }
78
+ export interface WorkspaceAllData {
79
+ entities: RegistryEntity[];
80
+ schedules: RegistrySchedule[];
81
+ workflows: RegistryWorkflow[];
82
+ agents: RegistryAgent[];
83
+ endpoints: RegistryEndpoint[];
84
+ components: RegistryComponent[];
85
+ fileStorages: RegistryFileStorage[];
86
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,38 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { shouldOutputJson, jsonOut, jsonLine } from '../output.js';
3
+ describe('shouldOutputJson', () => {
4
+ it('returns true when json flag is true', () => {
5
+ expect(shouldOutputJson(true)).toBe(true);
6
+ });
7
+ it('returns false when json flag is false (--no-json)', () => {
8
+ expect(shouldOutputJson(false)).toBe(false);
9
+ });
10
+ it('returns false when undefined and stdout is TTY', () => {
11
+ const original = process.stdout.isTTY;
12
+ Object.defineProperty(process.stdout, 'isTTY', { value: true, writable: true });
13
+ expect(shouldOutputJson(undefined)).toBe(false);
14
+ Object.defineProperty(process.stdout, 'isTTY', { value: original, writable: true });
15
+ });
16
+ it('returns true when undefined and stdout is not TTY', () => {
17
+ const original = process.stdout.isTTY;
18
+ Object.defineProperty(process.stdout, 'isTTY', { value: false, writable: true });
19
+ expect(shouldOutputJson(undefined)).toBe(true);
20
+ Object.defineProperty(process.stdout, 'isTTY', { value: original, writable: true });
21
+ });
22
+ });
23
+ describe('jsonOut', () => {
24
+ it('writes JSON to stdout with newline', () => {
25
+ const spy = vi.spyOn(process.stdout, 'write').mockImplementation(() => true);
26
+ jsonOut({ hello: 'world' });
27
+ expect(spy).toHaveBeenCalledWith('{"hello":"world"}\n');
28
+ spy.mockRestore();
29
+ });
30
+ });
31
+ describe('jsonLine', () => {
32
+ it('writes a single NDJSON line', () => {
33
+ const spy = vi.spyOn(process.stdout, 'write').mockImplementation(() => true);
34
+ jsonLine({ event: 'test', ts: 123 });
35
+ expect(spy).toHaveBeenCalledWith('{"event":"test","ts":123}\n');
36
+ spy.mockRestore();
37
+ });
38
+ });
@@ -0,0 +1,17 @@
1
+ /**
2
+ * JSON output utilities for agent-friendly CLI.
3
+ *
4
+ * When stdout is not a TTY (piped/captured by an agent), output switches
5
+ * to machine-readable JSON automatically. The --json flag forces it.
6
+ */
7
+ /**
8
+ * Determine whether to output JSON based on:
9
+ * 1. Explicit --json flag (true)
10
+ * 2. Explicit --no-json flag (false)
11
+ * 3. Auto-detect: non-TTY stdout means agent/pipe, output JSON
12
+ */
13
+ export declare function shouldOutputJson(jsonFlag: boolean | undefined): boolean;
14
+ /** Write a single JSON object to stdout (for one-shot commands). */
15
+ export declare function jsonOut(data: unknown): void;
16
+ /** Write one NDJSON line to stdout (for streaming commands). */
17
+ export declare function jsonLine(data: unknown): void;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * JSON output utilities for agent-friendly CLI.
3
+ *
4
+ * When stdout is not a TTY (piped/captured by an agent), output switches
5
+ * to machine-readable JSON automatically. The --json flag forces it.
6
+ */
7
+ /**
8
+ * Determine whether to output JSON based on:
9
+ * 1. Explicit --json flag (true)
10
+ * 2. Explicit --no-json flag (false)
11
+ * 3. Auto-detect: non-TTY stdout means agent/pipe, output JSON
12
+ */
13
+ export function shouldOutputJson(jsonFlag) {
14
+ if (jsonFlag === true)
15
+ return true;
16
+ if (jsonFlag === false)
17
+ return false;
18
+ return !process.stdout.isTTY;
19
+ }
20
+ /** Write a single JSON object to stdout (for one-shot commands). */
21
+ export function jsonOut(data) {
22
+ process.stdout.write(JSON.stringify(data) + '\n');
23
+ }
24
+ /** Write one NDJSON line to stdout (for streaming commands). */
25
+ export function jsonLine(data) {
26
+ process.stdout.write(JSON.stringify(data) + '\n');
27
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runwork",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "CLI for Runwork: develop, preview, and deploy Runwork apps from your local machine.",
5
5
  "license": "UNLICENSED",
6
6
  "author": "Runwork <info@runwork.ai> (https://www.runwork.ai)",