breakaway 1.0.2-main.25 → 1.0.2-main.26
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/package.json +1 -1
- package/scripts/tasks/hook-config.js +1 -1
- package/scripts/tasks/message-wait.mjs +1 -1
- package/scripts/tasks/repo.js +1 -1
- package/scripts/tasks/session-hook.mjs +1 -1
- package/scripts/tasks/settings.js +20 -35
- package/scripts/tasks.mjs +21 -32
- package/src/cli-version.js +2 -2
- package/src/redact.js +3 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "breakaway",
|
|
3
|
-
"version": "1.0.2-main.
|
|
3
|
+
"version": "1.0.2-main.26",
|
|
4
4
|
"description": "The task board for you and your coding agents: a Cloudflare Worker, its web app, Taskwarrior sync, and the CLI (npx breakaway).",
|
|
5
5
|
"license": "FSL-1.1-Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -42,7 +42,7 @@ function readOptional(path) {
|
|
|
42
42
|
|
|
43
43
|
/** This machine's tasks.env, as `{ NAME: value }`. */
|
|
44
44
|
function envFile() {
|
|
45
|
-
const dir = configDir({ env: process.env, home: homedir()
|
|
45
|
+
const dir = configDir({ env: process.env, home: homedir() });
|
|
46
46
|
return parseEnvFile(readOptional(join(dir, 'tasks.env')));
|
|
47
47
|
}
|
|
48
48
|
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* quietly, and a message waits for the agent's next turn; its `timeout` (300 s) outlasts the window,
|
|
8
8
|
* because Claude Code kills an async hook at its timeout (CLD-146).
|
|
9
9
|
*
|
|
10
|
-
* Quiet by design: without a claimed task (.task-session), with BREAKAWAY_SESSION_LOG=off
|
|
10
|
+
* Quiet by design: without a claimed task (.task-session), with BREAKAWAY_SESSION_LOG=off, or
|
|
11
11
|
* on any error, it exits 0. It writes nothing inside the repository: a cloud session's Stop check
|
|
12
12
|
* would take a dirty file as work to commit.
|
|
13
13
|
*/
|
package/scripts/tasks/repo.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Which repository the CLI works in (docs/specs/IDEA-14-multi-repo.md, section 5; CLD-123): the
|
|
3
3
|
* checkout's, from `git remote get-url origin`, matched against the board's registry. `--repo` or
|
|
4
|
-
* BREAKAWAY_REPO
|
|
4
|
+
* BREAKAWAY_REPO overrides it. Pure, so it's tested without git or the board.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* additionalContext so Claude gets them on its next turn (docs/specs/IDEA-15-message-a-running-agent.md).
|
|
8
8
|
*
|
|
9
9
|
* Quiet by design: without a claimed task (.task-session, written by `tasks claim`), with
|
|
10
|
-
* BREAKAWAY_SESSION_LOG=off
|
|
10
|
+
* BREAKAWAY_SESSION_LOG=off, or on any error, it does nothing and exits 0. A post
|
|
11
11
|
* that fails leaves its reason in the temp folder, which the CLI's next command here shows (BRK-86).
|
|
12
12
|
*/
|
|
13
13
|
import { boardConfig, claimedTask, dropClaim, projectRoot } from './hook-config.js';
|
|
@@ -1,52 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Which board install the CLI and the session hooks talk to, and the names of their settings (CLD-136,
|
|
3
|
-
* docs/specs/IDEA-13-breakaway.md, section 2).
|
|
4
|
-
* name the first install used before breakaway (SAMEWAVE_TASKS_URL), read as a fallback so existing machines,
|
|
5
|
-
* cloud environments, and agents keep working. Pure (the environment, files, and checks come in as arguments),
|
|
3
|
+
* docs/specs/IDEA-13-breakaway.md, section 2). Pure (the environment, files, and checks come in as arguments),
|
|
6
4
|
* so it's tested without a disk.
|
|
7
5
|
*/
|
|
8
|
-
/** Each setting's
|
|
6
|
+
/** Each setting's name, in the environment and in tasks.env (BRK-77 retired the first install's names). */
|
|
9
7
|
export const NAMES = Object.freeze({
|
|
10
|
-
URL:
|
|
11
|
-
TOKEN:
|
|
12
|
-
AGENT:
|
|
13
|
-
REPO:
|
|
14
|
-
CLIENT_ID:
|
|
15
|
-
SECRET:
|
|
16
|
-
SYNC_KEY:
|
|
17
|
-
SESSION_LOG:
|
|
8
|
+
URL: 'BREAKAWAY_URL',
|
|
9
|
+
TOKEN: 'BREAKAWAY_TOKEN',
|
|
10
|
+
AGENT: 'BREAKAWAY_AGENT',
|
|
11
|
+
REPO: 'BREAKAWAY_REPO',
|
|
12
|
+
CLIENT_ID: 'BREAKAWAY_CLIENT_ID',
|
|
13
|
+
SECRET: 'BREAKAWAY_SECRET',
|
|
14
|
+
SYNC_KEY: 'BREAKAWAY_SYNC_KEY',
|
|
15
|
+
SESSION_LOG: 'BREAKAWAY_SESSION_LOG',
|
|
18
16
|
});
|
|
19
17
|
|
|
20
|
-
const LEGACY_NAMES = new Set(Object.values(NAMES).map(([, legacy]) => legacy));
|
|
21
|
-
|
|
22
18
|
/**
|
|
23
|
-
* A setting's value: from the environment first
|
|
24
|
-
*
|
|
19
|
+
* A setting's value: from the environment first, then the env file, else `fallback`. The environment always wins,
|
|
20
|
+
* so `BREAKAWAY_URL=… npx breakaway` points one command elsewhere.
|
|
25
21
|
*/
|
|
26
22
|
export function readSetting(key, { env = {}, file = {} }, fallback) {
|
|
27
|
-
const
|
|
28
|
-
if (!
|
|
29
|
-
|
|
30
|
-
return fallback;
|
|
23
|
+
const name = NAMES[key];
|
|
24
|
+
if (!name) throw new Error(`no setting ${key}`);
|
|
25
|
+
return env[name] || file[name] || fallback;
|
|
31
26
|
}
|
|
32
27
|
|
|
33
|
-
/** True when an env file uses the first install's names, so the CLI writes it back with the same ones. */
|
|
34
|
-
export const usesLegacyNames = (file) => Object.keys(file ?? {}).some((name) => LEGACY_NAMES.has(name));
|
|
35
|
-
|
|
36
|
-
/** A setting's name in an env file the CLI writes: the file's own style, breakaway's for a new one. */
|
|
37
|
-
export const nameFor = (key, legacy) => NAMES[key][legacy ? 1 : 0];
|
|
38
|
-
|
|
39
28
|
/**
|
|
40
29
|
* This machine's folder for the board (tasks.env, the Taskwarrior credentials, the routines copy):
|
|
41
|
-
* BREAKAWAY_HOME when it's set (one per install, for a machine that uses two), else ~/.config/breakaway
|
|
42
|
-
* else ~/.config/samewave when only that one exists (a machine set up before breakaway), else ~/.config/breakaway.
|
|
30
|
+
* BREAKAWAY_HOME when it's set (one per install, for a machine that uses two), else ~/.config/breakaway.
|
|
43
31
|
*/
|
|
44
|
-
/** @param {{ env?: Record<string, string | undefined>, home?: string
|
|
45
|
-
export function configDir({ env = {}, home
|
|
32
|
+
/** @param {{ env?: Record<string, string | undefined>, home?: string }} options */
|
|
33
|
+
export function configDir({ env = {}, home }) {
|
|
46
34
|
if (env.BREAKAWAY_HOME) return env.BREAKAWAY_HOME.replace(/\/$/u, '');
|
|
47
|
-
|
|
48
|
-
const samewave = `${home}/.config/samewave`;
|
|
49
|
-
return !exists(breakaway) && exists(samewave) ? samewave : breakaway;
|
|
35
|
+
return `${home}/.config/breakaway`;
|
|
50
36
|
}
|
|
51
37
|
|
|
52
38
|
/** `path` as a Taskwarrior include line can name it: `~/…` under `home`, else as it is. */
|
|
@@ -59,8 +45,7 @@ export function taskrcUrl(text) {
|
|
|
59
45
|
}
|
|
60
46
|
|
|
61
47
|
/**
|
|
62
|
-
* The board's base URL, and where it came from: the environment or env file (BREAKAWAY_URL,
|
|
63
|
-
* SAMEWAVE_TASKS_URL), else the checkout's own .taskrc (`sync.server.url`, so the CLI and Taskwarrior agree),
|
|
48
|
+
* The board's base URL, and where it came from: the environment or env file (BREAKAWAY_URL), else the checkout's own .taskrc (`sync.server.url`, so the CLI and Taskwarrior agree),
|
|
64
49
|
* else the install's breakaway.config.json (`url`), else null: nothing says which board.
|
|
65
50
|
*/
|
|
66
51
|
export function boardUrl({ env = {}, file = {}, taskrc = null, config = null }) {
|
package/scripts/tasks.mjs
CHANGED
|
@@ -4,14 +4,13 @@
|
|
|
4
4
|
* Talks to the board's JSON API; claims there are atomic. See docs/tasks.md.
|
|
5
5
|
*
|
|
6
6
|
* Settings come from the environment, then tasks.env in this machine's folder for the board ($BREAKAWAY_HOME,
|
|
7
|
-
* else ~/.config/breakaway
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* BREAKAWAY_CLIENT_ID SAMEWAVE_TASKS_CLIENT_ID for `setup` (Taskwarrior sync), with BREAKAWAY_SECRET
|
|
7
|
+
* else ~/.config/breakaway; scripts/tasks/settings.js):
|
|
8
|
+
* BREAKAWAY_TOKEN API token (or the cloud environment's API credential)
|
|
9
|
+
* BREAKAWAY_URL the board; default: this checkout's .taskrc (sync.server.url),
|
|
10
|
+
* then breakaway.config.json (tools/tasks/ in a copy)
|
|
11
|
+
* BREAKAWAY_AGENT your name on claims (default user@host)
|
|
12
|
+
* BREAKAWAY_REPO the repository to work in (default: the checkout's origin)
|
|
13
|
+
* BREAKAWAY_CLIENT_ID for `setup` (Taskwarrior sync), with BREAKAWAY_SECRET
|
|
15
14
|
*/
|
|
16
15
|
import { execFileSync, spawnSync } from 'node:child_process';
|
|
17
16
|
import { createPrivateKey, pbkdf2Sync, randomBytes, randomUUID } from 'node:crypto';
|
|
@@ -53,19 +52,10 @@ import {
|
|
|
53
52
|
unknownSubcommand,
|
|
54
53
|
} from './tasks/cli.js';
|
|
55
54
|
import { CLI_VERSION } from '../src/cli-version.js';
|
|
56
|
-
import {
|
|
57
|
-
import {
|
|
58
|
-
boardUrl,
|
|
59
|
-
configDir,
|
|
60
|
-
nameFor,
|
|
61
|
-
parseEnvFile,
|
|
62
|
-
readSetting,
|
|
63
|
-
taskrcFixes,
|
|
64
|
-
tildePath,
|
|
65
|
-
usesLegacyNames,
|
|
66
|
-
} from './tasks/settings.js';
|
|
55
|
+
import { parseInstall, secretName } from '../src/install.js';
|
|
56
|
+
import { NAMES, boardUrl, configDir, parseEnvFile, readSetting, taskrcFixes, tildePath } from './tasks/settings.js';
|
|
67
57
|
|
|
68
|
-
const CONFIG_DIR = configDir({ env: process.env, home: homedir()
|
|
58
|
+
const CONFIG_DIR = configDir({ env: process.env, home: homedir() });
|
|
69
59
|
const ENV_FILE = join(CONFIG_DIR, 'tasks.env');
|
|
70
60
|
// Every other repository's routine, as agents-connect --repo wrote it to the Secrets Store: the owner's copy,
|
|
71
61
|
// so connecting one more merges instead of dropping the others (the Secrets Store never gives a value back).
|
|
@@ -100,12 +90,12 @@ function sharedFile(name) {
|
|
|
100
90
|
const CONFIG_FILE = sharedFile('breakaway.config.json');
|
|
101
91
|
|
|
102
92
|
/**
|
|
103
|
-
* The board's install, from breakaway.config.json: its Secrets Store and the prefix of its secrets'
|
|
104
|
-
*
|
|
105
|
-
*
|
|
93
|
+
* The board's install, from breakaway.config.json: its Secrets Store and the prefix of its secrets' names. Read only by
|
|
94
|
+
* the owner's commands that write secrets; a checkout without the file gets a new install's defaults, as the package
|
|
95
|
+
* does (BRK-77 retired the first install's names).
|
|
106
96
|
*/
|
|
107
97
|
function installConfig() {
|
|
108
|
-
if (!existsSync(CONFIG_FILE)) return {
|
|
98
|
+
if (!existsSync(CONFIG_FILE)) return parseInstall({});
|
|
109
99
|
try {
|
|
110
100
|
return parseInstall(JSON.parse(readFileSync(CONFIG_FILE, 'utf8')));
|
|
111
101
|
} catch (error) {
|
|
@@ -251,7 +241,7 @@ Everywhere: --json for machine-readable output, --as <name> to claim as someone
|
|
|
251
241
|
Projects: ideas and routines are the board's; repos lists each repository's areas.
|
|
252
242
|
|
|
253
243
|
Settings: BREAKAWAY_TOKEN, BREAKAWAY_URL, BREAKAWAY_AGENT, BREAKAWAY_REPO, from the environment or tasks.env in
|
|
254
|
-
$BREAKAWAY_HOME (default ~/.config/breakaway)
|
|
244
|
+
$BREAKAWAY_HOME (default ~/.config/breakaway).
|
|
255
245
|
Without BREAKAWAY_URL the board is this checkout's .taskrc sync.server.url. See docs/tasks.md#another-install.`;
|
|
256
246
|
|
|
257
247
|
// ---- settings ----------------------------------------------------------------------------
|
|
@@ -261,7 +251,7 @@ function readEnvFile() {
|
|
|
261
251
|
}
|
|
262
252
|
|
|
263
253
|
const fileEnv = readEnvFile();
|
|
264
|
-
/** A setting by its key in NAMES (scripts/tasks/settings.js): `TOKEN` reads BREAKAWAY_TOKEN
|
|
254
|
+
/** A setting by its key in NAMES (scripts/tasks/settings.js): `TOKEN` reads BREAKAWAY_TOKEN. */
|
|
265
255
|
const setting = (key, fallback) => readSetting(key, { env: process.env, file: fileEnv }, fallback);
|
|
266
256
|
const BOARD = boardUrl({
|
|
267
257
|
env: process.env,
|
|
@@ -271,9 +261,8 @@ const BOARD = boardUrl({
|
|
|
271
261
|
});
|
|
272
262
|
/** The board's address, or null when nothing says which board (the commands then ask for one). */
|
|
273
263
|
const BASE = BOARD.url;
|
|
274
|
-
/**
|
|
275
|
-
const
|
|
276
|
-
const envName = (key) => nameFor(key, LEGACY_ENV);
|
|
264
|
+
/** A setting's name in tasks.env: `TOKEN` is BREAKAWAY_TOKEN. */
|
|
265
|
+
const envName = (key) => NAMES[key];
|
|
277
266
|
// In a cloud session, go through its proxy: that's where the board's API credential is added.
|
|
278
267
|
routeThroughSessionProxy();
|
|
279
268
|
|
|
@@ -442,7 +431,7 @@ const enc = encodeURIComponent;
|
|
|
442
431
|
|
|
443
432
|
let repoContext;
|
|
444
433
|
/**
|
|
445
|
-
* {slug, registry}: the repository this checkout works in, from --repo,
|
|
434
|
+
* {slug, registry}: the repository this checkout works in, from --repo, BREAKAWAY_REPO, or the
|
|
446
435
|
* origin remote. slug is null on a board without repositories or in a checkout it doesn't know, and
|
|
447
436
|
* then everything works as before.
|
|
448
437
|
*/
|
|
@@ -1415,7 +1404,7 @@ const commands = {
|
|
|
1415
1404
|
/**
|
|
1416
1405
|
* Owner, once per repository: its routine's /fire URL and token (asked for here, never on a command line).
|
|
1417
1406
|
* The default repository's go in its two secrets, as always; `--repo <slug>` puts another's in
|
|
1418
|
-
* the install's ROUTINES secret (
|
|
1407
|
+
* the install's ROUTINES secret (BREAKAWAY_ROUTINES with breakaway's prefix), JSON keyed by slug, merged with this machine's copy of the others.
|
|
1419
1408
|
*/
|
|
1420
1409
|
async 'agents-connect'() {
|
|
1421
1410
|
const { repos, default: fallback } = await call('GET', 'repos');
|
|
@@ -1896,7 +1885,7 @@ function promoteEnvFile() {
|
|
|
1896
1885
|
|
|
1897
1886
|
/**
|
|
1898
1887
|
* Pipes each value into `wrangler secrets-store secret update` (never on a command line). `values` are keyed by
|
|
1899
|
-
* the part of the secret's name after the install's prefix (`API_TOKEN` is
|
|
1888
|
+
* the part of the secret's name after the install's prefix (`API_TOKEN` is BREAKAWAY_API_TOKEN with breakaway's prefix).
|
|
1900
1889
|
* An install without a Secrets Store (the Deploy to Cloudflare button's, CLD-139) keeps them as Worker secrets.
|
|
1901
1890
|
*/
|
|
1902
1891
|
function updateSecretsStore(values) {
|
package/src/cli-version.js
CHANGED
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
* and how to update it. scripts/tasks/version.test.js fails when the copied files change and this doesn't:
|
|
5
5
|
* so it lives in the board's package (CLD-135) and the CLI imports it from here.
|
|
6
6
|
*/
|
|
7
|
-
export const CLI_VERSION =
|
|
8
|
-
export const CLI_FINGERPRINT = '
|
|
7
|
+
export const CLI_VERSION = 47;
|
|
8
|
+
export const CLI_FINGERPRINT = 'e779f7d4ae3104e4';
|
package/src/redact.js
CHANGED
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
const PATTERNS = [
|
|
7
7
|
// Private keys, whole blocks.
|
|
8
8
|
[/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/gu, '[redacted key]'],
|
|
9
|
-
//
|
|
9
|
+
// Settings whose name says they're secret, KEY=value, under any prefix: breakaway's, an install's own (it picks its
|
|
10
|
+
// secrets' prefix), Cloudflare's, GitHub's, and the rest (BRK-77).
|
|
10
11
|
[
|
|
11
|
-
/\b(
|
|
12
|
+
/\b([A-Z][A-Z0-9]*_[A-Z0-9_]*(?:TOKEN|SECRET|KEY|PASSWORD)[A-Z0-9_]*\s*[=:]\s*)("[^"]*"|'[^']*'|\S+)/gu,
|
|
12
13
|
'$1[redacted]',
|
|
13
14
|
],
|
|
14
15
|
// Anthropic, GitHub, Slack-style tokens.
|