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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "breakaway",
3
- "version": "1.0.2-main.25",
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(), exists: existsSync });
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 (or SAMEWAVE_TASKS_SESSION_LOG=off), or
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
  */
@@ -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 (or SAMEWAVE_TASKS_REPO) overrides it. Pure, so it's tested without git or the board.
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 (or SAMEWAVE_TASKS_SESSION_LOG=off), or on any error, it does nothing and exits 0. A post
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). Every setting has a breakaway name (BREAKAWAY_URL) and the
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 breakaway name, then the fallback the first install set up. */
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: ['BREAKAWAY_URL', 'SAMEWAVE_TASKS_URL'],
11
- TOKEN: ['BREAKAWAY_TOKEN', 'SAMEWAVE_TASKS_TOKEN'],
12
- AGENT: ['BREAKAWAY_AGENT', 'SAMEWAVE_AGENT'],
13
- REPO: ['BREAKAWAY_REPO', 'SAMEWAVE_TASKS_REPO'],
14
- CLIENT_ID: ['BREAKAWAY_CLIENT_ID', 'SAMEWAVE_TASKS_CLIENT_ID'],
15
- SECRET: ['BREAKAWAY_SECRET', 'SAMEWAVE_TASKS_SECRET'],
16
- SYNC_KEY: ['BREAKAWAY_SYNC_KEY', 'SAMEWAVE_TASKS_SYNC_KEY'],
17
- SESSION_LOG: ['BREAKAWAY_SESSION_LOG', 'SAMEWAVE_TASKS_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 (either name), then the env file (either name), else
24
- * `fallback`. The environment always wins, so `BREAKAWAY_URL=… npx breakaway` points one command elsewhere.
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 names = NAMES[key];
28
- if (!names) throw new Error(`no setting ${key}`);
29
- for (const source of [env, file]) for (const name of names) if (source[name]) return source[name];
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, exists: (path: string) => boolean }} options */
45
- export function configDir({ env = {}, home, exists }) {
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
- const breakaway = `${home}/.config/breakaway`;
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, then
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, or ~/.config/samewave where only that one exists). Each has a breakaway name and,
8
- * as a fallback, the name an install from before breakaway used first (scripts/tasks/settings.js):
9
- * BREAKAWAY_TOKEN SAMEWAVE_TASKS_TOKEN API token (or the cloud environment's API credential)
10
- * BREAKAWAY_URL SAMEWAVE_TASKS_URL the board; default: this checkout's .taskrc (sync.server.url),
11
- * then breakaway.config.json (tools/tasks/ in a copy), then the legacy install's
12
- * BREAKAWAY_AGENT SAMEWAVE_AGENT your name on claims (default user@host)
13
- * BREAKAWAY_REPO SAMEWAVE_TASKS_REPO the repository to work in (default: the checkout's origin)
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 { LEGACY, parseInstall, secretName } from '../src/install.js';
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(), exists: existsSync });
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
- * names (a legacy install's: SAMEWAVE_TASKS_). Read only by the owner's commands that write secrets; a checkout without
105
- * the file is a legacy install.
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 { ...LEGACY, secretsStore: null };
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); the first names (SAMEWAVE_TASKS_TOKEN, …) still work.
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, then SAMEWAVE_TASKS_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
- /** The names this machine's tasks.env uses: the first ones in a file that has them, else breakaway's. */
275
- const LEGACY_ENV = usesLegacyNames(fileEnv);
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, SAMEWAVE_TASKS_REPO, or the
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 (SAMEWAVE_TASKS_ROUTINES on an install from before breakaway), JSON keyed by slug, merged with this machine's copy of the others.
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 SAMEWAVE_TASKS_API_TOKEN on an install from before breakaway).
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) {
@@ -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 = 46;
8
- export const CLI_FINGERPRINT = '0eaad403bcd23b2e';
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
- // An install's own settings (breakaway's prefix and the legacy one): KEY=value where the name says it's secret.
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((?:BREAKAWAY|SAMEWAVE|CLOUDFLARE|GITHUB|ANTHROPIC)_[A-Z0-9_]*(?:TOKEN|SECRET|KEY|PASSWORD)[A-Z0-9_]*\s*[=:]\s*)("[^"]*"|'[^']*'|\S+)/gu,
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.