@atolis-hq/wake 0.1.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 (75) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +140 -0
  3. package/dist/src/adapters/claude/claude-runner.js +388 -0
  4. package/dist/src/adapters/claude/prompt-templates.js +57 -0
  5. package/dist/src/adapters/codex/codex-runner.js +391 -0
  6. package/dist/src/adapters/cursor/cursor-runner.js +352 -0
  7. package/dist/src/adapters/docker/docker-cli.js +97 -0
  8. package/dist/src/adapters/fake/fake-artifact-verifier.js +14 -0
  9. package/dist/src/adapters/fake/fake-github-pull-request-activity-source.js +73 -0
  10. package/dist/src/adapters/fake/fake-resource-index.js +21 -0
  11. package/dist/src/adapters/fake/fake-runner.js +22 -0
  12. package/dist/src/adapters/fake/fake-ticketing-system.js +166 -0
  13. package/dist/src/adapters/fake/fake-workspace-manager.js +27 -0
  14. package/dist/src/adapters/fs/resource-index.js +84 -0
  15. package/dist/src/adapters/fs/self-update-ledger.js +17 -0
  16. package/dist/src/adapters/fs/state-store.js +364 -0
  17. package/dist/src/adapters/git/git-workspace-manager.js +168 -0
  18. package/dist/src/adapters/github/github-artifact-verifier.js +38 -0
  19. package/dist/src/adapters/github/github-auth.js +16 -0
  20. package/dist/src/adapters/github/github-client.js +100 -0
  21. package/dist/src/adapters/github/github-issues-work-source.js +410 -0
  22. package/dist/src/adapters/github/github-pull-request-activity-source.js +263 -0
  23. package/dist/src/adapters/http/ui-assets.js +302 -0
  24. package/dist/src/adapters/http/ui-data.js +389 -0
  25. package/dist/src/adapters/http/ui-server.js +151 -0
  26. package/dist/src/adapters/runner/cli-command.js +43 -0
  27. package/dist/src/adapters/runner/prompt-templates.js +76 -0
  28. package/dist/src/adapters/runner/runner-cli-adapter.js +112 -0
  29. package/dist/src/adapters/runner/runner-registry.js +141 -0
  30. package/dist/src/adapters/runner/stage-prompt.js +256 -0
  31. package/dist/src/adapters/runner/transcripts.js +25 -0
  32. package/dist/src/cli/correlate-command.js +62 -0
  33. package/dist/src/cli/init-command.js +11 -0
  34. package/dist/src/cli/locks-command.js +19 -0
  35. package/dist/src/cli/sandbox-command.js +191 -0
  36. package/dist/src/cli/sandbox-logging.js +30 -0
  37. package/dist/src/cli/sandbox-resume.js +77 -0
  38. package/dist/src/cli/scaffold-assets.js +173 -0
  39. package/dist/src/cli/self-update-command.js +158 -0
  40. package/dist/src/cli/startup-preflight.js +127 -0
  41. package/dist/src/cli/stop-command.js +42 -0
  42. package/dist/src/cli/ui-command.js +27 -0
  43. package/dist/src/config/defaults.js +11 -0
  44. package/dist/src/config/load-config.js +26 -0
  45. package/dist/src/core/contracts.js +1 -0
  46. package/dist/src/core/control-plane.js +127 -0
  47. package/dist/src/core/lifecycle-service.js +8 -0
  48. package/dist/src/core/policy-engine.js +200 -0
  49. package/dist/src/core/projection-updater.js +438 -0
  50. package/dist/src/core/quota-backoff.js +69 -0
  51. package/dist/src/core/sink-router.js +85 -0
  52. package/dist/src/core/tick-runner.js +1347 -0
  53. package/dist/src/domain/branch-naming.js +14 -0
  54. package/dist/src/domain/resource-uri.js +38 -0
  55. package/dist/src/domain/runner-routing.js +108 -0
  56. package/dist/src/domain/schema.js +685 -0
  57. package/dist/src/domain/sources.js +16 -0
  58. package/dist/src/domain/stages.js +39 -0
  59. package/dist/src/domain/types.js +1 -0
  60. package/dist/src/domain/workflows.js +83 -0
  61. package/dist/src/lib/clock.js +5 -0
  62. package/dist/src/lib/event-log.js +21 -0
  63. package/dist/src/lib/json-file.js +23 -0
  64. package/dist/src/lib/lock.js +118 -0
  65. package/dist/src/lib/paths.js +44 -0
  66. package/dist/src/lib/work-id.js +19 -0
  67. package/dist/src/main.js +523 -0
  68. package/dist/src/version.js +1 -0
  69. package/docker/Dockerfile +54 -0
  70. package/docker/entrypoint.sh +75 -0
  71. package/docker/log-command.sh +107 -0
  72. package/docker/setup.sh +72 -0
  73. package/package.json +52 -0
  74. package/prompts/implement.md +49 -0
  75. package/prompts/refine.md +44 -0
@@ -0,0 +1,523 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from 'node:child_process';
3
+ import { dirname, resolve } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { createDockerCli } from './adapters/docker/docker-cli.js';
6
+ import { createFileBackedFakeTicketingSystem } from './adapters/fake/fake-ticketing-system.js';
7
+ import { createFakeWorkspaceManager } from './adapters/fake/fake-workspace-manager.js';
8
+ import { createGitWorkspaceManager } from './adapters/git/git-workspace-manager.js';
9
+ import { createRunnerCliAdapter } from './adapters/runner/runner-cli-adapter.js';
10
+ import { createRegistryRunner, runnerKindForOverride, } from './adapters/runner/runner-registry.js';
11
+ import { createResourceIndex } from './adapters/fs/resource-index.js';
12
+ import { createStateStore } from './adapters/fs/state-store.js';
13
+ import { readSelfUpdateLedger, writeSelfUpdateLedger, } from './adapters/fs/self-update-ledger.js';
14
+ import { resolveGitHubToken } from './adapters/github/github-auth.js';
15
+ import { createGitHubArtifactVerifier } from './adapters/github/github-artifact-verifier.js';
16
+ import { createGitHubClient } from './adapters/github/github-client.js';
17
+ import { createGitHubIssuesWorkSource } from './adapters/github/github-issues-work-source.js';
18
+ import { createGitHubPullRequestActivitySource } from './adapters/github/github-pull-request-activity-source.js';
19
+ import { runCorrelateCommand } from './cli/correlate-command.js';
20
+ import { runInitCommand } from './cli/init-command.js';
21
+ import { runSandboxCommand } from './cli/sandbox-command.js';
22
+ import { runStartupPreflight } from './cli/startup-preflight.js';
23
+ import { runUiCommand } from './cli/ui-command.js';
24
+ import { loadWakeConfig } from './config/load-config.js';
25
+ import { createControlPlane } from './core/control-plane.js';
26
+ import { createOutboundSinkRouter, createWorkSourceFanIn } from './core/sink-router.js';
27
+ import { createTickRunner } from './core/tick-runner.js';
28
+ import { systemClock } from './lib/clock.js';
29
+ import { configuredTicketSource } from './domain/sources.js';
30
+ import { wakeVersion } from './version.js';
31
+ function commandArgsBeforeTerminator(args) {
32
+ const terminatorIndex = args.indexOf('--');
33
+ if (terminatorIndex === -1) {
34
+ return args;
35
+ }
36
+ return args.slice(0, terminatorIndex);
37
+ }
38
+ export function readFlagBeforeCommandTerminator(name, args) {
39
+ const scopedArgs = commandArgsBeforeTerminator(args);
40
+ const index = scopedArgs.indexOf(name);
41
+ if (index === -1) {
42
+ return undefined;
43
+ }
44
+ return scopedArgs[index + 1];
45
+ }
46
+ function hasFlag(name, args) {
47
+ return commandArgsBeforeTerminator(args).includes(name);
48
+ }
49
+ function resolvePackageRoot() {
50
+ return resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
51
+ }
52
+ function routesOnlyToFake(config) {
53
+ return Object.values(config.tiers).every((candidates) => {
54
+ const first = candidates[0];
55
+ return first !== undefined && config.runners[first]?.kind === 'fake';
56
+ });
57
+ }
58
+ export function formatTickFailureDetails(runRecord) {
59
+ if (runRecord === null) {
60
+ return null;
61
+ }
62
+ const summary = runRecord.summary?.trim();
63
+ const exitCode = runRecord.metadata !== undefined && typeof runRecord.metadata.exitCode === 'number'
64
+ ? runRecord.metadata.exitCode
65
+ : undefined;
66
+ const stderr = runRecord.metadata !== undefined && typeof runRecord.metadata.stderr === 'string'
67
+ ? runRecord.metadata.stderr.trim()
68
+ : '';
69
+ if (summary === undefined && exitCode === undefined && stderr.length === 0) {
70
+ return null;
71
+ }
72
+ const lines = ['Tick failure details:', `runId: ${runRecord.runId}`];
73
+ if (exitCode !== undefined) {
74
+ lines.push(`exitCode: ${exitCode}`);
75
+ }
76
+ if (summary !== undefined) {
77
+ lines.push('', 'Summary:', summary);
78
+ }
79
+ if (stderr.length > 0) {
80
+ lines.push('', 'stderr:', stderr);
81
+ }
82
+ return lines.join('\n');
83
+ }
84
+ async function runCommand(command, args, env = process.env) {
85
+ await new Promise((resolveRun, reject) => {
86
+ const child = spawn(command, args, {
87
+ cwd: process.cwd(),
88
+ env,
89
+ stdio: 'inherit',
90
+ });
91
+ child.on('error', reject);
92
+ child.on('close', (exitCode) => {
93
+ if (exitCode === 0) {
94
+ resolveRun();
95
+ return;
96
+ }
97
+ reject(new Error(`${command} ${args.join(' ')} failed with exit code ${exitCode ?? 1}`));
98
+ });
99
+ });
100
+ }
101
+ function parseGithubRepoSlug(remoteUrl) {
102
+ const match = /github\.com[:/]([^/]+\/[^/]+?)(?:\.git)?$/.exec(remoteUrl.trim());
103
+ if (match === null) {
104
+ throw new Error(`Could not parse a GitHub owner/repo from origin remote: ${remoteUrl}`);
105
+ }
106
+ return match[1];
107
+ }
108
+ async function runCommandCapture(command, args) {
109
+ return await new Promise((resolveRun, reject) => {
110
+ const child = spawn(command, args, {
111
+ cwd: process.cwd(),
112
+ env: process.env,
113
+ stdio: ['ignore', 'pipe', 'inherit'],
114
+ });
115
+ let stdout = '';
116
+ child.stdout.on('data', (chunk) => {
117
+ stdout += chunk.toString();
118
+ });
119
+ child.on('error', reject);
120
+ child.on('close', (exitCode) => {
121
+ if (exitCode === 0) {
122
+ resolveRun(stdout);
123
+ return;
124
+ }
125
+ reject(new Error(`${command} ${args.join(' ')} failed with exit code ${exitCode ?? 1}`));
126
+ });
127
+ });
128
+ }
129
+ async function inspectDockerImage(image) {
130
+ return await new Promise((resolveInspect, reject) => {
131
+ const child = spawn('docker', ['image', 'inspect', image], {
132
+ cwd: process.cwd(),
133
+ env: process.env,
134
+ stdio: 'ignore',
135
+ });
136
+ child.on('error', reject);
137
+ child.on('close', (exitCode) => {
138
+ resolveInspect(exitCode === 0);
139
+ });
140
+ });
141
+ }
142
+ async function inspectDockerContainer(containerName) {
143
+ return await new Promise((resolveInspect, reject) => {
144
+ const child = spawn('docker', ['container', 'inspect', '-f', '{{.State.Running}}', containerName], {
145
+ cwd: process.cwd(),
146
+ env: process.env,
147
+ stdio: ['ignore', 'pipe', 'ignore'],
148
+ });
149
+ let stdout = '';
150
+ child.stdout.on('data', (chunk) => {
151
+ stdout += chunk.toString();
152
+ });
153
+ child.on('error', reject);
154
+ child.on('close', (exitCode) => {
155
+ if (exitCode !== 0) {
156
+ resolveInspect(null);
157
+ return;
158
+ }
159
+ resolveInspect(stdout.trim() === 'true' ? 'running' : 'stopped');
160
+ });
161
+ });
162
+ }
163
+ export async function buildRuntime(args) {
164
+ const wakeRoot = resolve(readFlagBeforeCommandTerminator('--wake-root', args) ?? resolve(process.cwd(), '.wake'));
165
+ const stateStore = createStateStore({ wakeRoot });
166
+ await stateStore.ensureWakeRoot();
167
+ const config = await loadWakeConfig({
168
+ wakeRoot,
169
+ configFile: stateStore.paths.configFile,
170
+ });
171
+ await stateStore.writeConfig(config);
172
+ const resourceIndex = createResourceIndex({ paths: stateStore.paths });
173
+ const prTrackingEnabled = config.sources.github.enabled && config.sources.github.pullRequests.enabled;
174
+ // Resolved once and shared: resolveGitHubToken shells out to `gh auth
175
+ // token`, so building a separate client per consumer would triple that
176
+ // subprocess spawn on every startup for no benefit — all three consumers
177
+ // talk to the same GitHub account under the same config.
178
+ const githubClient = config.sources.github.enabled
179
+ ? createGitHubClient(await resolveGitHubToken())
180
+ : undefined;
181
+ const artifactVerifier = prTrackingEnabled && githubClient !== undefined
182
+ ? createGitHubArtifactVerifier({ client: githubClient })
183
+ : undefined;
184
+ const ticketingSystem = githubClient !== undefined
185
+ ? createGitHubIssuesWorkSource({
186
+ client: githubClient,
187
+ stateStore,
188
+ config,
189
+ resourceIndex,
190
+ now: () => systemClock.now(),
191
+ })
192
+ : await createFileBackedFakeTicketingSystem({
193
+ fixturePath: stateStore.paths.issueFixtureFile,
194
+ now: () => systemClock.now(),
195
+ });
196
+ const sourceName = configuredTicketSource(config);
197
+ const sinkName = sourceName;
198
+ const pullRequestActivitySource = prTrackingEnabled && githubClient !== undefined
199
+ ? createGitHubPullRequestActivitySource({
200
+ client: githubClient,
201
+ stateStore,
202
+ config,
203
+ resourceIndex,
204
+ now: () => systemClock.now(),
205
+ })
206
+ : null;
207
+ const workSource = createWorkSourceFanIn([
208
+ {
209
+ source: sourceName,
210
+ pollEvents: ticketingSystem.pollEvents,
211
+ },
212
+ ...(pullRequestActivitySource === null
213
+ ? []
214
+ : [{ source: 'github-pr', pollEvents: pullRequestActivitySource.pollEvents }]),
215
+ ]);
216
+ const outboundSink = createOutboundSinkRouter({
217
+ sinks: [
218
+ {
219
+ sink: sinkName,
220
+ deliverIntent: ticketingSystem.deliverIntent,
221
+ },
222
+ ...(pullRequestActivitySource === null
223
+ ? []
224
+ : [{ sink: 'github-pr', deliverIntent: pullRequestActivitySource.deliverIntent }]),
225
+ ],
226
+ config,
227
+ });
228
+ const runnerOverride = readFlagBeforeCommandTerminator('--runner', args);
229
+ if (runnerOverride !== undefined &&
230
+ runnerOverride !== 'fake' &&
231
+ config.runners[runnerOverride] === undefined) {
232
+ throw new Error(`Unsupported runner override: ${runnerOverride}`);
233
+ }
234
+ const runner = createRegistryRunner({
235
+ config,
236
+ cwd: process.cwd(),
237
+ ...(runnerOverride === undefined ? {} : { override: runnerOverride }),
238
+ });
239
+ const workspaceManager = (runnerOverride !== undefined
240
+ ? runnerKindForOverride(config, runnerOverride) === 'fake'
241
+ : routesOnlyToFake(config))
242
+ ? createFakeWorkspaceManager(stateStore.paths.workspaceRoot)
243
+ : createGitWorkspaceManager({ wakeRoot });
244
+ const tickRunner = createTickRunner({
245
+ clock: systemClock,
246
+ config,
247
+ stateStore,
248
+ workSource,
249
+ outboundSink,
250
+ runner,
251
+ workspaceManager,
252
+ resourceIndex,
253
+ ...(artifactVerifier === undefined ? {} : { artifactVerifier }),
254
+ });
255
+ return {
256
+ config,
257
+ runnerAdapter: null,
258
+ runner,
259
+ stateStore,
260
+ tickRunner,
261
+ workspaceManager,
262
+ };
263
+ }
264
+ async function runTick(args) {
265
+ const runtime = await buildRuntime(args);
266
+ const outcome = await runtime.tickRunner.runTick();
267
+ console.log(JSON.stringify(outcome, null, 2));
268
+ if (outcome.status !== 'processed' || outcome.sentinel !== 'FAILED' || outcome.runId === undefined) {
269
+ return;
270
+ }
271
+ const runRecord = await runtime.stateStore.readRunRecord(outcome.runId);
272
+ const details = formatTickFailureDetails(runRecord);
273
+ if (details !== null) {
274
+ console.error(details);
275
+ }
276
+ }
277
+ async function runStart(args) {
278
+ const runtime = await buildRuntime(args);
279
+ const runnerOverride = readFlagBeforeCommandTerminator('--runner', args);
280
+ await runStartupPreflight(runtime.config, {
281
+ ...(runnerOverride === undefined ? {} : { runnerOverride }),
282
+ workspaceManager: runtime.workspaceManager,
283
+ });
284
+ const controlPlane = createControlPlane({
285
+ tickRunner: runtime.tickRunner,
286
+ intervalMs: runtime.config.scheduler.intervalMs,
287
+ maxIntervalMs: runtime.config.scheduler.maxIntervalMs,
288
+ isPaused: () => runtime.stateStore.isPaused(),
289
+ logger: {
290
+ info(message) {
291
+ console.log(message);
292
+ },
293
+ error(message) {
294
+ console.error(message);
295
+ },
296
+ },
297
+ sleep(ms) {
298
+ return new Promise((resolveSleep) => {
299
+ setTimeout(resolveSleep, ms);
300
+ });
301
+ },
302
+ });
303
+ const stop = () => controlPlane.stop();
304
+ process.on('SIGINT', stop);
305
+ process.on('SIGTERM', stop);
306
+ await controlPlane.start();
307
+ }
308
+ function resolveSmokEntry(config, kind) {
309
+ for (const entry of Object.values(config.runners)) {
310
+ if (entry.kind === 'fake') {
311
+ continue;
312
+ }
313
+ if (kind === undefined || entry.kind === kind) {
314
+ return entry;
315
+ }
316
+ }
317
+ return null;
318
+ }
319
+ async function runUi(args) {
320
+ const wakeRoot = resolve(readFlagBeforeCommandTerminator('--wake-root', args) ?? resolve(process.cwd(), '.wake'));
321
+ const stateStore = createStateStore({ wakeRoot });
322
+ await stateStore.ensureWakeRoot();
323
+ const config = await loadWakeConfig({
324
+ wakeRoot,
325
+ configFile: stateStore.paths.configFile,
326
+ });
327
+ const server = await runUiCommand({
328
+ args,
329
+ stateStore,
330
+ resourceIndex: createResourceIndex({ paths: stateStore.paths }),
331
+ config,
332
+ readFlag: readFlagBeforeCommandTerminator,
333
+ });
334
+ const stop = () => {
335
+ void server.close().then(() => process.exit(0));
336
+ };
337
+ process.on('SIGINT', stop);
338
+ process.on('SIGTERM', stop);
339
+ await new Promise(() => { });
340
+ }
341
+ async function runCorrelate(args) {
342
+ const wakeRoot = resolve(readFlagBeforeCommandTerminator('--wake-root', args) ?? resolve(process.cwd(), '.wake'));
343
+ const stateStore = createStateStore({ wakeRoot });
344
+ await stateStore.ensureWakeRoot();
345
+ await runCorrelateCommand({
346
+ args,
347
+ stateStore,
348
+ resourceIndex: createResourceIndex({ paths: stateStore.paths }),
349
+ clock: systemClock,
350
+ readFlag: readFlagBeforeCommandTerminator,
351
+ });
352
+ }
353
+ async function runSmoke(args) {
354
+ const runtime = await buildRuntime(args);
355
+ const explicitKind = args[0] === 'claude' || args[0] === 'codex' || args[0] === 'cursor' ? args[0] : undefined;
356
+ const smokeArgs = explicitKind === undefined ? args : args.slice(1);
357
+ const entry = resolveSmokEntry(runtime.config, explicitKind);
358
+ if (entry === null) {
359
+ throw new Error('Smoke tests require a real runner entry (`claude`, `codex`, or `cursor`) in config.runners.');
360
+ }
361
+ const runnerAdapter = createRunnerCliAdapter({
362
+ entry,
363
+ cwd: process.cwd(),
364
+ });
365
+ const result = await runnerAdapter.smoke(smokeArgs);
366
+ console.log(JSON.stringify(result, null, 2));
367
+ }
368
+ export async function dispatchMainCommand(input) {
369
+ const command = input.args[0] ?? 'tick';
370
+ if (command === '--version' || command === '-v' || command === 'version') {
371
+ console.log(wakeVersion);
372
+ return;
373
+ }
374
+ if (command === 'tick') {
375
+ await input.runTick(input.args.slice(1));
376
+ return;
377
+ }
378
+ if (command === 'start') {
379
+ await input.runStart(input.args.slice(1));
380
+ return;
381
+ }
382
+ if (command === 'init') {
383
+ await input.runInit(input.args.slice(1));
384
+ return;
385
+ }
386
+ if (command === 'sandbox') {
387
+ await input.runSandbox(input.args.slice(1));
388
+ return;
389
+ }
390
+ if (command === 'stop') {
391
+ await input.runSandbox(['stop', ...input.args.slice(1)]);
392
+ return;
393
+ }
394
+ if (command === 'smoke') {
395
+ await input.runSmoke(input.args.slice(1));
396
+ return;
397
+ }
398
+ if (command === 'ui') {
399
+ await input.runUi(input.args.slice(1));
400
+ return;
401
+ }
402
+ if (command === 'correlate') {
403
+ await input.runCorrelate(input.args.slice(1));
404
+ return;
405
+ }
406
+ throw new Error(`Unknown command: ${input.args.join(' ')}`);
407
+ }
408
+ async function main() {
409
+ const args = process.argv.slice(2);
410
+ await dispatchMainCommand({
411
+ args,
412
+ runInit: async (commandArgs) => {
413
+ await runInitCommand({
414
+ cwd: process.cwd(),
415
+ args: commandArgs,
416
+ repoRoot: resolvePackageRoot(),
417
+ });
418
+ },
419
+ runSandbox: async (commandArgs) => {
420
+ const wakeRoot = resolve(readFlagBeforeCommandTerminator('--wake-root', commandArgs) ?? process.cwd());
421
+ const stateStore = createStateStore({ wakeRoot });
422
+ await stateStore.ensureWakeRoot();
423
+ const config = await loadWakeConfig({
424
+ wakeRoot,
425
+ configFile: stateStore.paths.configFile,
426
+ });
427
+ const docker = createDockerCli({
428
+ // BuildKit is required for the Dockerfile's cache-mount syntax
429
+ // (`RUN --mount=type=cache`), which keeps `npm`/`apt` package
430
+ // caches warm across builds even when a layer above them changes.
431
+ run: (dockerArgs) => runCommand('docker', dockerArgs, { ...process.env, DOCKER_BUILDKIT: '1' }),
432
+ inspectImage: inspectDockerImage,
433
+ inspectContainer: inspectDockerContainer,
434
+ });
435
+ const repoRoot = config.dev?.repoRoot;
436
+ const selfUpdate = commandArgs[0] === 'self-update' && repoRoot !== undefined && repoRoot.length > 0
437
+ ? {
438
+ git: {
439
+ latestTag: async () => {
440
+ await runCommand('git', ['-C', repoRoot, 'fetch', '--tags']);
441
+ const output = await runCommandCapture('git', [
442
+ '-C',
443
+ repoRoot,
444
+ 'tag',
445
+ '--list',
446
+ 'v*',
447
+ '--sort=-v:refname',
448
+ ]);
449
+ const [latest] = output.split('\n').filter((line) => line.trim().length > 0);
450
+ if (latest === undefined) {
451
+ throw new Error('No version tags found in repo');
452
+ }
453
+ return latest.trim();
454
+ },
455
+ isWorkingTreeClean: async () => {
456
+ const output = await runCommandCapture('git', [
457
+ '-C',
458
+ repoRoot,
459
+ 'status',
460
+ '--porcelain',
461
+ ]);
462
+ return output.trim().length === 0;
463
+ },
464
+ checkoutTag: async (tag) => {
465
+ await runCommand('git', ['-C', repoRoot, 'checkout', tag]);
466
+ },
467
+ },
468
+ issueReporter: {
469
+ createIssue: async (issue) => {
470
+ const remoteUrl = await runCommandCapture('git', [
471
+ '-C',
472
+ repoRoot,
473
+ 'remote',
474
+ 'get-url',
475
+ 'origin',
476
+ ]);
477
+ const repoSlug = parseGithubRepoSlug(remoteUrl);
478
+ await runCommand('gh', [
479
+ 'issue',
480
+ 'create',
481
+ '--repo',
482
+ repoSlug,
483
+ '--title',
484
+ issue.title,
485
+ '--body',
486
+ issue.body,
487
+ ]);
488
+ },
489
+ },
490
+ readLedger: () => readSelfUpdateLedger(resolve(wakeRoot, 'self-update-ledger.json')),
491
+ writeLedger: (ledger) => writeSelfUpdateLedger(resolve(wakeRoot, 'self-update-ledger.json'), ledger),
492
+ }
493
+ : undefined;
494
+ await runSandboxCommand({
495
+ args: commandArgs,
496
+ config,
497
+ wakeRoot,
498
+ containerHomeRoot: stateStore.paths.containerHomeRoot,
499
+ docker,
500
+ stateStore,
501
+ sleep: (ms) => new Promise((resolveSleep) => setTimeout(resolveSleep, ms)),
502
+ logger: {
503
+ info(message) {
504
+ console.log(message);
505
+ },
506
+ error(message) {
507
+ console.error(message);
508
+ },
509
+ },
510
+ selfUpdate,
511
+ });
512
+ },
513
+ runTick,
514
+ runStart,
515
+ runSmoke,
516
+ runUi,
517
+ runCorrelate,
518
+ });
519
+ }
520
+ main().catch((error) => {
521
+ console.error(error);
522
+ process.exitCode = 1;
523
+ });
@@ -0,0 +1 @@
1
+ export const wakeVersion = "v0.0.108";
@@ -0,0 +1,54 @@
1
+ # syntax=docker/dockerfile:1
2
+ FROM node:20-bookworm-slim
3
+
4
+ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
5
+ --mount=type=cache,target=/var/lib/apt/lists,sharing=locked \
6
+ apt-get update \
7
+ && apt-get install -y --no-install-recommends git openssh-client ca-certificates curl gnupg \
8
+ && mkdir -p /etc/apt/keyrings \
9
+ && curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
10
+ | gpg --dearmor -o /etc/apt/keyrings/githubcli-archive-keyring.gpg \
11
+ && chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \
12
+ && echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
13
+ > /etc/apt/sources.list.d/github-cli.list \
14
+ && curl -fsSL https://ngrok-agent.s3.amazonaws.com/ngrok.asc \
15
+ | gpg --dearmor -o /etc/apt/keyrings/ngrok-archive-keyring.gpg \
16
+ && chmod go+r /etc/apt/keyrings/ngrok-archive-keyring.gpg \
17
+ && echo "deb [signed-by=/etc/apt/keyrings/ngrok-archive-keyring.gpg] https://ngrok-agent.s3.amazonaws.com buster main" \
18
+ > /etc/apt/sources.list.d/ngrok.list \
19
+ && apt-get update \
20
+ && apt-get install -y --no-install-recommends gh ngrok
21
+
22
+ RUN --mount=type=cache,target=/root/.npm \
23
+ npm install -g @anthropic-ai/claude-code @openai/codex
24
+
25
+ RUN useradd --create-home --shell /bin/bash wake \
26
+ && mkdir -p /home/wake/.codex-runtime \
27
+ && mkdir -p /home/wake/.cursor \
28
+ && chown -R wake:wake /home/wake/.codex-runtime \
29
+ && chown -R wake:wake /home/wake/.cursor
30
+
31
+ ENV CODEX_HOME=/home/wake/.codex-runtime
32
+ ENV PATH=/home/wake/.local/bin:$PATH
33
+
34
+ RUN curl https://cursor.com/install -fsS | HOME=/home/wake bash \
35
+ && printf '#!/bin/bash\n[ "$1" = "agent" ] && shift\nexec ~/.local/bin/agent "$@"\n' \
36
+ > /home/wake/.local/bin/cursor \
37
+ && chmod +x /home/wake/.local/bin/cursor \
38
+ && chown -R wake:wake /home/wake/.local
39
+
40
+ WORKDIR /app
41
+ COPY package*.json ./
42
+ RUN --mount=type=cache,target=/root/.npm \
43
+ if [ -f package-lock.json ]; then npm ci; else npm install; fi
44
+
45
+ COPY . .
46
+ ARG WAKE_BUILD_TAG
47
+ RUN WAKE_BUILD_TAG="$WAKE_BUILD_TAG" npm run build && chmod +x docker/entrypoint.sh
48
+
49
+ USER wake
50
+ WORKDIR /home/wake
51
+
52
+ EXPOSE 4317
53
+
54
+ ENTRYPOINT ["/app/docker/entrypoint.sh"]
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env bash
2
+ set -uo pipefail
3
+
4
+ mkdir -p /wake/logs
5
+
6
+ write_ngrok_public_url() {
7
+ local output_file="/wake/control-plane-ui-url"
8
+
9
+ rm -f "${output_file}"
10
+ for _ in $(seq 1 30); do
11
+ local public_url
12
+ public_url="$(
13
+ node -e '
14
+ const http = require("node:http");
15
+ const req = http.get("http://127.0.0.1:4040/api/tunnels", (res) => {
16
+ let body = "";
17
+ res.setEncoding("utf8");
18
+ res.on("data", (chunk) => { body += chunk; });
19
+ res.on("end", () => {
20
+ try {
21
+ const tunnels = JSON.parse(body).tunnels ?? [];
22
+ const tunnel = tunnels.find((entry) => typeof entry.public_url === "string" && entry.public_url.startsWith("https://"))
23
+ ?? tunnels.find((entry) => typeof entry.public_url === "string");
24
+ if (tunnel !== undefined) {
25
+ process.stdout.write(tunnel.public_url);
26
+ }
27
+ } catch {}
28
+ });
29
+ });
30
+ req.on("error", () => {});
31
+ req.setTimeout(1000, () => req.destroy());
32
+ ' || true
33
+ )"
34
+
35
+ if [ -n "${public_url}" ]; then
36
+ printf '%s\n' "${public_url}" > "${output_file}"
37
+ echo "wake ui: ngrok tunnel available at ${public_url}"
38
+ return 0
39
+ fi
40
+
41
+ sleep 1
42
+ done
43
+
44
+ echo "wake ui: ngrok tunnel started but public URL was not discovered; see /wake/logs/ngrok.log"
45
+ }
46
+
47
+ if [ "${WAKE_UI_ENABLED:-false}" = "true" ]; then
48
+ echo "wake ui: starting on 0.0.0.0:${WAKE_UI_PORT:-4317}"
49
+ node /app/dist/src/main.js ui \
50
+ --wake-root /wake \
51
+ --host 0.0.0.0 \
52
+ --port "${WAKE_UI_PORT:-4317}" \
53
+ ${WAKE_UI_TOKEN:+--token "${WAKE_UI_TOKEN}"} \
54
+ >> /wake/logs/ui.log 2>&1 &
55
+
56
+ if [ "${WAKE_UI_TUNNEL_ENABLED:-false}" = "true" ]; then
57
+ if [ -n "${NGROK_AUTHTOKEN:-}" ]; then
58
+ ngrok config add-authtoken "${NGROK_AUTHTOKEN}" >> /wake/logs/ngrok.log 2>&1
59
+ fi
60
+
61
+ echo "wake ui: starting ngrok tunnel for 127.0.0.1:${WAKE_UI_PORT:-4317}"
62
+ ngrok http "127.0.0.1:${WAKE_UI_PORT:-4317}" --log=stdout >> /wake/logs/ngrok.log 2>&1 &
63
+ write_ngrok_public_url &
64
+ fi
65
+ fi
66
+
67
+ if [ "${WAKE_START_ENABLED:-false}" = "true" ]; then
68
+ echo "wake start: starting resident loop"
69
+ node /app/dist/src/main.js start \
70
+ --wake-root /wake \
71
+ >> /wake/logs/start.log 2>&1 &
72
+ echo "$!" > /wake/logs/start.pid
73
+ fi
74
+
75
+ exec sleep infinity