@onepointfour-packs/flaredeck-agent 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 (148) hide show
  1. package/LICENSE.md +14 -0
  2. package/README.md +102 -0
  3. package/dist/agent.d.ts +104 -0
  4. package/dist/agent.js +329 -0
  5. package/dist/agent.js.map +1 -0
  6. package/dist/approval.d.ts +72 -0
  7. package/dist/approval.js +161 -0
  8. package/dist/approval.js.map +1 -0
  9. package/dist/approvals.d.ts +2 -0
  10. package/dist/approvals.js +148 -0
  11. package/dist/approvals.js.map +1 -0
  12. package/dist/auth.d.ts +79 -0
  13. package/dist/auth.js +181 -0
  14. package/dist/auth.js.map +1 -0
  15. package/dist/automode.d.ts +33 -0
  16. package/dist/automode.js +69 -0
  17. package/dist/automode.js.map +1 -0
  18. package/dist/branches.d.ts +25 -0
  19. package/dist/branches.js +81 -0
  20. package/dist/branches.js.map +1 -0
  21. package/dist/cli.d.ts +2 -0
  22. package/dist/cli.js +12 -0
  23. package/dist/cli.js.map +1 -0
  24. package/dist/codesearch.d.ts +27 -0
  25. package/dist/codesearch.js +111 -0
  26. package/dist/codesearch.js.map +1 -0
  27. package/dist/concurrency.d.ts +14 -0
  28. package/dist/concurrency.js +52 -0
  29. package/dist/concurrency.js.map +1 -0
  30. package/dist/config.d.ts +106 -0
  31. package/dist/config.js +119 -0
  32. package/dist/config.js.map +1 -0
  33. package/dist/engine-antigravity.d.ts +23 -0
  34. package/dist/engine-antigravity.js +357 -0
  35. package/dist/engine-antigravity.js.map +1 -0
  36. package/dist/engine-claude.d.ts +18 -0
  37. package/dist/engine-claude.js +64 -0
  38. package/dist/engine-claude.js.map +1 -0
  39. package/dist/engine.d.ts +122 -0
  40. package/dist/engine.js +36 -0
  41. package/dist/engine.js.map +1 -0
  42. package/dist/env.d.ts +1 -0
  43. package/dist/env.js +10 -0
  44. package/dist/env.js.map +1 -0
  45. package/dist/environment.d.ts +39 -0
  46. package/dist/environment.js +100 -0
  47. package/dist/environment.js.map +1 -0
  48. package/dist/flaredeck-check.d.ts +21 -0
  49. package/dist/flaredeck-check.js +12 -0
  50. package/dist/flaredeck-check.js.map +1 -0
  51. package/dist/flaredeck-proxy.d.ts +60 -0
  52. package/dist/flaredeck-proxy.js +186 -0
  53. package/dist/flaredeck-proxy.js.map +1 -0
  54. package/dist/flaredeck.d.ts +132 -0
  55. package/dist/flaredeck.js +106 -0
  56. package/dist/flaredeck.js.map +1 -0
  57. package/dist/gemini.d.ts +21 -0
  58. package/dist/gemini.js +59 -0
  59. package/dist/gemini.js.map +1 -0
  60. package/dist/gitguard.d.ts +2 -0
  61. package/dist/gitguard.js +90 -0
  62. package/dist/gitguard.js.map +1 -0
  63. package/dist/github.d.ts +38 -0
  64. package/dist/github.js +153 -0
  65. package/dist/github.js.map +1 -0
  66. package/dist/githubauth.d.ts +21 -0
  67. package/dist/githubauth.js +90 -0
  68. package/dist/githubauth.js.map +1 -0
  69. package/dist/intake.d.ts +124 -0
  70. package/dist/intake.js +383 -0
  71. package/dist/intake.js.map +1 -0
  72. package/dist/jam.d.ts +45 -0
  73. package/dist/jam.js +385 -0
  74. package/dist/jam.js.map +1 -0
  75. package/dist/jev.d.ts +31 -0
  76. package/dist/jev.js +75 -0
  77. package/dist/jev.js.map +1 -0
  78. package/dist/jevguard.d.ts +28 -0
  79. package/dist/jevguard.js +213 -0
  80. package/dist/jevguard.js.map +1 -0
  81. package/dist/mailbox.d.ts +22 -0
  82. package/dist/mailbox.js +51 -0
  83. package/dist/mailbox.js.map +1 -0
  84. package/dist/main.d.ts +2 -0
  85. package/dist/main.js +764 -0
  86. package/dist/main.js.map +1 -0
  87. package/dist/markdown.d.ts +5 -0
  88. package/dist/markdown.js +81 -0
  89. package/dist/markdown.js.map +1 -0
  90. package/dist/oauth.d.ts +30 -0
  91. package/dist/oauth.js +216 -0
  92. package/dist/oauth.js.map +1 -0
  93. package/dist/permissions.d.ts +43 -0
  94. package/dist/permissions.js +57 -0
  95. package/dist/permissions.js.map +1 -0
  96. package/dist/preflight.d.ts +71 -0
  97. package/dist/preflight.js +116 -0
  98. package/dist/preflight.js.map +1 -0
  99. package/dist/providers.d.ts +26 -0
  100. package/dist/providers.js +27 -0
  101. package/dist/providers.js.map +1 -0
  102. package/dist/repopicker.d.ts +3 -0
  103. package/dist/repopicker.js +111 -0
  104. package/dist/repopicker.js.map +1 -0
  105. package/dist/runs.d.ts +59 -0
  106. package/dist/runs.js +211 -0
  107. package/dist/runs.js.map +1 -0
  108. package/dist/sandbox.d.ts +13 -0
  109. package/dist/sandbox.js +110 -0
  110. package/dist/sandbox.js.map +1 -0
  111. package/dist/schedules.d.ts +11 -0
  112. package/dist/schedules.js +85 -0
  113. package/dist/schedules.js.map +1 -0
  114. package/dist/secretguard.d.ts +4 -0
  115. package/dist/secretguard.js +115 -0
  116. package/dist/secretguard.js.map +1 -0
  117. package/dist/secrets.d.ts +27 -0
  118. package/dist/secrets.js +51 -0
  119. package/dist/secrets.js.map +1 -0
  120. package/dist/serve.d.ts +17 -0
  121. package/dist/serve.js +1206 -0
  122. package/dist/serve.js.map +1 -0
  123. package/dist/status.d.ts +73 -0
  124. package/dist/status.js +98 -0
  125. package/dist/status.js.map +1 -0
  126. package/dist/stream.d.ts +91 -0
  127. package/dist/stream.js +103 -0
  128. package/dist/stream.js.map +1 -0
  129. package/dist/streamtoken.d.ts +2 -0
  130. package/dist/streamtoken.js +30 -0
  131. package/dist/streamtoken.js.map +1 -0
  132. package/dist/team.d.ts +61 -0
  133. package/dist/team.js +136 -0
  134. package/dist/team.js.map +1 -0
  135. package/dist/ui.d.ts +110 -0
  136. package/dist/ui.js +348 -0
  137. package/dist/ui.js.map +1 -0
  138. package/dist/updater.d.ts +15 -0
  139. package/dist/updater.js +178 -0
  140. package/dist/updater.js.map +1 -0
  141. package/dist/workspace.d.ts +115 -0
  142. package/dist/workspace.js +247 -0
  143. package/dist/workspace.js.map +1 -0
  144. package/dist/worktree.d.ts +42 -0
  145. package/dist/worktree.js +140 -0
  146. package/dist/worktree.js.map +1 -0
  147. package/package.json +52 -0
  148. package/python/sidecar.py +178 -0
@@ -0,0 +1,69 @@
1
+ import { mkdirSync, writeFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { configPath } from './config.js';
4
+ export const DEFAULT_ENVIRONMENT = {
5
+ organization: 'Phiresky / FlareDeck',
6
+ trustedDomains: ['mcp.flaredeck.app', 'app.flaredeck.app', '*.googleapis.com', 'registry.npmjs.org'],
7
+ sensitiveTargets: ['prod', 'preprod', 'production branches and any Firebase project named phireboard'],
8
+ sandboxed: true,
9
+ };
10
+ function environmentEntries(env) {
11
+ return [
12
+ '$defaults',
13
+ `Organization: ${env.organization}. Primary use: software development — an unattended agent ` +
14
+ `working tickets from a Kanban board, editing code, running tests, and pushing branches.`,
15
+ ...(env.sandboxed
16
+ ? [
17
+ // "Host containment" is the slot the docs name for containers and VMs. Until it says
18
+ // what this box is, the classifier assumes a developer laptop on the open internet and
19
+ // treats routine disposable-environment work as risky.
20
+ 'Host containment: a dedicated machine that does nothing but run this agent. Its ' +
21
+ 'checkout, its database and its local services are disposable and are rebuilt from ' +
22
+ 'scratch routinely, so destructive local operations — dropping and recreating the ' +
23
+ 'local database, deleting worktrees, killing local servers — are ordinary work here ' +
24
+ 'and not a loss of anything. There are no neighbouring workloads to disturb.',
25
+ ]
26
+ : []),
27
+ `Trusted internal domains: ${env.trustedDomains.join(', ')}`,
28
+ 'Key internal services: FlareDeck at mcp.flaredeck.app is this agent\'s own task tracker. ' +
29
+ 'Reading cards and posting comments, claims and status changes there is its normal work, ' +
30
+ 'not data leaving the organisation. A card\'s fields — Environment, Component, lane, ' +
31
+ 'assignee, labels, notes — are metadata on a ticket: setting a card\'s Environment to ' +
32
+ '"Production" records where a bug was seen and deploys nothing, touches no server and no ' +
33
+ 'database. Only commands and scripts aimed at the targets below are production operations.',
34
+ `Sensitive remote targets: ${env.sensitiveTargets.join('; ')}. Treat anything else as ` +
35
+ `ordinary development infrastructure.`,
36
+ ];
37
+ }
38
+ /**
39
+ * The settings the agent runs under, written OUTSIDE the repo on purpose.
40
+ *
41
+ * The classifier deliberately refuses to read `autoMode` from a repo's `.claude/settings.json`,
42
+ * "so a checked-in repo or a build step could not otherwise inject its own allow rules". The same
43
+ * reasoning applies here with more force: the agent can edit its own checkout, so any policy kept
44
+ * there is policy the agent could rewrite. Keeping it beside the agent's config means the thing
45
+ * being governed cannot reach the thing governing it.
46
+ */
47
+ export function writeAutoModeSettings(config, env = DEFAULT_ENVIRONMENT) {
48
+ const path = join(dirname(configPath(config.repo)), 'auto-mode.json');
49
+ mkdirSync(dirname(path), { recursive: true });
50
+ writeFileSync(path, `${JSON.stringify({
51
+ autoMode: { environment: environmentEntries(env) },
52
+ // No memory on this machine. The CLI would otherwise read and write
53
+ // ~/.claude/projects/<cwd>/memory — a file keyed to a directory on one box, which is
54
+ // exactly what an agent that must behave the same wherever it starts cannot depend on.
55
+ // Its memory lives on its FlareDeck account instead (see `remember`), and the brief says so.
56
+ autoMemoryEnabled: false,
57
+ permissions: {
58
+ // Evaluated BEFORE the classifier and always prompt, even in auto mode — which in this
59
+ // harness means they reach canUseTool, and so the team lead. These are the calls worth
60
+ // a person's attention rather than a judgement in the moment.
61
+ ask: ['Bash(git push *)', 'Bash(gh pr create *)'],
62
+ // Evaluated before the classifier too, and nothing can override them — not the
63
+ // classifier, not stated intent, not a hook.
64
+ deny: [`Edit(//${'**'}/.flaredeck/**)`],
65
+ },
66
+ }, null, 2)}\n`, 'utf8');
67
+ return path;
68
+ }
69
+ //# sourceMappingURL=automode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"automode.js","sourceRoot":"","sources":["../src/automode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAE1C,OAAO,EAAE,UAAU,EAAe,MAAM,aAAa,CAAC;AAwBtD,MAAM,CAAC,MAAM,mBAAmB,GAAgB;IAC9C,YAAY,EAAE,sBAAsB;IACpC,cAAc,EAAE,CAAC,mBAAmB,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,oBAAoB,CAAC;IACpG,gBAAgB,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,+DAA+D,CAAC;IACtG,SAAS,EAAE,IAAI;CAChB,CAAC;AAEF,SAAS,kBAAkB,CAAC,GAAgB;IAC1C,OAAO;QACL,WAAW;QACX,iBAAiB,GAAG,CAAC,YAAY,4DAA4D;YAC3F,yFAAyF;QAC3F,GAAG,CAAC,GAAG,CAAC,SAAS;YACf,CAAC,CAAC;gBACE,qFAAqF;gBACrF,uFAAuF;gBACvF,uDAAuD;gBACvD,kFAAkF;oBAChF,oFAAoF;oBACpF,mFAAmF;oBACnF,qFAAqF;oBACrF,6EAA6E;aAChF;YACH,CAAC,CAAC,EAAE,CAAC;QACP,6BAA6B,GAAG,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;QAC5D,2FAA2F;YACzF,0FAA0F;YAC1F,sFAAsF;YACtF,uFAAuF;YACvF,0FAA0F;YAC1F,2FAA2F;QAC7F,6BAA6B,GAAG,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,2BAA2B;YACrF,sCAAsC;KACzC,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,qBAAqB,CAAC,MAAc,EAAE,MAAmB,mBAAmB;IAC1F,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAC;IACtE,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,aAAa,CACX,IAAI,EACJ,GAAG,IAAI,CAAC,SAAS,CACf;QACE,QAAQ,EAAE,EAAE,WAAW,EAAE,kBAAkB,CAAC,GAAG,CAAC,EAAE;QAClD,oEAAoE;QACpE,qFAAqF;QACrF,uFAAuF;QACvF,6FAA6F;QAC7F,iBAAiB,EAAE,KAAK;QACxB,WAAW,EAAE;YACX,uFAAuF;YACvF,uFAAuF;YACvF,8DAA8D;YAC9D,GAAG,EAAE,CAAC,kBAAkB,EAAE,sBAAsB,CAAC;YACjD,+EAA+E;YAC/E,6CAA6C;YAC7C,IAAI,EAAE,CAAC,UAAU,IAAI,iBAAiB,CAAC;SACxC;KACF,EACD,IAAI,EACJ,CAAC,CACF,IAAI,EACL,MAAM,CACP,CAAC;IACF,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Which branch this machine works from.
3
+ *
4
+ * Asked once at login, from the repo's actual branches, rather than left to a project file
5
+ * somebody has to know exists. Getting it wrong is quiet and expensive: every card would be cut
6
+ * from the wrong history and every pull request would target the wrong place, and both look
7
+ * fine until a merge is enormous.
8
+ */
9
+ export type RemoteBranch = {
10
+ name: string;
11
+ when: string;
12
+ };
13
+ /**
14
+ * The remote's branches, most recently changed first, each with how long ago that was.
15
+ *
16
+ * Alphabetical was useless on a real repo: 350 branches, and the one that mattered somewhere
17
+ * past "CST-679". The branch work merges into is the one people are committing to, so the order
18
+ * that answers the question is by last commit — and the date beside each name is what lets a
19
+ * person tell a live branch from a dead one with the same kind of name.
20
+ *
21
+ * Needs the remote-tracking refs to be current, so it fetches first. On a repo with no fetch
22
+ * possible it falls back to the remote's plain list, undated and unsorted, rather than nothing.
23
+ */
24
+ export declare function remoteBranches(repo: string): Promise<RemoteBranch[]>;
25
+ export declare function askBaseBranch(repo: string, say: (s: string) => void): Promise<string | null>;
@@ -0,0 +1,81 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { createInterface } from 'node:readline/promises';
3
+ function git(args, cwd) {
4
+ return new Promise((resolve) => {
5
+ const child = spawn('git', args, { cwd });
6
+ let out = '';
7
+ child.stdout.on('data', (d) => (out += String(d)));
8
+ child.on('close', (code) => resolve(code === 0 ? out : ''));
9
+ child.on('error', () => resolve(''));
10
+ });
11
+ }
12
+ /**
13
+ * The remote's branches, most recently changed first, each with how long ago that was.
14
+ *
15
+ * Alphabetical was useless on a real repo: 350 branches, and the one that mattered somewhere
16
+ * past "CST-679". The branch work merges into is the one people are committing to, so the order
17
+ * that answers the question is by last commit — and the date beside each name is what lets a
18
+ * person tell a live branch from a dead one with the same kind of name.
19
+ *
20
+ * Needs the remote-tracking refs to be current, so it fetches first. On a repo with no fetch
21
+ * possible it falls back to the remote's plain list, undated and unsorted, rather than nothing.
22
+ */
23
+ export async function remoteBranches(repo) {
24
+ await git(['fetch', '--prune', '--quiet', 'origin'], repo);
25
+ const dated = await git(
26
+ // The full ref, not the short form: origin/HEAD shortens to a branch called "origin".
27
+ ['for-each-ref', '--sort=-committerdate', '--format=%(refname)\t%(committerdate:relative)', 'refs/remotes/origin/'], repo);
28
+ const fromRefs = dated
29
+ .split('\n')
30
+ .map((l) => l.split('\t'))
31
+ .filter((p) => p.length === 2 && !!p[0])
32
+ .map(([ref, when]) => ({ name: ref.replace(/^refs\/remotes\/origin\//, ''), when }))
33
+ .filter((b) => b.name !== 'HEAD');
34
+ if (fromRefs.length)
35
+ return fromRefs;
36
+ const plain = await git(['ls-remote', '--heads', 'origin'], repo);
37
+ const names = plain
38
+ .split('\n')
39
+ .map((l) => l.split('refs/heads/')[1]?.trim())
40
+ .filter((n) => !!n);
41
+ return [...new Set(names)].map((name) => ({ name, when: '' }));
42
+ }
43
+ /** The usual names for the branch work merges into — pulled forward, but only when recent. */
44
+ const LIKELY = ['dev', 'develop', 'development', 'main', 'master', 'trunk'];
45
+ export async function askBaseBranch(repo, say) {
46
+ const branches = await remoteBranches(repo);
47
+ const all = branches.map((b) => b.name);
48
+ if (all.length === 0) {
49
+ say('Could not read the remote branches, so nothing to choose from.');
50
+ return null;
51
+ }
52
+ // Most recently changed first — that is the order that answers the question. The usual names
53
+ // are only pulled forward when they are themselves recent; a stale "development" nobody has
54
+ // touched in a year is not the usual answer, whatever it is called.
55
+ const recent = branches.slice(0, 12);
56
+ const likely = recent.filter((b) => LIKELY.includes(b.name));
57
+ const offered = [...likely, ...recent.filter((b) => !likely.includes(b))].slice(0, 12);
58
+ say('\nWhich branch does work start from, and merge back into? (most recently changed first)');
59
+ const width = Math.max(...offered.map((b) => b.name.length));
60
+ offered.forEach((b, i) => say(` ${String(i + 1).padStart(2)}. ${b.name.padEnd(width)}${b.when ? ` ${b.when}` : ''}`));
61
+ if (all.length > offered.length)
62
+ say(` … and ${all.length - offered.length} more`);
63
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
64
+ try {
65
+ const answer = (await rl.question(`\nNumber, or type a branch name [${offered[0]?.name}]: `)).trim();
66
+ if (!answer)
67
+ return offered[0]?.name ?? null;
68
+ const n = Number(answer);
69
+ if (Number.isInteger(n) && n >= 1 && n <= offered.length)
70
+ return offered[n - 1].name;
71
+ // A typed name is taken at its word only if it exists — a typo here is silent otherwise.
72
+ if (all.includes(answer))
73
+ return answer;
74
+ say(`There is no branch called "${answer}" on origin.`);
75
+ return offered[0]?.name ?? null;
76
+ }
77
+ finally {
78
+ rl.close();
79
+ }
80
+ }
81
+ //# sourceMappingURL=branches.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"branches.js","sourceRoot":"","sources":["../src/branches.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAYzD,SAAS,GAAG,CAAC,IAAc,EAAE,GAAW;IACtC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;QAC1C,IAAI,GAAG,GAAG,EAAE,CAAC;QACb,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACnD,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAC5D,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,IAAY;IAC/C,MAAM,GAAG,CAAC,CAAC,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC;IAC3D,MAAM,KAAK,GAAG,MAAM,GAAG;IACrB,sFAAsF;IACtF,CAAC,cAAc,EAAE,uBAAuB,EAAE,gDAAgD,EAAE,sBAAsB,CAAC,EACnH,IAAI,CACL,CAAC;IACF,MAAM,QAAQ,GAAG,KAAK;SACnB,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;SACzB,MAAM,CAAC,CAAC,CAAC,EAAyB,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;SAC9D,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,OAAO,CAAC,0BAA0B,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;SACnF,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC;IACpC,IAAI,QAAQ,CAAC,MAAM;QAAE,OAAO,QAAQ,CAAC;IAErC,MAAM,KAAK,GAAG,MAAM,GAAG,CAAC,CAAC,WAAW,EAAE,SAAS,EAAE,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC;IAClE,MAAM,KAAK,GAAG,KAAK;SAChB,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;SAC7C,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACnC,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;AACjE,CAAC;AAED,8FAA8F;AAC9F,MAAM,MAAM,GAAG,CAAC,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;AAE5E,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAAY,EAAE,GAAwB;IACxE,MAAM,QAAQ,GAAG,MAAM,cAAc,CAAC,IAAI,CAAC,CAAC;IAC5C,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACxC,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrB,GAAG,CAAC,gEAAgE,CAAC,CAAC;QACtE,OAAO,IAAI,CAAC;IACd,CAAC;IAED,6FAA6F;IAC7F,4FAA4F;IAC5F,oEAAoE;IACpE,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACrC,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAC7D,MAAM,OAAO,GAAG,CAAC,GAAG,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEvF,GAAG,CAAC,yFAAyF,CAAC,CAAC;IAC/F,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IAC7D,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;IACzH,IAAI,GAAG,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM;QAAE,GAAG,CAAC,WAAW,GAAG,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,OAAO,CAAC,CAAC;IAEpF,MAAM,EAAE,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC7E,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,oCAAoC,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACrG,IAAI,CAAC,MAAM;YAAE,OAAO,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,IAAI,IAAI,CAAC;QAC7C,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;QACzB,IAAI,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,MAAM;YAAE,OAAO,OAAO,CAAC,CAAC,GAAG,CAAC,CAAE,CAAC,IAAI,CAAC;QACtF,yFAAyF;QACzF,IAAI,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC;YAAE,OAAO,MAAM,CAAC;QACxC,GAAG,CAAC,8BAA8B,MAAM,cAAc,CAAC,CAAC;QACxD,OAAO,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,IAAI,IAAI,CAAC;IAClC,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;AACH,CAAC"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ // A thin launcher: it must run BEFORE the SDK is loaded, because an SDK downloaded in the
3
+ // background can only be swapped in while nothing has it open. Everything else is main.ts.
4
+ import { applyPendingUpdate } from './updater.js';
5
+ const first = process.argv[2];
6
+ if (!first || first === 'serve' || first.startsWith('--')) {
7
+ const note = applyPendingUpdate();
8
+ if (note)
9
+ process.env['FLAREDECK_AGENT_UPDATED'] = note;
10
+ }
11
+ await import('./main.js');
12
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,0FAA0F;AAC1F,2FAA2F;AAC3F,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAElD,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAC9B,IAAI,CAAC,KAAK,IAAI,KAAK,KAAK,OAAO,IAAI,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;IAC1D,MAAM,IAAI,GAAG,kBAAkB,EAAE,CAAC;IAClC,IAAI,IAAI;QAAE,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,GAAG,IAAI,CAAC;AAC1D,CAAC;AACD,MAAM,MAAM,CAAC,WAAW,CAAC,CAAC"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Where in the repo a card's work probably lives, worked out with grep rather than a model.
3
+ *
4
+ * A card almost always names something: a handler, an endpoint path, an error string, a file.
5
+ * Those names are searched for across the checkout and the files are ranked by how many of
6
+ * them they mention. It is not understanding — it is a map with the likely places circled —
7
+ * and it saves the working agent the first few tool calls of every card, which on a large repo
8
+ * are the expensive ones. No model, no network; a second or two of `git grep`.
9
+ */
10
+ export type Candidate = {
11
+ path: string;
12
+ score: number;
13
+ terms: string[];
14
+ };
15
+ export type CodeSearch = {
16
+ terms: string[];
17
+ files: Candidate[];
18
+ ms: number;
19
+ };
20
+ /**
21
+ * Identifiers worth searching for, from the card's text. Specific beats common: a CamelCase name
22
+ * with three humps is a better lead than a word that happens to be capitalised.
23
+ */
24
+ export declare function searchTerms(text: string): string[];
25
+ /** Files ranked by how many of the card's names they mention. Empty when the repo is not git or nothing matched. */
26
+ export declare function codeSearch(repo: string, text: string): Promise<CodeSearch>;
27
+ export declare function codeSearchText(cs: CodeSearch | null): string;
@@ -0,0 +1,111 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { existsSync } from 'node:fs';
3
+ const MAX_TERMS = 24;
4
+ const MAX_FILES = 12;
5
+ const TIMEOUT_MS = 12_000;
6
+ /** Words that look like identifiers but point nowhere: the tools, the products, the ceremony. */
7
+ const NOISE = new Set([
8
+ 'FlareDeck', 'Flaredeck', 'GitHub', 'GitLab', 'JavaScript', 'TypeScript',
9
+ 'OpenAI', 'ChatGPT', 'MySQL', 'PostgreSQL', 'MongoDB', 'GraphQL', 'WebVTT', 'macOS', 'iPhone', 'iOS', 'JSON', 'HTML', 'HTTP', 'HTTPS',
10
+ 'README', 'TODO', 'FIXME', 'PRODUCTION', 'NOTE', 'SELECT', 'UPDATE', 'DELETE', 'INSERT', 'WHERE', 'FROM', 'JOIN', 'COUNT',
11
+ 'Production', 'Development', 'Backlog', 'Released', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday',
12
+ ]);
13
+ /**
14
+ * Identifiers worth searching for, from the card's text. Specific beats common: a CamelCase name
15
+ * with three humps is a better lead than a word that happens to be capitalised.
16
+ */
17
+ export function searchTerms(text) {
18
+ const found = new Map();
19
+ const add = (t, weight) => {
20
+ const k = t.trim();
21
+ if (k.length < 4 || k.length > 80 || NOISE.has(k))
22
+ return;
23
+ found.set(k, Math.max(found.get(k) ?? 0, weight));
24
+ };
25
+ // CamelCase and PascalCase identifiers: GetCompletionRateQueryHandler, learnerCourseId.
26
+ for (const m of text.matchAll(/\b[A-Z][a-z0-9]+(?:[A-Z][a-z0-9]+){1,}\b|\b[a-z]+(?:[A-Z][a-z0-9]+){2,}\b/g))
27
+ add(m[0], 3);
28
+ // snake_case and SCREAMING_CASE with at least one underscore inside.
29
+ for (const m of text.matchAll(/\b[a-z]+(?:_[a-z0-9]+){1,}\b|\b[A-Z]{2,}(?:_[A-Z0-9]+){1,}\b/g))
30
+ add(m[0], 2);
31
+ // Code spans: `whatever is quoted like this`.
32
+ for (const m of text.matchAll(/`([^`\n]{4,60})`/g))
33
+ add(m[1], 3);
34
+ // File names with an extension.
35
+ for (const m of text.matchAll(/\b[\w.-]+\.(?:cs|ts|tsx|js|jsx|py|go|rb|java|kt|swift|sql|scss|css|html|vue|json|yaml|yml)\b/g))
36
+ add(m[0], 4);
37
+ // API paths: the last two segments of /api/learning/mine are what the code names.
38
+ for (const m of text.matchAll(/\/api\/[\w\-/{}.]+/g)) {
39
+ const parts = m[0].split('/').filter((p) => p && !/^[{}]|^\d+$|^[0-9a-f-]{20,}$/i.test(p));
40
+ const tail = parts.slice(-2).join('/');
41
+ if (tail)
42
+ add(tail, 3);
43
+ const last = parts[parts.length - 1];
44
+ if (last)
45
+ add(last, 2);
46
+ }
47
+ // Quoted error strings: "Commands out of sync".
48
+ for (const m of text.matchAll(/["“']([A-Z][^"”'\n]{8,70})["”']/g))
49
+ add(m[1], 2);
50
+ return [...found.entries()]
51
+ .sort((a, b) => b[1] - a[1] || b[0].length - a[0].length)
52
+ .slice(0, MAX_TERMS)
53
+ .map(([t]) => t);
54
+ }
55
+ function gitGrepCount(repo, term) {
56
+ return new Promise((resolve) => {
57
+ execFile('git', ['grep', '-I', '-c', '-F', '-e', term, '--', '.', ':!*.min.*', ':!*.map', ':!*.lock', ':!package-lock.json', ':!yarn.lock', ':!*.svg'], { cwd: repo, timeout: TIMEOUT_MS, maxBuffer: 4 * 1024 * 1024 }, (err, stdout) => {
58
+ const out = new Map();
59
+ if (err && !stdout)
60
+ return resolve(out); // exit 1 = no matches; anything else = treat as none
61
+ for (const line of String(stdout).split('\n')) {
62
+ const i = line.lastIndexOf(':');
63
+ if (i <= 0)
64
+ continue;
65
+ const n = Number(line.slice(i + 1));
66
+ if (Number.isFinite(n) && n > 0)
67
+ out.set(line.slice(0, i), n);
68
+ }
69
+ resolve(out);
70
+ });
71
+ });
72
+ }
73
+ /** Files ranked by how many of the card's names they mention. Empty when the repo is not git or nothing matched. */
74
+ export async function codeSearch(repo, text) {
75
+ const started = Date.now();
76
+ const terms = searchTerms(text);
77
+ if (!terms.length || !existsSync(repo))
78
+ return { terms, files: [], ms: Date.now() - started };
79
+ const results = await Promise.all(terms.map((t) => gitGrepCount(repo, t)));
80
+ const score = new Map();
81
+ results.forEach((hits, i) => {
82
+ const term = terms[i];
83
+ // A name in two thousand files is not a lead; one in a dozen is.
84
+ if (hits.size === 0 || hits.size > 60)
85
+ return;
86
+ const specificity = 1 + Math.log(60 / hits.size);
87
+ for (const [path, n] of hits) {
88
+ const c = score.get(path) ?? { path, score: 0, terms: [] };
89
+ c.score += specificity * (1 + Math.log(n));
90
+ c.terms.push(term);
91
+ score.set(path, c);
92
+ }
93
+ });
94
+ const files = [...score.values()]
95
+ // Tests and generated files are real matches but rarely where the fix goes; nudge them down, keep them.
96
+ .map((c) => (/(^|\/)(tests?|__tests__|spec|specs|migrations|generated|dist|build)\//i.test(c.path) || /\.(spec|test)\./.test(c.path) ? { ...c, score: c.score * 0.6 } : c))
97
+ .sort((a, b) => b.score - a.score)
98
+ .slice(0, MAX_FILES);
99
+ return { terms, files, ms: Date.now() - started };
100
+ }
101
+ export function codeSearchText(cs) {
102
+ if (!cs || !cs.files.length)
103
+ return '';
104
+ return [
105
+ '',
106
+ 'Candidate files (ranked by how many names from the card they mention — a map, not an answer):',
107
+ ...cs.files.map((f) => ` - ${f.path} (${f.terms.slice(0, 4).join(', ')}${f.terms.length > 4 ? `, +${f.terms.length - 4}` : ''})`),
108
+ ` searched for: ${cs.terms.slice(0, 12).join(', ')}${cs.terms.length > 12 ? ', …' : ''}`,
109
+ ].join('\n');
110
+ }
111
+ //# sourceMappingURL=codesearch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codesearch.js","sourceRoot":"","sources":["../src/codesearch.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { HookCallbackMatcher, HookEvent } from '@anthropic-ai/claude-agent-sdk';
2
+ /**
3
+ * Cap how many sub-agents run at once.
4
+ *
5
+ * Sub-agents are where an environment gets contended: two testing the API is fine, a third
6
+ * starting its own copy is not. Leasing ports per job would be the thorough answer, but a cap is
7
+ * most of the benefit for a fraction of the machinery — with a known ceiling you can size the
8
+ * environment for it and stop thinking about it.
9
+ *
10
+ * Done as a PreToolUse hook rather than a prompt instruction because hooks run before every other
11
+ * permission step and a hook deny holds even in bypassPermissions. A limit the model can talk
12
+ * itself past is not a limit.
13
+ */
14
+ export declare function subagentLimit(max: number, onNote: (text: string) => void): Partial<Record<HookEvent, HookCallbackMatcher[]>>;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Cap how many sub-agents run at once.
3
+ *
4
+ * Sub-agents are where an environment gets contended: two testing the API is fine, a third
5
+ * starting its own copy is not. Leasing ports per job would be the thorough answer, but a cap is
6
+ * most of the benefit for a fraction of the machinery — with a known ceiling you can size the
7
+ * environment for it and stop thinking about it.
8
+ *
9
+ * Done as a PreToolUse hook rather than a prompt instruction because hooks run before every other
10
+ * permission step and a hook deny holds even in bypassPermissions. A limit the model can talk
11
+ * itself past is not a limit.
12
+ */
13
+ export function subagentLimit(max, onNote) {
14
+ let running = 0;
15
+ return {
16
+ PreToolUse: [
17
+ {
18
+ // Only the tool that spawns them; everything else is untouched.
19
+ matcher: 'Agent',
20
+ hooks: [
21
+ async () => {
22
+ if (running >= max) {
23
+ onNote(`held back a sub-agent — ${running}/${max} already running`);
24
+ return {
25
+ decision: 'block',
26
+ reason: `${running} sub-agents are already running and the limit is ${max}. Wait for one ` +
27
+ `to finish before starting another, or do this part yourself. Starting more would ` +
28
+ `have them competing for the same ports, database and dev server.`,
29
+ };
30
+ }
31
+ running += 1;
32
+ return { continue: true };
33
+ },
34
+ ],
35
+ },
36
+ ],
37
+ PostToolUse: [
38
+ {
39
+ matcher: 'Agent',
40
+ hooks: [
41
+ async () => {
42
+ // Never below zero: a hook that missed its pair would otherwise leak capacity upward
43
+ // and quietly remove the limit.
44
+ running = Math.max(0, running - 1);
45
+ return { continue: true };
46
+ },
47
+ ],
48
+ },
49
+ ],
50
+ };
51
+ }
52
+ //# sourceMappingURL=concurrency.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"concurrency.js","sourceRoot":"","sources":["../src/concurrency.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,aAAa,CAAC,GAAW,EAAE,MAA8B;IACvE,IAAI,OAAO,GAAG,CAAC,CAAC;IAEhB,OAAO;QACL,UAAU,EAAE;YACV;gBACE,gEAAgE;gBAChE,OAAO,EAAE,OAAO;gBAChB,KAAK,EAAE;oBACL,KAAK,IAAI,EAAE;wBACT,IAAI,OAAO,IAAI,GAAG,EAAE,CAAC;4BACnB,MAAM,CAAC,2BAA2B,OAAO,IAAI,GAAG,kBAAkB,CAAC,CAAC;4BACpE,OAAO;gCACL,QAAQ,EAAE,OAAO;gCACjB,MAAM,EACJ,GAAG,OAAO,oDAAoD,GAAG,iBAAiB;oCAClF,mFAAmF;oCACnF,kEAAkE;6BACrE,CAAC;wBACJ,CAAC;wBACD,OAAO,IAAI,CAAC,CAAC;wBACb,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;oBAC5B,CAAC;iBACF;aACF;SACF;QACD,WAAW,EAAE;YACX;gBACE,OAAO,EAAE,OAAO;gBAChB,KAAK,EAAE;oBACL,KAAK,IAAI,EAAE;wBACT,qFAAqF;wBACrF,gCAAgC;wBAChC,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,CAAC,CAAC;wBACnC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;oBAC5B,CAAC;iBACF;aACF;SACF;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,106 @@
1
+ import type { Tokens } from './oauth.js';
2
+ /**
3
+ * What this machine's agent is, and where it works.
4
+ *
5
+ * Deliberately thin. Anything that describes a PROJECT (boards, lanes, build commands, territories)
6
+ * belongs in that project's own `.flaredeck/agent.yaml`, so one agent binary serves any repo.
7
+ * Anything that describes the agent's POWERS lives in Flaredeck against its account, so copying
8
+ * this file to another box grants nothing.
9
+ */
10
+ export type Config = {
11
+ /**
12
+ * Where this agent was started — a repo, or a folder of them. The directory is the declaration
13
+ * of what it may touch; see workspace.ts.
14
+ */
15
+ repo: string;
16
+ /**
17
+ * In a folder of several repos, the ones ticked at login. Directory names, because that is what
18
+ * was on screen. It only ever NARROWS what the folder holds — a name here that is not in the
19
+ * folder grants nothing — and empty or absent means every repo found there.
20
+ */
21
+ repos?: string[];
22
+ /**
23
+ * Which model actually runs a turn. Per-repo, so one machine can run some agents on Claude and
24
+ * some on Gemini and compare them on the same board. `FLAREDECK_AGENT_ENGINE` overrides it for
25
+ * one run without editing anything, which is how you try the other one on a card and put it
26
+ * back. Absent means claude.
27
+ */
28
+ engine?: 'claude' | 'antigravity';
29
+ /** MCP server name registered with the Claude CLI — also the `mcp__<name>__` tool prefix. */
30
+ mcpServerName: string;
31
+ mcpUrl: string;
32
+ /**
33
+ * The harness's OWN FlareDeck credentials, separate from the model's MCP login. The supervisor
34
+ * has to be able to post and read an approval even when the model is wedged, so it cannot go
35
+ * through the model's tools. Key comes from Integrations → API in FlareDeck.
36
+ */
37
+ tenantId?: string;
38
+ apiKey?: string;
39
+ /**
40
+ * The harness's OWN FlareDeck OAuth tokens — not Claude Code's. This is what makes it a
41
+ * first-class client rather than a passenger on the model's session: it can listen, post and
42
+ * call tools while the model is busy, wedged, or not running at all.
43
+ */
44
+ oauth?: {
45
+ accessToken: string;
46
+ refreshToken?: string;
47
+ expiresAt: number;
48
+ clientId: string;
49
+ clientSecret?: string;
50
+ };
51
+ /**
52
+ * GitHub, held by the HARNESS. The agent has publishing tools and no `gh`, so it cannot read
53
+ * this even from a shell — the credential is out of its reach rather than off-limits by rule.
54
+ */
55
+ github?: {
56
+ accessToken: string;
57
+ refreshToken?: string;
58
+ expiresAt?: number;
59
+ };
60
+ /**
61
+ * Where work is cut from and merged back into. Chosen at login from the repo's real branches,
62
+ * because asking once beats a project file someone has to know to write. A repo that declares
63
+ * `branches.base` in .flaredeck/agent.yaml overrides this — that one is reviewed and shared.
64
+ */
65
+ baseBranch?: string;
66
+ /** Where the event stream and /notify live. */
67
+ agentServiceUrl?: string;
68
+ /**
69
+ * Production targets the HARNESS may run an approved script against — see runs.ts. Configured
70
+ * on the machine, by a person, never by the agent: the command is what runs, the agent only
71
+ * supplies the script that goes into it.
72
+ */
73
+ runs?: Record<string, RunTarget>;
74
+ };
75
+ export type RunTarget = {
76
+ /** Program and arguments. `{script}` marks where the script goes; without it, stdin. */
77
+ command: string[];
78
+ cwd?: string;
79
+ /** Shown to the agent in the tool description. */
80
+ about?: string;
81
+ /** Who may approve a run against this target — emails or user keys. Empty: whoever owns the card. */
82
+ approvers?: string[];
83
+ };
84
+ export declare const DEFAULTS: {
85
+ readonly mcpServerName: "flaredeck";
86
+ readonly mcpUrl: "https://mcp.flaredeck.app/mcp";
87
+ readonly agentServiceUrl: "https://flaredeck-agent-861259873190.us-central1.run.app";
88
+ };
89
+ /**
90
+ * Config per REPO, not per machine.
91
+ *
92
+ * One machine runs several agents — a worker on one project, a lead on another, or simply two
93
+ * checkouts. With a single file they shared an identity: starting the agent in a second repo
94
+ * picked up the first one's account and listened on its queue, which looks like it is working
95
+ * and is entirely the wrong agent.
96
+ *
97
+ * Keyed by path, so `cd` into a repo and run it and you get that repo's agent.
98
+ */
99
+ export declare const configPath: (repo: string) => string;
100
+ export declare function loadConfig(repo?: string): Config | null;
101
+ export declare function saveConfig(config: Config): string;
102
+ export declare function loadMe(): Tokens | null;
103
+ export declare function saveMe(tokens: Tokens): string;
104
+ export declare function forgetConfig(repo: string): boolean;
105
+ /** Config with defaults filled in — for commands that can run before `login` has written one. */
106
+ export declare function configOrDefaults(repo?: string): Config;