runwork 0.10.3 → 0.10.4
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.
- package/dist/agents/__tests__/claude-code-stats.test.js +1 -0
- package/dist/agents/claude-code.js +1 -1
- package/dist/agents/cursor.js +1 -1
- package/dist/commands/clone.js +1 -1
- package/dist/commands/deploy.js +1 -1
- package/dist/commands/dev.js +22 -18
- package/dist/commands/init.js +1 -1
- package/dist/dev/__tests__/detach.test.js +77 -1
- package/dist/dev/detach.d.ts +23 -0
- package/dist/dev/detach.js +45 -0
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.js +1 -1
- package/dist/git/__tests__/credentials.test.js +1 -1
- package/dist/git/auto-commit.js +1 -1
- package/dist/git/credentials.js +1 -1
- package/dist/git/identity.js +1 -1
- package/dist/git/preflight.js +1 -1
- package/dist/git/sync.js +1 -1
- package/dist/health/checks.js +1 -1
- package/dist/template/manifest.js +1 -1
- package/dist/utils/agent-guidance.d.ts +13 -0
- package/dist/utils/agent-guidance.js +22 -7
- package/dist/utils/subprocess.d.ts +19 -0
- package/dist/utils/subprocess.js +27 -0
- package/dist/utils/which.js +1 -1
- package/package.json +1 -1
|
@@ -199,6 +199,7 @@ describe('ClaudeCodeAdapter.readVersion', () => {
|
|
|
199
199
|
expect(version).toBe('claude-code 1.0.23');
|
|
200
200
|
expect(execFileSync).toHaveBeenCalledWith('claude', ['--version'], {
|
|
201
201
|
stdio: 'pipe',
|
|
202
|
+
windowsHide: true,
|
|
202
203
|
});
|
|
203
204
|
});
|
|
204
205
|
it('returns null when claude is not installed', async () => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from 'fs';
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { homedir, platform } from 'os';
|
|
4
|
-
import { execFileSync } from '
|
|
4
|
+
import { execFileSync } from '../utils/subprocess.js';
|
|
5
5
|
import { whichBinary } from '../utils/which.js';
|
|
6
6
|
import { buildSkillMd } from './types.js';
|
|
7
7
|
import { mergeJsonMcpServers, readJsonConfig, writeJsonConfig, removeRunworkMcpServers } from './utils/json-config.js';
|
package/dist/agents/cursor.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readdirSync, unlinkSync, writeFileSync } from 'fs';
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { homedir, platform } from 'os';
|
|
4
|
-
import { execFileSync } from '
|
|
4
|
+
import { execFileSync } from '../utils/subprocess.js';
|
|
5
5
|
import { querySqlite, openWritableSqlite } from '../utils/sqlite.js';
|
|
6
6
|
import { whichBinary } from '../utils/which.js';
|
|
7
7
|
import { mergeJsonMcpServers, removeRunworkMcpServers, readJsonConfig, writeJsonConfig } from './utils/json-config.js';
|
package/dist/commands/clone.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import { execFileSync } from '
|
|
2
|
+
import { execFileSync } from '../utils/subprocess.js';
|
|
3
3
|
import { writeFileSync, mkdirSync, existsSync } from 'fs';
|
|
4
4
|
import { join, resolve } from 'path';
|
|
5
5
|
import { requireAuth } from '../auth/store.js';
|
package/dist/commands/deploy.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import { execFileSync } from '
|
|
2
|
+
import { execFileSync } from '../utils/subprocess.js';
|
|
3
3
|
import { readFileSync, existsSync } from 'fs';
|
|
4
4
|
import { requireAuth } from '../auth/store.js';
|
|
5
5
|
import { ApiClient } from '../api/client.js';
|
package/dist/commands/dev.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Command, Option } from 'commander';
|
|
2
|
-
import { execFileSync } from '
|
|
2
|
+
import { execFileSync } from '../utils/subprocess.js';
|
|
3
3
|
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
4
4
|
import { join } from 'path';
|
|
5
5
|
import { requireAuth } from '../auth/store.js';
|
|
@@ -11,7 +11,7 @@ import { requireGit } from '../git/preflight.js';
|
|
|
11
11
|
import { startLogTailer } from '../logs/tailer.js';
|
|
12
12
|
import { startPreviewUrlPoller } from '../dev/preview-url-poller.js';
|
|
13
13
|
import { buildSessionFile, getSessionState, readSessionFile, removeSessionFile, removeSessionFileIfOwned, writeSessionFile, } from '../dev/session.js';
|
|
14
|
-
import { INTERNAL_DETACHED_CHILD_FLAG, isInternalDetachedChild, runAsDetachedParent,
|
|
14
|
+
import { INTERNAL_DETACHED_CHILD_FLAG, buildChildArgs, isInternalDetachedChild, runAsDetachedParent, } from '../dev/detach.js';
|
|
15
15
|
import { stopSession } from '../dev/stop.js';
|
|
16
16
|
import { formatStartedAgo, getAttachLogPaths, getCurrentPreviewUrl, renderLogLine, resolveAttachTarget, startLogTail, startSessionFileWatch, } from '../dev/attach.js';
|
|
17
17
|
import { populateTypes } from '../types-manager.js';
|
|
@@ -164,19 +164,31 @@ export async function execDev(options) {
|
|
|
164
164
|
// fresh machine where `git config --global user.email/.name` has never
|
|
165
165
|
// been set (extremely common on Windows after a clean install).
|
|
166
166
|
ensureGitIdentity(cwd, creds);
|
|
167
|
-
// Detect user edits made outside of `runwork dev
|
|
167
|
+
// Detect user edits made outside of `runwork dev`. When this fires
|
|
168
|
+
// for an AI agent, it almost always means the agent edited code
|
|
169
|
+
// before starting dev -- the wrong order. We surface this loudly so
|
|
170
|
+
// the agent's tool-result-handling can correct course on the next
|
|
171
|
+
// task. The recommendation is canonical: start `runwork dev --detach`
|
|
172
|
+
// first, then edit. See DEV_FIRST_RULE.
|
|
168
173
|
const oldManifest = await loadManifest(cwd);
|
|
169
174
|
if (oldManifest) {
|
|
170
175
|
const userEdits = await detectUserEdits(cwd, oldManifest);
|
|
171
176
|
if (userEdits.length > 0) {
|
|
172
177
|
if (useJson) {
|
|
173
|
-
jsonLine({
|
|
178
|
+
jsonLine({
|
|
179
|
+
event: 'startup',
|
|
180
|
+
phase: 'user_edits_detected',
|
|
181
|
+
files: userEdits,
|
|
182
|
+
timestamp: ts(),
|
|
183
|
+
warning: `Detected ${userEdits.length} file(s) edited before \`runwork dev\` was running. These will be batch-synced now. Next time, start \`runwork dev --detach\` BEFORE editing so changes flow incrementally and you can verify each edit against the preview.`,
|
|
184
|
+
});
|
|
174
185
|
}
|
|
175
186
|
else {
|
|
176
187
|
console.log(`Detected ${userEdits.length} file(s) edited outside dev session:`);
|
|
177
188
|
for (const file of userEdits) {
|
|
178
189
|
console.log(` ${file}`);
|
|
179
190
|
}
|
|
191
|
+
console.log(yellow(` Next time, start \`runwork dev --detach\` BEFORE editing -- changes will sync incrementally and the preview will validate each one.`));
|
|
180
192
|
}
|
|
181
193
|
try {
|
|
182
194
|
execFileSync('git', ['add', '--', ...userEdits], { stdio: 'pipe' });
|
|
@@ -552,20 +564,12 @@ async function runDevDetachParent(opts) {
|
|
|
552
564
|
else {
|
|
553
565
|
console.log(dim('Starting dev session in background...'));
|
|
554
566
|
}
|
|
555
|
-
//
|
|
556
|
-
//
|
|
557
|
-
//
|
|
558
|
-
//
|
|
559
|
-
//
|
|
560
|
-
|
|
561
|
-
// `process.execPath` at spawn time. We forward everything from argv[1]
|
|
562
|
-
// onwards. This works for both Node (`node script.js dev --detach` ->
|
|
563
|
-
// ["script.js", "dev", "--detach"]) and Bun-standalone binaries
|
|
564
|
-
// (`runwork dev --detach` -> ["dev", "--detach"]) because in both cases
|
|
565
|
-
// re-spawning with execPath + argv.slice(1) reproduces the same
|
|
566
|
-
// invocation.
|
|
567
|
-
const userArgs = stripInternalChildFlag(process.argv.slice(1));
|
|
568
|
-
const childArgs = [...userArgs, INTERNAL_DETACHED_CHILD_FLAG];
|
|
567
|
+
// `buildChildArgs` reproduces our invocation as args for the child
|
|
568
|
+
// (which is spawned with the same `process.execPath`), with two
|
|
569
|
+
// platform-aware tweaks: it skips Bun's auto-injected virtual-FS path
|
|
570
|
+
// on Windows (which the child runtime re-injects on its own), and it
|
|
571
|
+
// ensures exactly one copy of the internal-child marker.
|
|
572
|
+
const childArgs = buildChildArgs(process.argv);
|
|
569
573
|
const outcome = await runAsDetachedParent({
|
|
570
574
|
appDir: cwd,
|
|
571
575
|
expectedAppId: config.appId,
|
package/dist/commands/init.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import { execFileSync } from '
|
|
2
|
+
import { execFileSync } from '../utils/subprocess.js';
|
|
3
3
|
import { writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
4
4
|
import { join, resolve } from 'path';
|
|
5
5
|
import { homedir } from 'os';
|
|
@@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
|
2
2
|
import * as fs from 'fs';
|
|
3
3
|
import * as os from 'os';
|
|
4
4
|
import * as path from 'path';
|
|
5
|
-
import { pollForSession, runAsDetachedParent, isInternalDetachedChild, stripInternalChildFlag, INTERNAL_DETACHED_CHILD_FLAG, } from '../detach.js';
|
|
5
|
+
import { buildChildArgs, pollForSession, runAsDetachedParent, isInternalDetachedChild, looksLikeBunStandaloneArtifact, stripInternalChildFlag, INTERNAL_DETACHED_CHILD_FLAG, } from '../detach.js';
|
|
6
6
|
import { buildSessionFile, getSessionPaths, writeSessionFile, } from '../session.js';
|
|
7
7
|
function createTmpAppDir() {
|
|
8
8
|
return fs.mkdtempSync(path.join(os.tmpdir(), 'runwork-detach-test-'));
|
|
@@ -32,6 +32,82 @@ describe('isInternalDetachedChild() / stripInternalChildFlag()', () => {
|
|
|
32
32
|
expect(stripInternalChildFlag(['dev', '--detach'])).toEqual(['dev', '--detach']);
|
|
33
33
|
});
|
|
34
34
|
});
|
|
35
|
+
describe('looksLikeBunStandaloneArtifact()', () => {
|
|
36
|
+
it('detects Bun-on-Windows virtual-FS paths (forward slashes)', () => {
|
|
37
|
+
expect(looksLikeBunStandaloneArtifact('B:/~BUN/root/runwork-windows-x64.exe')).toBe(true);
|
|
38
|
+
expect(looksLikeBunStandaloneArtifact('C:/~BUN/root/foo.exe')).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
it('detects Bun-on-Windows virtual-FS paths (backslashes)', () => {
|
|
41
|
+
expect(looksLikeBunStandaloneArtifact('B:\\~BUN\\root\\runwork.exe')).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
it('detects bunfs prefix on POSIX (defensive)', () => {
|
|
44
|
+
expect(looksLikeBunStandaloneArtifact('/$bunfs/root/runwork')).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
it('does not flag normal paths or arguments', () => {
|
|
47
|
+
expect(looksLikeBunStandaloneArtifact('dev')).toBe(false);
|
|
48
|
+
expect(looksLikeBunStandaloneArtifact('--detach')).toBe(false);
|
|
49
|
+
expect(looksLikeBunStandaloneArtifact('/usr/local/bin/runwork')).toBe(false);
|
|
50
|
+
expect(looksLikeBunStandaloneArtifact('C:/Users/Oytun/.runwork/bin/runwork.exe')).toBe(false);
|
|
51
|
+
expect(looksLikeBunStandaloneArtifact('node')).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
describe('buildChildArgs()', () => {
|
|
55
|
+
it('Node script invocation: forwards script path + user args, appends marker', () => {
|
|
56
|
+
// node /path/to/dist/index.js dev --detach
|
|
57
|
+
const argv = ['node', '/path/to/dist/index.js', 'dev', '--detach'];
|
|
58
|
+
expect(buildChildArgs(argv)).toEqual([
|
|
59
|
+
'/path/to/dist/index.js', 'dev', '--detach', INTERNAL_DETACHED_CHILD_FLAG,
|
|
60
|
+
]);
|
|
61
|
+
});
|
|
62
|
+
it('Bun standalone macOS/Linux: forwards user args directly', () => {
|
|
63
|
+
// /usr/local/bin/runwork dev --detach
|
|
64
|
+
const argv = ['/usr/local/bin/runwork', 'dev', '--detach'];
|
|
65
|
+
expect(buildChildArgs(argv)).toEqual([
|
|
66
|
+
'dev', '--detach', INTERNAL_DETACHED_CHILD_FLAG,
|
|
67
|
+
]);
|
|
68
|
+
});
|
|
69
|
+
it('Bun standalone Windows: drops the virtual-FS argv[1] before forwarding', () => {
|
|
70
|
+
// The exact failure from the field. Bun-on-Windows injects argv[1]
|
|
71
|
+
// = "B:/~BUN/root/runwork-windows-x64.exe". We must NOT forward it
|
|
72
|
+
// -- the child Bun runtime will inject its own.
|
|
73
|
+
const argv = [
|
|
74
|
+
'C:\\Users\\Oytun\\.runwork\\bin\\runwork.exe',
|
|
75
|
+
'B:/~BUN/root/runwork-windows-x64.exe',
|
|
76
|
+
'dev',
|
|
77
|
+
'--detach',
|
|
78
|
+
];
|
|
79
|
+
expect(buildChildArgs(argv)).toEqual([
|
|
80
|
+
'dev', '--detach', INTERNAL_DETACHED_CHILD_FLAG,
|
|
81
|
+
]);
|
|
82
|
+
});
|
|
83
|
+
it('strips an existing internal-child flag and re-adds exactly one', () => {
|
|
84
|
+
const argv = [
|
|
85
|
+
'/usr/local/bin/runwork',
|
|
86
|
+
'dev',
|
|
87
|
+
'--detach',
|
|
88
|
+
INTERNAL_DETACHED_CHILD_FLAG,
|
|
89
|
+
INTERNAL_DETACHED_CHILD_FLAG,
|
|
90
|
+
];
|
|
91
|
+
const out = buildChildArgs(argv);
|
|
92
|
+
expect(out.filter((a) => a === INTERNAL_DETACHED_CHILD_FLAG)).toHaveLength(1);
|
|
93
|
+
});
|
|
94
|
+
it('preserves the order of user args and only mutates argv[0..1]', () => {
|
|
95
|
+
const argv = [
|
|
96
|
+
'C:\\bin\\runwork.exe',
|
|
97
|
+
'B:/~BUN/root/runwork-windows-x64.exe',
|
|
98
|
+
'dev',
|
|
99
|
+
'--restart',
|
|
100
|
+
'--detach',
|
|
101
|
+
'--json',
|
|
102
|
+
];
|
|
103
|
+
expect(buildChildArgs(argv)).toEqual([
|
|
104
|
+
'dev', '--restart', '--detach', '--json', INTERNAL_DETACHED_CHILD_FLAG,
|
|
105
|
+
]);
|
|
106
|
+
});
|
|
107
|
+
it('handles an empty argv tail (just the binary)', () => {
|
|
108
|
+
expect(buildChildArgs(['runwork'])).toEqual([INTERNAL_DETACHED_CHILD_FLAG]);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
35
111
|
describe('pollForSession()', () => {
|
|
36
112
|
let appDir;
|
|
37
113
|
beforeEach(() => {
|
package/dist/dev/detach.d.ts
CHANGED
|
@@ -162,3 +162,26 @@ export declare function isInternalDetachedChild(argv?: readonly string[]): boole
|
|
|
162
162
|
* misconfigured wrapper).
|
|
163
163
|
*/
|
|
164
164
|
export declare function stripInternalChildFlag(args: readonly string[]): string[];
|
|
165
|
+
/**
|
|
166
|
+
* Detect a Bun standalone virtual-filesystem path. Bun's compile mode
|
|
167
|
+
* on Windows injects the in-bundle script path as `process.argv[1]`
|
|
168
|
+
* (e.g., `B:/~BUN/root/runwork-windows-x64.exe`). When we self-spawn,
|
|
169
|
+
* the child Bun runtime re-injects an equivalent entry on its own --
|
|
170
|
+
* forwarding ours causes a duplicate that downstream parsers (commander
|
|
171
|
+
* here) misread as a stray positional command. macOS and Linux Bun
|
|
172
|
+
* standalone do NOT inject this entry, but the prefix is documented in
|
|
173
|
+
* Bun source as `/$bunfs/` if it ever appears, so we detect that too
|
|
174
|
+
* defensively.
|
|
175
|
+
*/
|
|
176
|
+
export declare function looksLikeBunStandaloneArtifact(p: string): boolean;
|
|
177
|
+
/**
|
|
178
|
+
* Construct the args we should forward to the spawned child so it
|
|
179
|
+
* re-runs the same `runwork dev` invocation as the parent. Drops
|
|
180
|
+
* elements that the child runtime will re-inject on its own (notably
|
|
181
|
+
* the Bun-on-Windows virtual-FS path) and drops any pre-existing copy
|
|
182
|
+
* of the internal-child marker before we re-add exactly one.
|
|
183
|
+
*
|
|
184
|
+
* Pure function for testability -- accepts the parent's argv and returns
|
|
185
|
+
* what to hand to `spawn`. Real callers pass `process.argv`.
|
|
186
|
+
*/
|
|
187
|
+
export declare function buildChildArgs(parentArgv: readonly string[]): string[];
|
package/dist/dev/detach.js
CHANGED
|
@@ -245,3 +245,48 @@ export function isInternalDetachedChild(argv = process.argv) {
|
|
|
245
245
|
export function stripInternalChildFlag(args) {
|
|
246
246
|
return args.filter((a) => a !== INTERNAL_DETACHED_CHILD_FLAG);
|
|
247
247
|
}
|
|
248
|
+
/**
|
|
249
|
+
* Detect a Bun standalone virtual-filesystem path. Bun's compile mode
|
|
250
|
+
* on Windows injects the in-bundle script path as `process.argv[1]`
|
|
251
|
+
* (e.g., `B:/~BUN/root/runwork-windows-x64.exe`). When we self-spawn,
|
|
252
|
+
* the child Bun runtime re-injects an equivalent entry on its own --
|
|
253
|
+
* forwarding ours causes a duplicate that downstream parsers (commander
|
|
254
|
+
* here) misread as a stray positional command. macOS and Linux Bun
|
|
255
|
+
* standalone do NOT inject this entry, but the prefix is documented in
|
|
256
|
+
* Bun source as `/$bunfs/` if it ever appears, so we detect that too
|
|
257
|
+
* defensively.
|
|
258
|
+
*/
|
|
259
|
+
export function looksLikeBunStandaloneArtifact(p) {
|
|
260
|
+
// Windows: drive-letter paths under \~BUN\, e.g. "B:/~BUN/root/..." or "B:\~BUN\root\..."
|
|
261
|
+
if (/^[a-z]:[/\\]~BUN[/\\]/i.test(p))
|
|
262
|
+
return true;
|
|
263
|
+
// Linux/macOS bunfs prefix (documented; not currently emitted in user-visible argv).
|
|
264
|
+
if (p.startsWith('/$bunfs/'))
|
|
265
|
+
return true;
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Construct the args we should forward to the spawned child so it
|
|
270
|
+
* re-runs the same `runwork dev` invocation as the parent. Drops
|
|
271
|
+
* elements that the child runtime will re-inject on its own (notably
|
|
272
|
+
* the Bun-on-Windows virtual-FS path) and drops any pre-existing copy
|
|
273
|
+
* of the internal-child marker before we re-add exactly one.
|
|
274
|
+
*
|
|
275
|
+
* Pure function for testability -- accepts the parent's argv and returns
|
|
276
|
+
* what to hand to `spawn`. Real callers pass `process.argv`.
|
|
277
|
+
*/
|
|
278
|
+
export function buildChildArgs(parentArgv) {
|
|
279
|
+
// argv[0] is always the binary; the child gets it back via spawn's
|
|
280
|
+
// execPath argument. We start scanning from argv[1].
|
|
281
|
+
const rest = [];
|
|
282
|
+
for (let i = 1; i < parentArgv.length; i++) {
|
|
283
|
+
if (i === 1 && looksLikeBunStandaloneArtifact(parentArgv[i])) {
|
|
284
|
+
// Skip Bun-on-Windows's auto-injected virtual-FS path; the child
|
|
285
|
+
// runtime injects its own equivalent entry. Forwarding ours would
|
|
286
|
+
// duplicate it in the child's argv.
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
rest.push(parentArgv[i]);
|
|
290
|
+
}
|
|
291
|
+
return [...stripInternalChildFlag(rest), INTERNAL_DETACHED_CHILD_FLAG];
|
|
292
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "0.10.
|
|
1
|
+
export declare const VERSION = "0.10.4";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Auto-generated by scripts/embed-types.ts -- do not edit
|
|
2
|
-
export const VERSION = "0.10.
|
|
2
|
+
export const VERSION = "0.10.4";
|
|
@@ -60,7 +60,7 @@ describe('git/credentials', () => {
|
|
|
60
60
|
'--global',
|
|
61
61
|
'--unset',
|
|
62
62
|
'credential.https://runwork.ai.helper',
|
|
63
|
-
], { stdio: 'pipe' });
|
|
63
|
+
], { stdio: 'pipe', windowsHide: true });
|
|
64
64
|
});
|
|
65
65
|
it('ignores errors silently', async () => {
|
|
66
66
|
mockExecFileSync.mockImplementation(() => {
|
package/dist/git/auto-commit.js
CHANGED
package/dist/git/credentials.js
CHANGED
package/dist/git/identity.js
CHANGED
package/dist/git/preflight.js
CHANGED
package/dist/git/sync.js
CHANGED
package/dist/health/checks.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from 'crypto';
|
|
2
|
-
import { execFileSync } from '
|
|
2
|
+
import { execFileSync } from '../utils/subprocess.js';
|
|
3
3
|
import { readFileSync, writeFileSync, existsSync, readdirSync, mkdirSync } from 'fs';
|
|
4
4
|
import { join, relative } from 'path';
|
|
5
5
|
import { buildIgnoreSets } from '../utils/ignore-matcher.js';
|
|
@@ -24,6 +24,19 @@ export interface AgentResponse<T> {
|
|
|
24
24
|
}
|
|
25
25
|
/** Map of key template files and what they're for. */
|
|
26
26
|
export declare const APP_STRUCTURE: Record<string, string>;
|
|
27
|
+
/**
|
|
28
|
+
* The single most important workflow rule for AI agents working on a
|
|
29
|
+
* Runwork app. Repeated across multiple guides because it's the
|
|
30
|
+
* difference between a working dev cycle and a frustrating one.
|
|
31
|
+
*
|
|
32
|
+
* Rationale -- agents that don't follow this end up:
|
|
33
|
+
* 1. Editing files locally with no preview to verify against.
|
|
34
|
+
* 2. Hitting a single bulk-sync at dev startup that masks which
|
|
35
|
+
* change broke what (instead of incremental per-change syncs).
|
|
36
|
+
* 3. Risking sync conflicts when the local state has diverged from
|
|
37
|
+
* the remote in a way the auto-sync can't reconcile.
|
|
38
|
+
*/
|
|
39
|
+
export declare const DEV_FIRST_RULE = "CRITICAL: Always start `runwork dev --detach` BEFORE editing any code in this app. Dev establishes the live sync pipeline -- without it, changes do not flow to the preview, and the dev startup will batch-sync everything at once instead of giving you per-change feedback. To check whether dev is already running for this app, run `runwork info --json` and inspect `localDevSession.state` (alive / stale / none). If alive, just edit. If none or stale, run `runwork dev --detach --json` first, capture the preview URL from the `session_started` event, then proceed with edits.";
|
|
27
40
|
export declare function buildInitGuide(appName: string, dir: string): AgentGuide;
|
|
28
41
|
export declare function buildCloneGuide(appName: string, dir: string): AgentGuide;
|
|
29
42
|
export declare function buildDevSessionGuide(): AgentGuide;
|
|
@@ -18,7 +18,21 @@ export const APP_STRUCTURE = {
|
|
|
18
18
|
'blueprint.json': 'App feature registry. Update after adding entities, workflows, agents, etc.',
|
|
19
19
|
'CLAUDE.md': 'Complete framework documentation. Read this before editing anything.',
|
|
20
20
|
};
|
|
21
|
+
/**
|
|
22
|
+
* The single most important workflow rule for AI agents working on a
|
|
23
|
+
* Runwork app. Repeated across multiple guides because it's the
|
|
24
|
+
* difference between a working dev cycle and a frustrating one.
|
|
25
|
+
*
|
|
26
|
+
* Rationale -- agents that don't follow this end up:
|
|
27
|
+
* 1. Editing files locally with no preview to verify against.
|
|
28
|
+
* 2. Hitting a single bulk-sync at dev startup that masks which
|
|
29
|
+
* change broke what (instead of incremental per-change syncs).
|
|
30
|
+
* 3. Risking sync conflicts when the local state has diverged from
|
|
31
|
+
* the remote in a way the auto-sync can't reconcile.
|
|
32
|
+
*/
|
|
33
|
+
export const DEV_FIRST_RULE = 'CRITICAL: Always start `runwork dev --detach` BEFORE editing any code in this app. Dev establishes the live sync pipeline -- without it, changes do not flow to the preview, and the dev startup will batch-sync everything at once instead of giving you per-change feedback. To check whether dev is already running for this app, run `runwork info --json` and inspect `localDevSession.state` (alive / stale / none). If alive, just edit. If none or stale, run `runwork dev --detach --json` first, capture the preview URL from the `session_started` event, then proceed with edits.';
|
|
21
34
|
const COMMON_TIPS = [
|
|
35
|
+
DEV_FIRST_RULE,
|
|
22
36
|
'Read CLAUDE.md in the app directory first -- it has complete framework documentation with code examples.',
|
|
23
37
|
'You do NOT need to run git commands manually. runwork dev handles file syncing automatically. (git itself must be installed on the system -- see dependencies.)',
|
|
24
38
|
'Do NOT install external AI SDKs (openai, @anthropic-ai/sdk). Use @runworkai/framework/ai instead.',
|
|
@@ -39,8 +53,8 @@ export function buildInitGuide(appName, dir) {
|
|
|
39
53
|
structure: APP_STRUCTURE,
|
|
40
54
|
nextSteps: [
|
|
41
55
|
`cd ${dir}`,
|
|
42
|
-
'runwork dev # start
|
|
43
|
-
'
|
|
56
|
+
'runwork dev --detach --json # FIRST: start the dev sandbox in the background; capture the preview URL from the session_started event',
|
|
57
|
+
'THEN edit files for your needs (see structure above) -- changes auto-sync to the preview',
|
|
44
58
|
'runwork deploy # deploy to production when ready',
|
|
45
59
|
],
|
|
46
60
|
tips: COMMON_TIPS,
|
|
@@ -53,9 +67,9 @@ export function buildCloneGuide(appName, dir) {
|
|
|
53
67
|
structure: APP_STRUCTURE,
|
|
54
68
|
nextSteps: [
|
|
55
69
|
`cd ${dir}`,
|
|
56
|
-
'runwork dev # start
|
|
70
|
+
'runwork dev --detach --json # FIRST: start dev in the background, capture the preview URL',
|
|
57
71
|
'Review existing files to understand what is already built',
|
|
58
|
-
'
|
|
72
|
+
'THEN edit files for your needs -- changes auto-sync to the preview',
|
|
59
73
|
'runwork deploy # deploy to production when ready',
|
|
60
74
|
],
|
|
61
75
|
tips: [SYSTEM_DEPENDENCIES_NOTE, ...COMMON_TIPS],
|
|
@@ -89,17 +103,18 @@ export function buildDeployGuide() {
|
|
|
89
103
|
}
|
|
90
104
|
export function buildInfoGuide() {
|
|
91
105
|
return {
|
|
92
|
-
context: 'This shows the current state of the app: what is deployed, what integrations are connected, and what resources (entities, workflows, agents, etc.) are registered.',
|
|
106
|
+
context: 'This shows the current state of the app: what is deployed, what integrations are connected, and what resources (entities, workflows, agents, etc.) are registered. The `localDevSession` field tells you whether a dev session is already running on this machine -- check it BEFORE starting a new one.',
|
|
93
107
|
nextSteps: [
|
|
94
|
-
'
|
|
108
|
+
'If localDevSession.state == "alive": dev is already running. Use the URL in localDevSession.previewUrl and proceed with edits.',
|
|
109
|
+
'If localDevSession.state == "none" or "stale": run `runwork dev --detach --json` BEFORE editing any code. Capture the preview URL from the session_started event.',
|
|
95
110
|
'runwork deploy # deploy to production when ready',
|
|
96
111
|
],
|
|
97
112
|
tips: [
|
|
113
|
+
DEV_FIRST_RULE,
|
|
98
114
|
'Entities listed here are the data models available via Entity CRUD methods.',
|
|
99
115
|
'Workflows listed here can be triggered via their registered endpoints or schedules.',
|
|
100
116
|
'Agents listed here need frontend pages (conversational) or triggers (task) to be accessible.',
|
|
101
117
|
'Integrations with connected status are ready to use. Others need setup in workspace settings.',
|
|
102
|
-
'If preview is not active, run runwork dev to start a development session.',
|
|
103
118
|
],
|
|
104
119
|
};
|
|
105
120
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin wrappers around `child_process.execFileSync` and `spawn` that
|
|
3
|
+
* default `windowsHide: true`. On Windows, every console-subsystem
|
|
4
|
+
* subprocess we spawn (git, where.exe, registry queries) creates its
|
|
5
|
+
* own console window unless this flag is set. With our hot paths --
|
|
6
|
+
* auto-commit's git invocations, manifest's `git ls-files`, the sync
|
|
7
|
+
* loop's git rebase / fetch / push -- a missing flag visibly flashes a
|
|
8
|
+
* console window every few seconds, which Codex Desktop users see as
|
|
9
|
+
* "empty terminals keep popping up."
|
|
10
|
+
*
|
|
11
|
+
* Use these as drop-in replacements for the `child_process` exports.
|
|
12
|
+
* Existing options the caller passes still win (so you can opt back to
|
|
13
|
+
* `windowsHide: false` if you genuinely need the window, e.g. for
|
|
14
|
+
* interactive prompts -- though we don't have any of those in our
|
|
15
|
+
* subprocess paths today).
|
|
16
|
+
*/
|
|
17
|
+
import { execFileSync as cpExecFileSync, spawn as cpSpawn } from 'child_process';
|
|
18
|
+
export declare const execFileSync: typeof cpExecFileSync;
|
|
19
|
+
export declare const spawn: typeof cpSpawn;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin wrappers around `child_process.execFileSync` and `spawn` that
|
|
3
|
+
* default `windowsHide: true`. On Windows, every console-subsystem
|
|
4
|
+
* subprocess we spawn (git, where.exe, registry queries) creates its
|
|
5
|
+
* own console window unless this flag is set. With our hot paths --
|
|
6
|
+
* auto-commit's git invocations, manifest's `git ls-files`, the sync
|
|
7
|
+
* loop's git rebase / fetch / push -- a missing flag visibly flashes a
|
|
8
|
+
* console window every few seconds, which Codex Desktop users see as
|
|
9
|
+
* "empty terminals keep popping up."
|
|
10
|
+
*
|
|
11
|
+
* Use these as drop-in replacements for the `child_process` exports.
|
|
12
|
+
* Existing options the caller passes still win (so you can opt back to
|
|
13
|
+
* `windowsHide: false` if you genuinely need the window, e.g. for
|
|
14
|
+
* interactive prompts -- though we don't have any of those in our
|
|
15
|
+
* subprocess paths today).
|
|
16
|
+
*/
|
|
17
|
+
import { execFileSync as cpExecFileSync, spawn as cpSpawn, } from 'child_process';
|
|
18
|
+
// We re-export with the *exact* `typeof` signature of the originals so
|
|
19
|
+
// caller-side overload narrowing (encoding -> string vs Buffer return
|
|
20
|
+
// type) keeps working. The implementation forwards through the original
|
|
21
|
+
// after merging in `windowsHide: true` as a default.
|
|
22
|
+
export const execFileSync = ((file, args, options) => {
|
|
23
|
+
return cpExecFileSync(file, args, { windowsHide: true, ...(options ?? {}) });
|
|
24
|
+
});
|
|
25
|
+
export const spawn = ((command, args, options) => {
|
|
26
|
+
return cpSpawn(command, args ?? [], { windowsHide: true, ...(options ?? {}) });
|
|
27
|
+
});
|
package/dist/utils/which.js
CHANGED
package/package.json
CHANGED