otacon 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.
- package/LICENSE +21 -0
- package/README.md +88 -0
- package/dist/cli/client.js +188 -0
- package/dist/cli/client.js.map +1 -0
- package/dist/cli/commands/answer.js +63 -0
- package/dist/cli/commands/answer.js.map +1 -0
- package/dist/cli/commands/ask.js +117 -0
- package/dist/cli/commands/ask.js.map +1 -0
- package/dist/cli/commands/clean.js +48 -0
- package/dist/cli/commands/clean.js.map +1 -0
- package/dist/cli/commands/doctor.js +86 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/expose.js +104 -0
- package/dist/cli/commands/expose.js.map +1 -0
- package/dist/cli/commands/implement-done.js +53 -0
- package/dist/cli/commands/implement-done.js.map +1 -0
- package/dist/cli/commands/install.js +113 -0
- package/dist/cli/commands/install.js.map +1 -0
- package/dist/cli/commands/open.js +37 -0
- package/dist/cli/commands/open.js.map +1 -0
- package/dist/cli/commands/progress.js +45 -0
- package/dist/cli/commands/progress.js.map +1 -0
- package/dist/cli/commands/start.js +66 -0
- package/dist/cli/commands/start.js.map +1 -0
- package/dist/cli/commands/status.js +44 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/commands/submit.js +64 -0
- package/dist/cli/commands/submit.js.map +1 -0
- package/dist/cli/commands/wait.js +66 -0
- package/dist/cli/commands/wait.js.map +1 -0
- package/dist/cli/install/assets.js +285 -0
- package/dist/cli/install/assets.js.map +1 -0
- package/dist/cli/install/locations.js +92 -0
- package/dist/cli/install/locations.js.map +1 -0
- package/dist/cli/install/tailscale.js +39 -0
- package/dist/cli/install/tailscale.js.map +1 -0
- package/dist/cli/main.js +73 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/output.js +39 -0
- package/dist/cli/output.js.map +1 -0
- package/dist/cli/session.js +77 -0
- package/dist/cli/session.js.map +1 -0
- package/dist/daemon/activity.js +56 -0
- package/dist/daemon/activity.js.map +1 -0
- package/dist/daemon/anchor.js +143 -0
- package/dist/daemon/anchor.js.map +1 -0
- package/dist/daemon/app.js +1081 -0
- package/dist/daemon/app.js.map +1 -0
- package/dist/daemon/approve.js +71 -0
- package/dist/daemon/approve.js.map +1 -0
- package/dist/daemon/desktop-notify.js +69 -0
- package/dist/daemon/desktop-notify.js.map +1 -0
- package/dist/daemon/diff.js +187 -0
- package/dist/daemon/diff.js.map +1 -0
- package/dist/daemon/linter/index.js +19 -0
- package/dist/daemon/linter/index.js.map +1 -0
- package/dist/daemon/linter/parse.js +350 -0
- package/dist/daemon/linter/parse.js.map +1 -0
- package/dist/daemon/linter/rules.js +359 -0
- package/dist/daemon/linter/rules.js.map +1 -0
- package/dist/daemon/main.js +48 -0
- package/dist/daemon/main.js.map +1 -0
- package/dist/daemon/notify.js +23 -0
- package/dist/daemon/notify.js.map +1 -0
- package/dist/daemon/presence.js +37 -0
- package/dist/daemon/presence.js.map +1 -0
- package/dist/daemon/queue.js +160 -0
- package/dist/daemon/queue.js.map +1 -0
- package/dist/daemon/store.js +393 -0
- package/dist/daemon/store.js.map +1 -0
- package/dist/daemon/threads.js +153 -0
- package/dist/daemon/threads.js.map +1 -0
- package/dist/daemon/transcript.js +89 -0
- package/dist/daemon/transcript.js.map +1 -0
- package/dist/daemon/ui.js +175 -0
- package/dist/daemon/ui.js.map +1 -0
- package/dist/shared/config.js +93 -0
- package/dist/shared/config.js.map +1 -0
- package/dist/shared/gwt.js +69 -0
- package/dist/shared/gwt.js.map +1 -0
- package/dist/shared/paths.js +67 -0
- package/dist/shared/paths.js.map +1 -0
- package/dist/shared/question-spec.js +44 -0
- package/dist/shared/question-spec.js.map +1 -0
- package/dist/shared/types.js +35 -0
- package/dist/shared/types.js.map +1 -0
- package/dist/shared/version.js +5 -0
- package/dist/shared/version.js.map +1 -0
- package/dist/ui/assets/arc-HhPfdCPZ.js +1 -0
- package/dist/ui/assets/architecture-7EHR7CIX-BPLblcyi.js +1 -0
- package/dist/ui/assets/architectureDiagram-3BPJPVTR-D2PIxGOb.js +36 -0
- package/dist/ui/assets/array-BifhSqXX.js +1 -0
- package/dist/ui/assets/blockDiagram-GPEHLZMM-DQ3Dn17h.js +132 -0
- package/dist/ui/assets/c4Diagram-AAUBKEIU-DxITrQgS.js +10 -0
- package/dist/ui/assets/channel-ipcU8ZNI.js +1 -0
- package/dist/ui/assets/chunk-2J33WTMH-Du1JoPx5.js +1 -0
- package/dist/ui/assets/chunk-3OPIFGDE-Dn7x2Yqf.js +62 -0
- package/dist/ui/assets/chunk-4BX2VUAB-DVnrE-4n.js +1 -0
- package/dist/ui/assets/chunk-55IACEB6-BAhFAimA.js +1 -0
- package/dist/ui/assets/chunk-5ZQYHXKU-0hEZptem.js +2 -0
- package/dist/ui/assets/chunk-727SXJPM-C1FN_cI3.js +206 -0
- package/dist/ui/assets/chunk-AQP2D5EJ-A656OBd4.js +231 -0
- package/dist/ui/assets/chunk-BSJP7CBP-D8oMbjm8.js +1 -0
- package/dist/ui/assets/chunk-CSCIHK7Q-DjIL8GLi.js +122 -0
- package/dist/ui/assets/chunk-FMBD7UC4-Otblfqvz.js +15 -0
- package/dist/ui/assets/chunk-KSCS5N6A-BOjTvm3H.js +10 -0
- package/dist/ui/assets/chunk-L5ZTLDWV-CaTLaw6L.js +1 -0
- package/dist/ui/assets/chunk-LZXEDZCA-Dq5p7qrD.js +2 -0
- package/dist/ui/assets/chunk-ND2GUHAM-jZ_NNnWi.js +1 -0
- package/dist/ui/assets/chunk-NNHCCRGN-DlpIbxXb.js +159 -0
- package/dist/ui/assets/chunk-NZK2D7GU-U_7l_sCh.js +1 -0
- package/dist/ui/assets/chunk-O5CBEL6O-MewqqNB7.js +70 -0
- package/dist/ui/assets/chunk-QZHKN3VN-DzGPH44B.js +1 -0
- package/dist/ui/assets/chunk-WU5MYG2G-DyEIVjoo.js +1 -0
- package/dist/ui/assets/chunk-XPW4576I-D5ArxNEF.js +32 -0
- package/dist/ui/assets/classDiagram-4FO5ZUOK-Byg2Hl9D.js +1 -0
- package/dist/ui/assets/classDiagram-v2-Q7XG4LA2-Byg2Hl9D.js +1 -0
- package/dist/ui/assets/cose-bilkent-S5V4N54A-PFXzf7WV.js +1 -0
- package/dist/ui/assets/cytoscape.esm-h6BdjjI9.js +321 -0
- package/dist/ui/assets/dagre-BM42HDAG-xrCfjZuZ.js +4 -0
- package/dist/ui/assets/dagre-Bx709z4p.js +1 -0
- package/dist/ui/assets/defaultLocale-C8Fc0cco.js +1 -0
- package/dist/ui/assets/diagram-2AECGRRQ-BFf-cyKY.js +43 -0
- package/dist/ui/assets/diagram-5GNKFQAL-kNPV4NfV.js +10 -0
- package/dist/ui/assets/diagram-KO2AKTUF-ByC1IUwG.js +3 -0
- package/dist/ui/assets/diagram-LMA3HP47-DZIJMPK0.js +24 -0
- package/dist/ui/assets/diagram-OG6HWLK6-CSDED9A-.js +24 -0
- package/dist/ui/assets/dist-YwjsDswi.js +1 -0
- package/dist/ui/assets/erDiagram-TEJ5UH35-yuzvjE6J.js +85 -0
- package/dist/ui/assets/eventmodeling-FCH6USID-CZR4eNG-.js +1 -0
- package/dist/ui/assets/flowDiagram-I6XJVG4X-ApPtVyYM.js +162 -0
- package/dist/ui/assets/ganttDiagram-6RSMTGT7-BeMLXtAr.js +292 -0
- package/dist/ui/assets/gitGraph-WXDBUCRP-JmTTBa7j.js +1 -0
- package/dist/ui/assets/gitGraphDiagram-PVQCEYII-Cjjnjs71.js +106 -0
- package/dist/ui/assets/graphlib-B8gBHxth.js +1 -0
- package/dist/ui/assets/index-BFQVRcSI.js +11 -0
- package/dist/ui/assets/index-Bj_kTrwP.css +1 -0
- package/dist/ui/assets/info-J43DQDTF-8vZ3gome.js +1 -0
- package/dist/ui/assets/infoDiagram-5YYISTIA-CnMk1cA-.js +2 -0
- package/dist/ui/assets/init-D6jRqBbL.js +1 -0
- package/dist/ui/assets/ishikawaDiagram-YF4QCWOH-Bl8z6huD.js +70 -0
- package/dist/ui/assets/journeyDiagram-JHISSGLW-DYIVfMpS.js +139 -0
- package/dist/ui/assets/kanban-definition-UN3LZRKU-BnR0ZzOz.js +89 -0
- package/dist/ui/assets/katex-Vhh-h91d.js +257 -0
- package/dist/ui/assets/line-DcBdQit6.js +1 -0
- package/dist/ui/assets/linear-HKjRHFAO.js +1 -0
- package/dist/ui/assets/mermaid-parser.core-DkYXrPlA.js +4 -0
- package/dist/ui/assets/mermaid.core-BmkfCI3b.js +9 -0
- package/dist/ui/assets/mindmap-definition-RKZ34NQL-sIAd4nDi.js +96 -0
- package/dist/ui/assets/ordinal-hYBb2elL.js +1 -0
- package/dist/ui/assets/otacon-DPXGiaVj.svg +11 -0
- package/dist/ui/assets/packet-YPE3B663-BxbxcfXN.js +1 -0
- package/dist/ui/assets/path-BWPyau1x.js +1 -0
- package/dist/ui/assets/pie-LRSECV5Y-BJxazjNs.js +1 -0
- package/dist/ui/assets/pieDiagram-4H26LBE5-BiOhc9GR.js +30 -0
- package/dist/ui/assets/plan-view-CH6NzUDb.js +74 -0
- package/dist/ui/assets/purify.es-CDvCXckx.js +3 -0
- package/dist/ui/assets/quadrantDiagram-W4KKPZXB-CVyHbWgo.js +7 -0
- package/dist/ui/assets/radar-GUYGQ44K-D9ohbnbV.js +1 -0
- package/dist/ui/assets/requirementDiagram-4Y6WPE33-Ba24_hqc.js +84 -0
- package/dist/ui/assets/rough.esm-CSKSodPl.js +1 -0
- package/dist/ui/assets/sankeyDiagram-5OEKKPKP-CxD4wiPL.js +40 -0
- package/dist/ui/assets/sequenceDiagram-3UESZ5HK-7qA7lD61.js +162 -0
- package/dist/ui/assets/src-IM8AE8MK.js +1 -0
- package/dist/ui/assets/stateDiagram-AJRCARHV-DNElRCuH.js +1 -0
- package/dist/ui/assets/stateDiagram-v2-BHNVJYJU-D6qTYpY3.js +1 -0
- package/dist/ui/assets/timeline-definition-PNZ67QCA-ChYC4Grd.js +120 -0
- package/dist/ui/assets/treeView-BLDUP644-Il0KnMi_.js +1 -0
- package/dist/ui/assets/treemap-LRROVOQU-CIiKcdRo.js +1 -0
- package/dist/ui/assets/vennDiagram-CIIHVFJN-Ulhkum9i.js +34 -0
- package/dist/ui/assets/wardley-L42UT6IY-BNd4ljz7.js +1 -0
- package/dist/ui/assets/wardleyDiagram-YWT4CUSO-BicXxh84.js +78 -0
- package/dist/ui/assets/xychartDiagram-2RQKCTM6-Duf-m_th.js +7 -0
- package/dist/ui/index.html +20 -0
- package/package.json +66 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// otacon wait [--timeout 540] [--session id] — the parked wait (DESIGN.md §6):
|
|
2
|
+
// long-poll the session's queue and print exactly one event as JSON, exit 0
|
|
3
|
+
// ({"event":"timeout"} included — re-parking is the agent's normal loop).
|
|
4
|
+
//
|
|
5
|
+
// The deadline is fixed once at entry; each iteration re-ensures the daemon
|
|
6
|
+
// (a cheap health probe when it is up, a respawn + handshake after a crash)
|
|
7
|
+
// and parks for min(remaining, 240s). A connection failure mid-park therefore
|
|
8
|
+
// re-parks transparently until the overall deadline — a daemon restart is
|
|
9
|
+
// invisible to the agent. The 240s slice stays under undici's 300s
|
|
10
|
+
// response-headers timeout (DECISIONS.md "wait parks in slices").
|
|
11
|
+
import { parseArgs } from "node:util";
|
|
12
|
+
import { api, ensureDaemon, sleep } from "../client.js";
|
|
13
|
+
import { CliError, fail, printJson, usageError } from "../output.js";
|
|
14
|
+
import { listSessions, realpathOr, resolveSession } from "../session.js";
|
|
15
|
+
const MAX_PARK_SECONDS = 240;
|
|
16
|
+
const RESPONSE_GRACE_MS = 10_000;
|
|
17
|
+
const RETRY_DELAY_MS = 250;
|
|
18
|
+
export async function waitCommand(argv) {
|
|
19
|
+
const { values } = parseArgs({
|
|
20
|
+
args: argv,
|
|
21
|
+
options: { timeout: { type: "string", default: "540" }, session: { type: "string" } },
|
|
22
|
+
});
|
|
23
|
+
const timeoutSeconds = Number(values.timeout);
|
|
24
|
+
if (!Number.isFinite(timeoutSeconds) || timeoutSeconds <= 0) {
|
|
25
|
+
usageError("--timeout must be a positive number of seconds");
|
|
26
|
+
}
|
|
27
|
+
await ensureDaemon();
|
|
28
|
+
const session = resolveSession(await listSessions(), values.session, realpathOr(process.cwd()));
|
|
29
|
+
const deadline = Date.now() + timeoutSeconds * 1000;
|
|
30
|
+
for (;;) {
|
|
31
|
+
const remainingMs = deadline - Date.now();
|
|
32
|
+
if (remainingMs <= 0)
|
|
33
|
+
break;
|
|
34
|
+
try {
|
|
35
|
+
await ensureDaemon();
|
|
36
|
+
const parkSeconds = Math.min(Math.ceil(remainingMs / 1000), MAX_PARK_SECONDS);
|
|
37
|
+
const response = await api("GET", `/api/sessions/${session.id}/events?wait=${parkSeconds}`, undefined,
|
|
38
|
+
// Guard a wedged connection; generous because the daemon owns the clock.
|
|
39
|
+
AbortSignal.timeout(Math.min(remainingMs, parkSeconds * 1000) + RESPONSE_GRACE_MS));
|
|
40
|
+
if (response.status === 404) {
|
|
41
|
+
fail("E_UNKNOWN_SESSION", `daemon no longer knows session ${session.id}: ${JSON.stringify(response.body)}`);
|
|
42
|
+
}
|
|
43
|
+
if (response.status !== 200) {
|
|
44
|
+
// A daemon 500 is not "unknown session" — surface it as what it is.
|
|
45
|
+
fail("E_INTERNAL", `wait failed: ${JSON.stringify(response.body)}`, undefined, 2);
|
|
46
|
+
}
|
|
47
|
+
if (response.body.event === "timeout")
|
|
48
|
+
continue; // re-park
|
|
49
|
+
printJson(response.body);
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
// Only a dead/wedged connection (daemon killed or restarting) is
|
|
54
|
+
// retryable: back off, then the loop re-ensures the daemon and re-parks
|
|
55
|
+
// (queued events survive on disk). Everything else — ensureDaemon
|
|
56
|
+
// refusals, 404s, programming errors — propagates instead of being
|
|
57
|
+
// silently retried until the deadline.
|
|
58
|
+
if (!(error instanceof CliError) || error.code !== "E_DAEMON_DOWN")
|
|
59
|
+
throw error;
|
|
60
|
+
await sleep(RETRY_DELAY_MS);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
printJson({ event: "timeout" });
|
|
64
|
+
return 0;
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=wait.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wait.js","sourceRoot":"","sources":["../../../src/cli/commands/wait.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
// The wrapper content `otacon install` writes (DESIGN.md §16) — this is the
|
|
2
|
+
// product-critical text that teaches an agent the whole protocol: the §6 full
|
|
3
|
+
// loop, §8 grill discipline, and §13 "never end your turn". One protocol card,
|
|
4
|
+
// three destinations: Claude Code and OpenCode get it as a SKILL.md; Codex gets
|
|
5
|
+
// it as a marker-delimited block inside its shared ~/.codex/AGENTS.md. Plus the
|
|
6
|
+
// Stop hook shell script (§13). Wrappers are managed files: reinstall
|
|
7
|
+
// overwrites them wholesale (DECISIONS.md "Wrappers are managed files").
|
|
8
|
+
/** Present in every wrapper this tool owns; doctor greps for it. */
|
|
9
|
+
export const MANAGED_MARKER = 'managed by `otacon install`';
|
|
10
|
+
export const CODEX_BEGIN = '<!-- BEGIN OTACON — managed by `otacon install`; content inside these markers is overwritten on reinstall -->';
|
|
11
|
+
export const CODEX_END = '<!-- END OTACON -->';
|
|
12
|
+
/**
|
|
13
|
+
* The protocol card — §6 full loop (start-first) + §8 grill discipline + §13
|
|
14
|
+
* failure habits — parametrized by the command prefix so one source feeds both
|
|
15
|
+
* wrappers (D7): `otacon` for what `otacon install` writes into any repo,
|
|
16
|
+
* `./bin/otacon` for this repo's dogfood (run-from-source). The only thing that
|
|
17
|
+
* varies between the two is `cmd`; the protocol text is identical, so a change
|
|
18
|
+
* here lands in both at once.
|
|
19
|
+
*/
|
|
20
|
+
function protocolCard(cmd) {
|
|
21
|
+
return `Run plan reviews through the otacon CLI instead of your native plan mode. The
|
|
22
|
+
user reviews in a browser. Every \`${cmd}\`
|
|
23
|
+
command prints exactly one JSON line on stdout. Exit 0 = proceed; exit 1 = a
|
|
24
|
+
machine-readable error you can fix (read the JSON); exit 2 = you invoked it wrong.
|
|
25
|
+
|
|
26
|
+
## The loop
|
|
27
|
+
|
|
28
|
+
1. \`${cmd} start --title <kebab-title>\` **first, before you research** — it mints
|
|
29
|
+
the session and prints the review URL. Tell the user to open it (\`${cmd} open\`
|
|
30
|
+
prints it again) so they can watch the whole thing from the first second.
|
|
31
|
+
\`--quick\` skips the interview — only when the user explicitly asks.
|
|
32
|
+
2. **Research the codebase**, narrating as you go with
|
|
33
|
+
\`${cmd} progress "<what you're doing>"\` — call it whenever you start a chunk of
|
|
34
|
+
work the user can't otherwise see (reading a module, drafting, revising). It is
|
|
35
|
+
a non-blocking one-liner that feeds the live activity log and the draft chip; no
|
|
36
|
+
answer comes back, so never park on it. Read enough to propose answers, not
|
|
37
|
+
collect questions.
|
|
38
|
+
3. **Grill** (mandatory unless --quick): Interview me relentlessly about every
|
|
39
|
+
aspect of this plan until we reach a shared understanding. Walk down each branch
|
|
40
|
+
of the design tree, resolving dependencies between decisions one-by-one. For
|
|
41
|
+
each question, provide your recommended answer. Explore the code before asking.
|
|
42
|
+
Never ask what the code can answer.
|
|
43
|
+
- \`${cmd} ask --question "..." --options "A|B|C" --recommend A\` — always lead
|
|
44
|
+
with your recommended answer. \`--multi\` for multi-select; omit \`--options\`
|
|
45
|
+
for free text. The user can always answer with free-form custom text instead
|
|
46
|
+
of (or alongside) the chips, so frame options as a starting point, not a cage.
|
|
47
|
+
- Independent questions whose answers don't shape each other? Post them in one
|
|
48
|
+
call: \`${cmd} ask --batch questions.json\` (or \`--batch -\` for stdin) — a JSON
|
|
49
|
+
array of the same specs (\`{question, options?, recommend?, multi?}\`). They land
|
|
50
|
+
as ordinary cards; loop \`wait\` to collect each answer. Dependent questions
|
|
51
|
+
still go one at a time.
|
|
52
|
+
- Park for the answer: \`${cmd} wait --timeout 540\` (set the Bash tool timeout
|
|
53
|
+
to 600000 ms). The answer arrives as \`{"event":"answer","question":"q<n>",...}\`.
|
|
54
|
+
4. **Draft** the plan at \`.otacon/<session>/plan.md\` in the schema below, then
|
|
55
|
+
\`${cmd} submit\`. On exit 1, fix every reported lint issue and resubmit until
|
|
56
|
+
accepted.
|
|
57
|
+
5. **Review loop** — park in \`${cmd} wait --timeout 540\` (Bash timeout 600000 ms)
|
|
58
|
+
and handle the one event it prints:
|
|
59
|
+
- \`comments\` → revise plan.md; write \`resolutions.json\` as
|
|
60
|
+
\`{"changelog":"what changed","threads":{"t1":"how you resolved it"}}\` with
|
|
61
|
+
one reply per comment thread; \`${cmd} submit --resolutions resolutions.json\`
|
|
62
|
+
(loop on lint errors); park again.
|
|
63
|
+
- \`question\` → \`${cmd} answer <q-id> --body "..."\` (or \`--file\`); park again. A
|
|
64
|
+
\`question\` may carry \`replyTo\` (a follow-up on an earlier question) — skim that
|
|
65
|
+
thread's prior turns for context, but still answer the new \`q<n>\`.
|
|
66
|
+
- \`answer\` → use it and continue; park again whenever you are waiting.
|
|
67
|
+
- \`timeout\` → park again immediately. A timeout is NEVER completion.
|
|
68
|
+
- \`approved\` → \`git add\` + commit the plan file at the printed \`path\`. Plain
|
|
69
|
+
\`approved\` (no \`implement\`) → print a one-line summary and STOP. \`approved\`
|
|
70
|
+
**with \`implement:true\`** → after committing, enter the **Implement loop**
|
|
71
|
+
(below) — do NOT stop; the session is now \`implementing\`.
|
|
72
|
+
- \`deleted\` → the user deleted this session in the review UI. It is over:
|
|
73
|
+
STOP. There is no approved plan and nothing to commit.
|
|
74
|
+
6. **Never end your turn while the session is open.** Nothing to do = park in
|
|
75
|
+
\`${cmd} wait\` again. Confused, crashed, or compacted? \`${cmd} status\` returns
|
|
76
|
+
the open session, revision, and pending events — resume the loop from it.
|
|
77
|
+
|
|
78
|
+
## CLI quick reference
|
|
79
|
+
|
|
80
|
+
- \`${cmd} start --title <t> [--quick]\` · \`${cmd} progress "<note>"\` ·
|
|
81
|
+
\`${cmd} ask ...\` · \`${cmd} wait --timeout 540\` · \`${cmd} submit [--resolutions f]\` ·
|
|
82
|
+
\`${cmd} answer <q> --body "..."\` · \`${cmd} implement-done [--pr <url>] [--failed]\` ·
|
|
83
|
+
\`${cmd} status\` · \`${cmd} open\`
|
|
84
|
+
|
|
85
|
+
## Implement loop (on \`approved\` with \`implement:true\`)
|
|
86
|
+
|
|
87
|
+
You are the **orchestrator**: you only coordinate and narrate
|
|
88
|
+
(\`${cmd} progress\` at each checkpoint) — every phase's real work runs in a fresh
|
|
89
|
+
native subagent (Task tool) so your own context stays lean.
|
|
90
|
+
|
|
91
|
+
1. **Setup.** Commit the plan file at the event \`path\` (exactly as plain Approve),
|
|
92
|
+
then \`git worktree add .otacon/worktrees/<slug> -b otacon/impl-<slug>\` off that
|
|
93
|
+
commit (\`.otacon/\` is gitignored). \`${cmd} progress\` each checkpoint throughout.
|
|
94
|
+
2. **Per phase, in order** (read the phases from the committed plan):
|
|
95
|
+
- \`${cmd} progress "phase N — implementing"\`; spawn an **implement+test**
|
|
96
|
+
subagent (Task tool) scoped to that phase's Goal/Files/Verification — it
|
|
97
|
+
implements and runs the phase Verification plus the repo gates.
|
|
98
|
+
- spawn a **separate** \`/code-review --fix\` subagent on the phase's working
|
|
99
|
+
diff; it applies findings; re-review. (\`/code-review\` effort is config — start
|
|
100
|
+
moderate so false positives don't become needless pauses.)
|
|
101
|
+
- **clean + green** → commit the phase and continue. **Blocked** (tests stay red,
|
|
102
|
+
review still flags, or a subagent is stuck) → on the FIRST blocker,
|
|
103
|
+
\`${cmd} ask\` with options \`retry|skip|abort|guidance\`, park in \`${cmd} wait\`,
|
|
104
|
+
and act on the answer. No auto-retry.
|
|
105
|
+
3. **Finish.** On success, \`git mv\` the committed plan (the \`approved\` event's
|
|
106
|
+
\`path\`) into \`docs/plans/archive/\` and commit it on the impl branch, so the
|
|
107
|
+
archived plan rides along in the PR (it only takes effect on the default branch
|
|
108
|
+
when the PR merges). Then \`gh pr create\` against the default branch (PR body =
|
|
109
|
+
the plan summary + the per-phase log; fall back to the local branch + path when
|
|
110
|
+
there is no remote), then \`${cmd} implement-done --pr <url>\`. On abort, skip the
|
|
111
|
+
archive move (leave the plan in \`docs/plans/\` so it stays active) and run
|
|
112
|
+
\`${cmd} implement-done --failed\`.
|
|
113
|
+
|
|
114
|
+
While \`implementing\` the Stop hook still keeps you on the line — never end the turn
|
|
115
|
+
until \`implement-done\`.
|
|
116
|
+
|
|
117
|
+
## Plan schema (linted on submit)
|
|
118
|
+
|
|
119
|
+
Frontmatter (\`title\`, \`session\`, \`revision\`, \`status\`, \`created\`), then these
|
|
120
|
+
H2 sections in order — the five required ones plus optional review-altitude
|
|
121
|
+
sections slotted in place (include them when the change warrants; skip them on
|
|
122
|
+
trivial plans): \`## Summary\` (≤5 lines, lead with a diagram — see below) ·
|
|
123
|
+
*(optional)* \`## Contract\` (≤12 lines —
|
|
124
|
+
the interface surface the reviewer signs off instead of reading code: inputs,
|
|
125
|
+
outputs, types, errors; one signature fence is fine under the 1-fence rule) ·
|
|
126
|
+
\`## Decisions\` (entries ≤3 lines, \`- D<n>: ... ← q<n>\` citing the grill answer
|
|
127
|
+
that produced it, or \`[assumed]\`) · *(optional)* \`## Impact\` (≤10 lines — blast
|
|
128
|
+
radius: the upstream modules this plan leans on and the downstream modules it can
|
|
129
|
+
break; a dependency mermaid is fine under the 1-fence rule) · \`## Phases\`
|
|
130
|
+
(\`### Phase <n> — <name>\`, each with \`Goal:\` ≤3 lines, \`Files:\` list,
|
|
131
|
+
\`Verification:\` ≤3 lines plus an optional \`\`\`gwt scenario block — see below,
|
|
132
|
+
optional collapsible \`#### Details\` block) ·
|
|
133
|
+
\`## Risks\` (≤5 items, ≤2 lines each) ·
|
|
134
|
+
\`## Open Questions\`. Mermaid / code / \`before\`+\`after\` fences are budget-exempt,
|
|
135
|
+
max one per read-path section; the markdown-native review visuals below share a
|
|
136
|
+
separate per-section cap. Details may elaborate on the read path, never
|
|
137
|
+
introduce new scope.
|
|
138
|
+
|
|
139
|
+
**Lead with a diagram.** Put a \`\`\`mermaid state / sequence / flow diagram right
|
|
140
|
+
under the \`## Summary\` headline — strongly recommended on ~90% of plans, so the
|
|
141
|
+
reviewer grasps the change's shape before reading prose. Keep the headline as the ≤5-line
|
|
142
|
+
Summary.
|
|
143
|
+
|
|
144
|
+
## Visuals — prefer them over prose where they carry the information
|
|
145
|
+
|
|
146
|
+
Four markdown-native primitives the review UI styles. They degrade to readable
|
|
147
|
+
markdown if rendering fails, and a comment can anchor to one specific risk, row,
|
|
148
|
+
or callout.
|
|
149
|
+
|
|
150
|
+
- **Callouts** — a blockquote whose first line is a type marker
|
|
151
|
+
(\`[!risk]\`, \`[!note]\`, \`[!decision]\`, \`[!assumption]\`). Risks and
|
|
152
|
+
assumptions SHOULD be callouts, not bullets buried in prose:
|
|
153
|
+
> [!risk]
|
|
154
|
+
> The JWT cutover locks out sessions issued before it.
|
|
155
|
+
- **Decision matrix** — a GFM table comparing options, the chosen row led by a
|
|
156
|
+
\`✓\`. A Decisions section weighing 2+ options SHOULD use a matrix:
|
|
157
|
+
| Pick | Option | Tradeoff |
|
|
158
|
+
| ---- | ------ | ------------------------------- |
|
|
159
|
+
| ✓ | RS256 | rotate keys without redeploy |
|
|
160
|
+
| | HS256 | shared secret on every verifier |
|
|
161
|
+
- **Scope pills** — inline tags \`[new]\` \`[breaking]\` \`[risky]\` \`[deletes]\`
|
|
162
|
+
for flagging scope mid-sentence ("adds a [new] issuer; [breaking] cookie removal").
|
|
163
|
+
- **Behavioral assertions** — a \`\`\`gwt fence inside a phase's \`Verification\`
|
|
164
|
+
holding one or more Given/When/Then scenarios (blank line between scenarios;
|
|
165
|
+
\`And\`/\`But\` continue a clause). They render as scenario cards that double as
|
|
166
|
+
the human's approve checklist (Test-Driven Review), so write the observable
|
|
167
|
+
behavior the reviewer signs off, not the test code:
|
|
168
|
+
\`\`\`gwt
|
|
169
|
+
Given a plan with no Contract section
|
|
170
|
+
When the agent submits it
|
|
171
|
+
Then the lint passes and review opens
|
|
172
|
+
\`\`\`
|
|
173
|
+
Capped at 6 scenarios per block; must sit under \`Verification\`.
|
|
174
|
+
|
|
175
|
+
Callouts and matrices are budget-exempt but capped (default 2 per read-path
|
|
176
|
+
section); pills are free. Reach for a visual when it carries the point better
|
|
177
|
+
than a sentence — never as decoration.
|
|
178
|
+
|
|
179
|
+
## Rules
|
|
180
|
+
|
|
181
|
+
- Never use native plan mode, AskUserQuestion, or any built-in question UI while
|
|
182
|
+
the session is open — every question goes through \`${cmd} ask\`.
|
|
183
|
+
- Long review or build ahead? Remind the user to keep the Mac awake: \`caffeinate -i\`
|
|
184
|
+
while the session runs.
|
|
185
|
+
`;
|
|
186
|
+
}
|
|
187
|
+
/** ~/.claude/skills/otacon/SKILL.md and the OpenCode equivalent (same format). */
|
|
188
|
+
export function skillMd() {
|
|
189
|
+
return `---
|
|
190
|
+
name: otacon
|
|
191
|
+
description: Plan a feature through an otacon review session — grill interview, schema'd plan, phone review with anchored comments, approved committed artifact. Use when the user asks to plan something with otacon, types /otacon, or wants a reviewed implementation plan before coding. Replaces native plan mode.
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
<!-- ${MANAGED_MARKER} — reinstall overwrites this file; the spec lives in otacon's DESIGN.md -->
|
|
195
|
+
|
|
196
|
+
# Otacon plan session protocol
|
|
197
|
+
|
|
198
|
+
${protocolCard('otacon')}`;
|
|
199
|
+
}
|
|
200
|
+
/** The marker-delimited block upserted into ~/.codex/AGENTS.md. */
|
|
201
|
+
export function codexBlock() {
|
|
202
|
+
return `${CODEX_BEGIN}
|
|
203
|
+
|
|
204
|
+
# Otacon plan sessions
|
|
205
|
+
|
|
206
|
+
When the user asks you to plan a feature "with otacon" (or to run a plan
|
|
207
|
+
review), follow this protocol exactly.
|
|
208
|
+
|
|
209
|
+
${protocolCard('otacon')}
|
|
210
|
+
${CODEX_END}`;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* THIS repo's dogfood wrapper — the committed \`.claude/skills/otacon/SKILL.md\`
|
|
214
|
+
* (DESIGN.md §16). It is the same protocol card as \`skillMd()\`, but with the
|
|
215
|
+
* \`./bin/otacon\` run-from-source command prefix and a repo preamble (run from
|
|
216
|
+
* source, restart after daemon edits). Generated from this function and never
|
|
217
|
+
* hand-edited; \`assets.test.ts\` asserts the committed file equals this output,
|
|
218
|
+
* so a protocol change that updates the card but forgets to regenerate the
|
|
219
|
+
* dogfood file fails CI (D7).
|
|
220
|
+
*/
|
|
221
|
+
export function dogfoodSkillMd() {
|
|
222
|
+
return `---
|
|
223
|
+
name: otacon
|
|
224
|
+
description: Plan a feature for THIS repo through an otacon review session — grill interview, schema'd plan, browser/phone review with anchored comments, approved committed artifact. Use when the user asks to plan something with otacon, types /otacon, or wants a reviewed implementation plan before coding. Replaces native plan mode. Dogfoods otacon on its own development.
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
<!-- Generated from src/cli/install/assets.ts (dogfoodSkillMd) — do NOT hand-edit;
|
|
228
|
+
assets.test.ts guards that this file equals that output. Regenerate after any
|
|
229
|
+
protocol change. -->
|
|
230
|
+
|
|
231
|
+
# Otacon plan session protocol (dogfooding this repo)
|
|
232
|
+
|
|
233
|
+
This repo **is** otacon. You plan features for it by running otacon's own CLI from
|
|
234
|
+
source via the \`./bin/otacon\` shim, so every command below exercises the code in
|
|
235
|
+
this checkout. That shim runs the CLI from \`src/\` via bun — no build needed; it
|
|
236
|
+
always reflects current source. The daemon auto-spawns from source on the first
|
|
237
|
+
command. Working state lives in the gitignored \`.otacon/\`; the approved plan is
|
|
238
|
+
committed to \`docs/plans/\`.
|
|
239
|
+
|
|
240
|
+
After editing **daemon** source (\`src/daemon/**\`) mid-session, restart the running
|
|
241
|
+
daemon so your change loads: \`./bin/otacon restart\` (the next command respawns it
|
|
242
|
+
from current source). Use \`./bin/otacon restart\`, not a raw curl to a fixed port —
|
|
243
|
+
in a git worktree the shim runs the daemon on a derived port, and \`restart\` always
|
|
244
|
+
targets the one this checkout talks to. CLI/linter/parser edits need no restart.
|
|
245
|
+
|
|
246
|
+
The spec these commands implement is otacon's own DESIGN.md (§6 loop, §8 grill, §13
|
|
247
|
+
failure habits); the canonical wrapper text lives in \`src/cli/install/assets.ts\`.
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
${protocolCard('./bin/otacon')}`;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* The Claude Code Stop hook (DESIGN.md §13): blocks ending the turn while an
|
|
255
|
+
* open otacon session exists in the cwd's repo. Plain sh, fast, fail-open —
|
|
256
|
+
* any failure (daemon down, curl missing, no match) allows the stop. With no
|
|
257
|
+
* local pointer, the open session is found by scanning the daemon registry for
|
|
258
|
+
* a non-terminal session whose repo equals the cwd's git root (DESIGN.md §7) —
|
|
259
|
+
* `implementing` still blocks (the build is live); only the terminal states
|
|
260
|
+
* (approved/implemented/implement_failed) let the agent end its turn.
|
|
261
|
+
*/
|
|
262
|
+
export const STOP_HOOK_SCRIPT = `#!/bin/sh
|
|
263
|
+
# otacon Stop hook — ${MANAGED_MARKER}; reinstall overwrites this file.
|
|
264
|
+
# Blocks Claude Code from ending its turn while the cwd's repo has an open
|
|
265
|
+
# otacon plan session (DESIGN.md §13). Fail-open by design: when anything here
|
|
266
|
+
# fails (daemon unreachable, curl missing, no match), the stop is allowed.
|
|
267
|
+
input=$(cat 2>/dev/null) || input=""
|
|
268
|
+
cwd=$(printf '%s' "$input" | sed -n 's/.*"cwd"[[:space:]]*:[[:space:]]*"\\([^"]*\\)".*/\\1/p')
|
|
269
|
+
[ -n "$cwd" ] || cwd=$PWD
|
|
270
|
+
root=$(git -C "$cwd" rev-parse --show-toplevel 2>/dev/null) || root=$cwd
|
|
271
|
+
root=$(cd "$root" 2>/dev/null && pwd -P) || exit 0
|
|
272
|
+
port=\${OTACON_PORT:-4747}
|
|
273
|
+
list=$(curl -fsS --max-time 2 "http://127.0.0.1:$port/api/sessions" 2>/dev/null) || exit 0
|
|
274
|
+
# Split the compact registry array one session-object per line, keep this repo's
|
|
275
|
+
# open (non-terminal) sessions, take the first id. The repo match is exact: the
|
|
276
|
+
# pattern includes the closing quote of the JSON value. Terminal statuses
|
|
277
|
+
# (approved/implemented/implement_failed) are over -- drop them so a finished
|
|
278
|
+
# build no longer traps the agent, while an in-flight implementing still blocks.
|
|
279
|
+
sid=$(printf '%s' "$list" | sed 's/},{/}\\
|
|
280
|
+
{/g' | grep -F "\\"repo\\":\\"$root\\"" | grep -vE '"status":"(approved|implemented|implement_failed)"' | sed -n '1s/.*"id":"\\([^"]*\\)".*/\\1/p')
|
|
281
|
+
[ -n "$sid" ] || exit 0
|
|
282
|
+
printf '{"decision":"block","reason":"otacon plan session %s is still open — run otacon wait --timeout 540 (Bash timeout 600000 ms) and keep handling events until the plan is approved; run otacon status to re-orient."}\\n' "$sid"
|
|
283
|
+
exit 0
|
|
284
|
+
`;
|
|
285
|
+
//# sourceMappingURL=assets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assets.js","sourceRoot":"","sources":["../../../src/cli/install/assets.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,8EAA8E;AAC9E,+EAA+E;AAC/E,gFAAgF;AAChF,gFAAgF;AAChF,sEAAsE;AACtE,yEAAyE;AAEzE,oEAAoE;AACpE,MAAM,CAAC,MAAM,cAAc,GAAG,6BAA6B,CAAC;AAE5D,MAAM,CAAC,MAAM,WAAW,GACtB,+GAA+G,CAAC;AAClH,MAAM,CAAC,MAAM,SAAS,GAAG,qBAAqB,CAAC;AAE/C;;;;;;;GAOG;AACH,SAAS,YAAY,CAAC,GAAW;IAC/B,OAAO;qCAC4B,GAAG;;;;;;OAMjC,GAAG;wEAC8D,GAAG;;;;OAIpE,GAAG;;;;;;;;;;SAUD,GAAG;;;;;eAKG,GAAG;;;;8BAIY,GAAG;;;OAG1B,GAAG;;iCAEuB,GAAG;;;;uCAIG,GAAG;;wBAElB,GAAG;;;;;;;;;;;;OAYpB,GAAG,qDAAqD,GAAG;;;;;MAK5D,GAAG,sCAAsC,GAAG;MAC5C,GAAG,kBAAkB,GAAG,6BAA6B,GAAG;MACxD,GAAG,kCAAkC,GAAG;MACxC,GAAG,iBAAiB,GAAG;;;;;KAKxB,GAAG;;;;;4CAKoC,GAAG;;SAEtC,GAAG;;;;;;;;SAQH,GAAG,gEAAgE,GAAG;;;;;;;iCAO9C,GAAG;;OAE7B,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wDAsE8C,GAAG;;;CAG1D,CAAC;AACF,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,OAAO;IACrB,OAAO;;;;;OAKF,cAAc;;;;EAInB,YAAY,CAAC,QAAQ,CAAC,EAAE,CAAC;AAC3B,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,UAAU;IACxB,OAAO,GAAG,WAAW;;;;;;;EAOrB,YAAY,CAAC,QAAQ,CAAC;EACtB,SAAS,EAAE,CAAC;AACd,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc;IAC5B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6BP,YAAY,CAAC,cAAc,CAAC,EAAE,CAAC;AACjC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG;uBACT,cAAc;;;;;;;;;;;;;;;;;;;;;CAqBpC,CAAC"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Where each agent reads its wrapper from (DESIGN.md §16; DECISIONS.md
|
|
2
|
+
// "Wrapper destinations per agent"), plus the merge/registration helpers
|
|
3
|
+
// install and doctor share. homedir() honors $HOME, which is what keeps the
|
|
4
|
+
// install e2e hermetic under a temp HOME.
|
|
5
|
+
import { readFileSync } from "node:fs";
|
|
6
|
+
import { homedir } from "node:os";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
export function claudeSkillPath() {
|
|
9
|
+
return join(homedir(), ".claude", "skills", "otacon", "SKILL.md");
|
|
10
|
+
}
|
|
11
|
+
/** The Stop hook script install writes; settings.json references it by this path. */
|
|
12
|
+
export function claudeHookScriptPath() {
|
|
13
|
+
return join(homedir(), ".claude", "hooks", "otacon-stop.sh");
|
|
14
|
+
}
|
|
15
|
+
export function claudeSettingsPath() {
|
|
16
|
+
return join(homedir(), ".claude", "settings.json");
|
|
17
|
+
}
|
|
18
|
+
/** Codex's global instructions file ($CODEX_HOME, default ~/.codex). */
|
|
19
|
+
export function codexAgentsPath() {
|
|
20
|
+
return join(process.env.CODEX_HOME ?? join(homedir(), ".codex"), "AGENTS.md");
|
|
21
|
+
}
|
|
22
|
+
/** OpenCode's global skills dir ($XDG_CONFIG_HOME, default ~/.config). */
|
|
23
|
+
export function opencodeSkillPath() {
|
|
24
|
+
return join(process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config"), "opencode", "skills", "otacon", "SKILL.md");
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Replace the marker-delimited block in `existing` with `block` (which carries
|
|
28
|
+
* its own begin/end lines), or append it when no markers are present. User
|
|
29
|
+
* content outside the markers survives byte-for-byte; re-running with the same
|
|
30
|
+
* block is a fixpoint (the idempotent-reinstall contract).
|
|
31
|
+
*/
|
|
32
|
+
export function upsertMarkedBlock(existing, block, begin, end) {
|
|
33
|
+
const from = existing.indexOf(begin);
|
|
34
|
+
const to = existing.indexOf(end);
|
|
35
|
+
if (from !== -1 && to !== -1 && to > from) {
|
|
36
|
+
return existing.slice(0, from) + block + existing.slice(to + end.length);
|
|
37
|
+
}
|
|
38
|
+
if (existing.trim() === "")
|
|
39
|
+
return `${block}\n`;
|
|
40
|
+
const separator = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
41
|
+
return `${existing}${separator}${block}\n`;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Whether ~/.claude/settings.json currently registers the otacon Stop hook
|
|
45
|
+
* (install's offer path and doctor's check). Missing or unparseable settings
|
|
46
|
+
* read as "not registered" — only --hooks treats unparseable as an error.
|
|
47
|
+
*/
|
|
48
|
+
export function settingsRegisterStopHook() {
|
|
49
|
+
try {
|
|
50
|
+
const raw = JSON.parse(readFileSync(claudeSettingsPath(), "utf8"));
|
|
51
|
+
return stopHookRegistered(raw?.hooks?.Stop, claudeHookScriptPath());
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** True when some Stop matcher entry already runs `command`. */
|
|
58
|
+
export function stopHookRegistered(stop, command) {
|
|
59
|
+
if (!Array.isArray(stop))
|
|
60
|
+
return false;
|
|
61
|
+
return stop.some((entry) => {
|
|
62
|
+
const hooks = entry?.hooks;
|
|
63
|
+
return (Array.isArray(hooks) &&
|
|
64
|
+
hooks.some((h) => h?.command === command));
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Additively merge the Stop hook entry into a parsed settings.json: every
|
|
69
|
+
* existing key (and every existing Stop matcher) is preserved; an entry already
|
|
70
|
+
* running `command` makes this a no-op. Returns undefined when the existing
|
|
71
|
+
* structure has a shape we refuse to rewrite (hooks or hooks.Stop not
|
|
72
|
+
* object/array) — never clobber what we cannot faithfully merge.
|
|
73
|
+
*/
|
|
74
|
+
export function mergeStopHook(raw, command) {
|
|
75
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw))
|
|
76
|
+
return undefined;
|
|
77
|
+
const settings = raw;
|
|
78
|
+
const hooks = settings.hooks ?? {};
|
|
79
|
+
if (hooks === null || typeof hooks !== "object" || Array.isArray(hooks))
|
|
80
|
+
return undefined;
|
|
81
|
+
const stop = hooks.Stop ?? [];
|
|
82
|
+
if (!Array.isArray(stop))
|
|
83
|
+
return undefined;
|
|
84
|
+
if (stopHookRegistered(stop, command))
|
|
85
|
+
return { settings, changed: false };
|
|
86
|
+
const entry = { matcher: "", hooks: [{ type: "command", command }] };
|
|
87
|
+
return {
|
|
88
|
+
settings: { ...settings, hooks: { ...hooks, Stop: [...stop, entry] } },
|
|
89
|
+
changed: true,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=locations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"locations.js","sourceRoot":"","sources":["../../../src/cli/install/locations.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Best-effort Tailscale discovery shared by doctor and expose (DESIGN.md §11,
|
|
2
|
+
// §16). OTACON_TAILSCALE pins the binary (hermetic tests; nonstandard
|
|
3
|
+
// installs) and is authoritative when set; otherwise PATH, then the macOS app
|
|
4
|
+
// bundle's embedded CLI (DECISIONS.md "doctor/expose automation boundary").
|
|
5
|
+
import { execFileSync } from "node:child_process";
|
|
6
|
+
const MAC_APP_BIN = "/Applications/Tailscale.app/Contents/MacOS/Tailscale";
|
|
7
|
+
export function findTailscale() {
|
|
8
|
+
const override = process.env.OTACON_TAILSCALE;
|
|
9
|
+
const candidates = override !== undefined && override !== "" ? [override] : ["tailscale", MAC_APP_BIN];
|
|
10
|
+
for (const bin of candidates) {
|
|
11
|
+
try {
|
|
12
|
+
execFileSync(bin, ["version"], { stdio: ["ignore", "pipe", "ignore"] });
|
|
13
|
+
return bin;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
// not this one
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
export function tailscaleStatus(bin) {
|
|
22
|
+
try {
|
|
23
|
+
const raw = JSON.parse(execFileSync(bin, ["status", "--json"], {
|
|
24
|
+
encoding: "utf8",
|
|
25
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
26
|
+
}));
|
|
27
|
+
const dns = raw?.Self?.DNSName;
|
|
28
|
+
return {
|
|
29
|
+
backendState: typeof raw?.BackendState === "string" ? raw.BackendState : "unknown",
|
|
30
|
+
...(typeof dns === "string" && dns !== ""
|
|
31
|
+
? { dnsName: dns.replace(/\.$/, "") }
|
|
32
|
+
: {}),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=tailscale.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tailscale.js","sourceRoot":"","sources":["../../../src/cli/install/tailscale.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,sEAAsE;AACtE,8EAA8E;AAC9E,4EAA4E;AAE5E,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,MAAM,WAAW,GAAG,sDAAsD,CAAC;AAE3E,MAAM,UAAU,aAAa;IAC3B,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC;IAC9C,MAAM,UAAU,GAAG,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;IACvG,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;QAC7B,IAAI,CAAC;YACH,YAAY,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;YACxE,OAAO,GAAG,CAAC;QACb,CAAC;QAAC,MAAM,CAAC;YACP,eAAe;QACjB,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AASD,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CACpB,YAAY,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE;YACtC,QAAQ,EAAE,MAAM;YAChB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;SAClC,CAAC,CACyD,CAAC;QAC9D,MAAM,GAAG,GAAG,GAAG,EAAE,IAAI,EAAE,OAAO,CAAC;QAC/B,OAAO;YACL,YAAY,EAAE,OAAO,GAAG,EAAE,YAAY,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS;YAClF,GAAG,CAAC,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,EAAE;gBACvC,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,EAAE;gBACrC,CAAC,CAAC,EAAE,CAAC;SACR,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC"}
|
package/dist/cli/main.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// otacon CLI dispatch (DESIGN.md §6). stdout carries exactly one JSON line per
|
|
3
|
+
// invocation; notices go to stderr; exit 0 success / 1 expected failure /
|
|
4
|
+
// 2 usage or internal error (src/cli/output.ts).
|
|
5
|
+
import { answerCommand } from "./commands/answer.js";
|
|
6
|
+
import { askCommand } from "./commands/ask.js";
|
|
7
|
+
import { cleanCommand } from "./commands/clean.js";
|
|
8
|
+
import { doctorCommand } from "./commands/doctor.js";
|
|
9
|
+
import { exposeCommand } from "./commands/expose.js";
|
|
10
|
+
import { implementDoneCommand } from "./commands/implement-done.js";
|
|
11
|
+
import { installCommand } from "./commands/install.js";
|
|
12
|
+
import { openCommand } from "./commands/open.js";
|
|
13
|
+
import { progressCommand } from "./commands/progress.js";
|
|
14
|
+
import { startCommand } from "./commands/start.js";
|
|
15
|
+
import { statusCommand } from "./commands/status.js";
|
|
16
|
+
import { submitCommand } from "./commands/submit.js";
|
|
17
|
+
import { waitCommand } from "./commands/wait.js";
|
|
18
|
+
import { CliError, printJson } from "./output.js";
|
|
19
|
+
const USAGE = "usage: otacon <start|submit|wait|ask|answer|progress|implement-done|status|open|clean|install|doctor|expose> [options]";
|
|
20
|
+
async function dispatch(command, argv) {
|
|
21
|
+
switch (command) {
|
|
22
|
+
case "start":
|
|
23
|
+
return startCommand(argv);
|
|
24
|
+
case "submit":
|
|
25
|
+
return submitCommand(argv);
|
|
26
|
+
case "wait":
|
|
27
|
+
return waitCommand(argv);
|
|
28
|
+
case "ask":
|
|
29
|
+
return askCommand(argv);
|
|
30
|
+
case "answer":
|
|
31
|
+
return answerCommand(argv);
|
|
32
|
+
case "progress":
|
|
33
|
+
return progressCommand(argv);
|
|
34
|
+
case "implement-done":
|
|
35
|
+
return implementDoneCommand(argv);
|
|
36
|
+
case "status":
|
|
37
|
+
return statusCommand(argv);
|
|
38
|
+
case "open":
|
|
39
|
+
return openCommand(argv);
|
|
40
|
+
case "clean":
|
|
41
|
+
return cleanCommand(argv);
|
|
42
|
+
case "install":
|
|
43
|
+
return installCommand(argv);
|
|
44
|
+
case "doctor":
|
|
45
|
+
return doctorCommand(argv);
|
|
46
|
+
case "expose":
|
|
47
|
+
return exposeCommand(argv);
|
|
48
|
+
default:
|
|
49
|
+
throw new CliError("E_USAGE", USAGE, 2);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const isParseArgsError = (error) => error instanceof Error &&
|
|
53
|
+
(error.code?.startsWith("ERR_PARSE_ARGS") ?? false);
|
|
54
|
+
/** Exit only after queued stdout writes flush (stdout may be a pipe). */
|
|
55
|
+
function exit(code) {
|
|
56
|
+
process.stdout.write("", () => process.exit(code));
|
|
57
|
+
}
|
|
58
|
+
dispatch(process.argv[2], process.argv.slice(3)).then((code) => exit(code), (error) => {
|
|
59
|
+
if (error instanceof CliError) {
|
|
60
|
+
printJson(error.toPayload());
|
|
61
|
+
exit(error.exitCode);
|
|
62
|
+
}
|
|
63
|
+
else if (isParseArgsError(error)) {
|
|
64
|
+
printJson({ ok: false, error: { code: "E_USAGE", message: `${error.message}; ${USAGE}` } });
|
|
65
|
+
exit(2);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
69
|
+
printJson({ ok: false, error: { code: "E_INTERNAL", message } });
|
|
70
|
+
exit(2);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
//# sourceMappingURL=main.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.js","sourceRoot":"","sources":["../../src/cli/main.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// JSON-only stdout discipline for the otacon CLI (DESIGN.md §6): every command
|
|
2
|
+
// prints exactly one JSON line on stdout — the machine-readable result a coding
|
|
3
|
+
// agent parses as its Bash tool output — and human-facing notices go to stderr.
|
|
4
|
+
//
|
|
5
|
+
// Exit codes: 0 success (including {"event":"timeout"}, DESIGN.md §6), 1
|
|
6
|
+
// expected failure the agent can act on (lint reject, ambiguous session, port
|
|
7
|
+
// conflict), 2 usage or internal error.
|
|
8
|
+
/** A machine-readable command failure; main.ts prints the payload and exits. */
|
|
9
|
+
export class CliError extends Error {
|
|
10
|
+
code;
|
|
11
|
+
exitCode;
|
|
12
|
+
extra;
|
|
13
|
+
constructor(code, message, exitCode = 1, extra = {}) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.code = code;
|
|
16
|
+
this.exitCode = exitCode;
|
|
17
|
+
this.extra = extra;
|
|
18
|
+
this.name = "CliError";
|
|
19
|
+
}
|
|
20
|
+
toPayload() {
|
|
21
|
+
return { ok: false, error: { code: this.code, message: this.message, ...this.extra } };
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/** Throw an expected failure (exit 1 unless told otherwise). */
|
|
25
|
+
export function fail(code, message, extra, exitCode = 1) {
|
|
26
|
+
throw new CliError(code, message, exitCode, extra);
|
|
27
|
+
}
|
|
28
|
+
export function usageError(message) {
|
|
29
|
+
throw new CliError("E_USAGE", message, 2);
|
|
30
|
+
}
|
|
31
|
+
/** The one stdout write a command makes. */
|
|
32
|
+
export function printJson(value) {
|
|
33
|
+
process.stdout.write(`${JSON.stringify(value)}\n`);
|
|
34
|
+
}
|
|
35
|
+
/** Human-facing notice; never stdout, which agents parse. */
|
|
36
|
+
export function notice(message) {
|
|
37
|
+
process.stderr.write(`otacon: ${message}\n`);
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=output.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"output.js","sourceRoot":"","sources":["../../src/cli/output.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,gFAAgF;AAChF,gFAAgF;AAChF,EAAE;AACF,yEAAyE;AACzE,8EAA8E;AAC9E,wCAAwC;AAIxC,gFAAgF;AAChF,MAAM,OAAO,QAAS,SAAQ,KAAK;IAEtB;IAEA;IACA;IAJX,YACW,IAAY,EACrB,OAAe,EACN,WAAqB,CAAC,EACtB,QAAiC,EAAE;QAE5C,KAAK,CAAC,OAAO,CAAC,CAAC;QALN,SAAI,GAAJ,IAAI,CAAQ;QAEZ,aAAQ,GAAR,QAAQ,CAAc;QACtB,UAAK,GAAL,KAAK,CAA8B;QAG5C,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;IACzB,CAAC;IAED,SAAS;QACP,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC;IACzF,CAAC;CACF;AAED,gEAAgE;AAChE,MAAM,UAAU,IAAI,CAClB,IAAY,EACZ,OAAe,EACf,KAA+B,EAC/B,WAAqB,CAAC;IAEtB,MAAM,IAAI,QAAQ,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;AACrD,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,OAAe;IACxC,MAAM,IAAI,QAAQ,CAAC,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;AAC5C,CAAC;AAED,4CAA4C;AAC5C,MAAM,UAAU,SAAS,CAAC,KAAc;IACtC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AACrD,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,MAAM,CAAC,OAAe;IACpC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,OAAO,IAAI,CAAC,CAAC;AAC/C,CAAC"}
|