@astrosheep/keiyaku 2.9.0 → 2.9.2

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 (47) hide show
  1. package/build/.tsbuildinfo +1 -1
  2. package/build/cli/commands/akuma/list/handler.js +1 -1
  3. package/build/cli/commands/akuma/view/handler.js +1 -1
  4. package/build/cli/commands/contract/amend/handler.js +1 -1
  5. package/build/cli/commands/contract/arc/handler.js +1 -1
  6. package/build/cli/commands/contract/bind/handler.js +1 -1
  7. package/build/cli/commands/contract/forfeit/handler.js +1 -1
  8. package/build/cli/commands/contract/log/handler.js +1 -1
  9. package/build/cli/commands/contract/petition/handler.js +1 -1
  10. package/build/cli/commands/contract/renew/handler.js +1 -1
  11. package/build/cli/commands/projection/call/handler.js +5 -2
  12. package/build/cli/commands/projection/kill/handler.js +3 -1
  13. package/build/cli/commands/projection/revive/handler.js +6 -3
  14. package/build/cli/commands/projection/status/handler.js +4 -2
  15. package/build/cli/commands/projection/tell/handler.js +3 -1
  16. package/build/cli/commands/projection/wait/handler.js +3 -1
  17. package/build/cli/commands/shared.js +5 -5
  18. package/build/cli/commands/system/completion/handler.js +1 -1
  19. package/build/cli/parse-flags.js +197 -0
  20. package/build/cli/parse-metadata.js +129 -0
  21. package/build/cli/parse-selectors.js +65 -0
  22. package/build/cli/parse.js +19 -373
  23. package/build/cli/skills-install.js +1 -1
  24. package/build/config/akuma-loader.js +5 -2
  25. package/build/core/amend.js +2 -5
  26. package/build/core/arc.js +2 -4
  27. package/build/core/call/call.js +7 -2
  28. package/build/core/call/context.js +27 -14
  29. package/build/core/call-persist.js +9 -2
  30. package/build/core/path-coordinate.js +27 -0
  31. package/build/core/petition-run.js +2 -5
  32. package/build/core/projection-coordinate.js +14 -2
  33. package/build/core/renew.js +2 -4
  34. package/build/core/status/board.js +6 -3
  35. package/build/core/task-contract.js +13 -0
  36. package/build/core/task-git-runtime.js +5 -2
  37. package/build/core/task-git-store.js +31 -21
  38. package/build/core/worktree-path.js +9 -2
  39. package/build/flow-error.js +2 -0
  40. package/build/generated/version.js +1 -1
  41. package/build/git/branches.js +33 -9
  42. package/build/git/core.js +9 -0
  43. package/package.json +1 -1
  44. package/skills/keiyaku/SKILL.md +30 -13
  45. package/skills/keiyaku-akuma/SKILL.md +30 -62
  46. package/skills/keiyaku-task/SKILL.md +51 -0
  47. package/skills/keiyaku-workflow/SKILL.md +71 -0
@@ -5,7 +5,7 @@ import { textResponse } from "../../../render/shared.js";
5
5
  import { projectDirectory } from "../../shared.js";
6
6
  import { renderAkumaList } from "../../akuma.js";
7
7
  export const handler = async (_stdin, flags) => {
8
- const cwd = await projectDirectory(flags);
8
+ const cwd = await projectDirectory(flags, "akuma ls");
9
9
  const loaded = await loadKeiyakuSettings(cwd);
10
10
  // Role markers need settings pointers; catalog consumers keep legacy agents disease actionable.
11
11
  assertSettingsKnobUsable(loaded, "agents");
@@ -22,7 +22,7 @@ export const handler = async (_stdin, flags, _signal, positional) => {
22
22
  if (isResponseArtifactId(name)) {
23
23
  throw new FlowError("EMPTY_PARAM", `artifact ${name} is a response artifact; akuma view expects an Akuma profile name`);
24
24
  }
25
- const cwd = await projectDirectory(flags);
25
+ const cwd = await projectDirectory(flags, "akuma view");
26
26
  const loaded = await loadKeiyakuSettings(cwd);
27
27
  assertSettingsKnobUsable(loaded, "agents");
28
28
  assertSettingsKnobUsable(loaded, "default");
@@ -5,6 +5,6 @@ import { renderCoreResult } from "../../../render/shared.js";
5
5
  import { effectiveDirectory, repositoryDirectory } from "../../shared.js";
6
6
  export const handler = async (stdin, flags) => {
7
7
  const parsed = buildAmendInput(stdin, effectiveDirectory(flags), flags.contractId, flags.contractAddressSource);
8
- const cwd = await repositoryDirectory(flags);
8
+ const cwd = await repositoryDirectory(flags, "amend");
9
9
  return renderCoreResult(amendKeiyaku({ ...parsed, cwd }), buildAmendResponse);
10
10
  };
@@ -4,6 +4,6 @@ import { renderCoreResult } from "../../../render/shared.js";
4
4
  import { effectiveDirectory, repositoryDirectory } from "../../shared.js";
5
5
  export const handler = async (stdin, flags) => {
6
6
  const parsed = buildArcInput(stdin, effectiveDirectory(flags), flags.waivePaths, flags.contractId, flags.contractAddressSource);
7
- const cwd = await repositoryDirectory(flags);
7
+ const cwd = await repositoryDirectory(flags, "arc");
8
8
  return renderCoreResult(arcKeiyaku({ ...parsed, cwd }), buildArcResponse);
9
9
  };
@@ -7,7 +7,7 @@ export const handler = async (stdin, flags) => {
7
7
  if (flags.dryRun) {
8
8
  return textResponse(stdin);
9
9
  }
10
- const cwd = await repositoryDirectory(flags);
10
+ const cwd = await repositoryDirectory(flags, "bind");
11
11
  if (flags.draftOnly) {
12
12
  return buildBindDraftResponse(await writeDraftArtifact({ cwd, title: "bind-draft", content: stdin }));
13
13
  }
@@ -4,7 +4,7 @@ import { forfeitContractCommand } from "../../../../core/forfeit.js";
4
4
  import { buildForfeitResponse } from "../../../render/shared.js";
5
5
  import { repositoryDirectory } from "../../shared.js";
6
6
  export const handler = async (_stdin, flags) => {
7
- const cwd = await repositoryDirectory(flags);
7
+ const cwd = await repositoryDirectory(flags, "forfeit");
8
8
  const address = await resolveContractAddress(cwd, flags.contractId, flags.contractAddressSource);
9
9
  return await withResolvedAddress(address, async () => buildForfeitResponse({
10
10
  ...await forfeitContractCommand({ cwd, contractId: address.binding.commissionId, ...defaultActorClock(), note: flags.reason }),
@@ -3,7 +3,7 @@ import { readContractLog } from "../../../../core/log.js";
3
3
  import { buildLogResponse } from "../../../render/misc.js";
4
4
  import { repositoryDirectory } from "../../shared.js";
5
5
  export const handler = async (_stdin, flags) => {
6
- const cwd = await repositoryDirectory(flags);
6
+ const cwd = await repositoryDirectory(flags, "log");
7
7
  const address = await resolveContractAddress(cwd, flags.contractId, flags.contractAddressSource);
8
8
  return await withResolvedAddress(address, async () => {
9
9
  const result = await readContractLog({ cwd, contractId: address.binding.commissionId });
@@ -9,7 +9,7 @@ export const handler = async (stdin, flags, signal) => {
9
9
  if (flags.json && !flags.dryRun) {
10
10
  throw new FlowError("EMPTY_PARAM", "petition --json requires --dry-run");
11
11
  }
12
- const cwd = await repositoryDirectory(flags);
12
+ const cwd = await repositoryDirectory(flags, "petition");
13
13
  const address = await resolveContractAddress(cwd, flags.contractId, flags.contractAddressSource);
14
14
  return await withResolvedAddress(address, async () => {
15
15
  const input = buildPetitionClaimInput({ cwd, contractId: address.binding.commissionId, stdin, signal, waivePaths: flags.waivePaths });
@@ -4,7 +4,7 @@ import { buildRenewResponse } from "../../../render/arc.js";
4
4
  import { renderCoreResult } from "../../../render/shared.js";
5
5
  import { repositoryDirectory } from "../../shared.js";
6
6
  export const handler = async (_stdin, flags) => {
7
- const cwd = await repositoryDirectory(flags);
7
+ const cwd = await repositoryDirectory(flags, "renew");
8
8
  const address = await resolveContractAddress(cwd, flags.contractId, flags.contractAddressSource);
9
9
  return renderCoreResult(renewKeiyaku({ cwd, address, waivePaths: flags.waivePaths }), buildRenewResponse);
10
10
  };
@@ -3,7 +3,7 @@ import { projectionDir } from "../../../../core/projection-core.js";
3
3
  import { waitForProjection } from "../../../../core/projection-wait.js";
4
4
  import { buildCallResponse, renderAdoptedLaunchReceipt } from "../../../render/call.js";
5
5
  import { buildWaitResponse } from "../../../render/wait.js";
6
- import { buildCallInput, projectDirectory } from "../../shared.js";
6
+ import { buildCallInput } from "../../shared.js";
7
7
  export const handler = async (stdin, flags, signal) => {
8
8
  const outcome = await runCallAndPersist({
9
9
  ...buildCallInput(stdin, flags, "call"),
@@ -22,8 +22,11 @@ export const handler = async (stdin, flags, signal) => {
22
22
  const projection = outcome.result.projection;
23
23
  if (!projection)
24
24
  throw new Error("call --wait requires projection facts after launch");
25
+ const projectionRoot = outcome.result.address.repo.kind === "repository"
26
+ ? outcome.result.address.repo.stableRoot
27
+ : flags.cwd ?? process.cwd();
25
28
  return buildWaitResponse(projection.id, await waitForProjection({
26
- projectionDirectory: projectionDir(await projectDirectory(flags), projection.id),
29
+ projectionDirectory: projectionDir(projectionRoot, projection.id),
27
30
  timeoutMs: flags.timeoutMs,
28
31
  signal,
29
32
  }), { alias: projection.alias });
@@ -8,7 +8,9 @@ import { textResponse } from "../../../render/shared.js";
8
8
  import { buildWaitResponse } from "../../../render/wait.js";
9
9
  import { projectDirectory, repositoryDirectory } from "../../shared.js";
10
10
  export const handler = async (_stdin, flags, signal, positional) => {
11
- const cwd = flags.contractId ? await repositoryDirectory(flags) : await projectDirectory(flags);
11
+ const cwd = flags.contractId
12
+ ? await repositoryDirectory(flags, "kill contract addressing")
13
+ : await projectDirectory(flags, "kill");
12
14
  const [projectionAddress] = positional;
13
15
  if (!projectionAddress)
14
16
  throw new FlowError("EMPTY_PARAM", "kill requires a projection address");
@@ -3,15 +3,18 @@ import { FlowError } from "../../../../flow-error.js";
3
3
  import { buildArtifactResolutionAttempt, } from "../../../../core/addressing.js";
4
4
  import { runCallAndPersist } from "../../../../core/call-persist.js";
5
5
  import { locateResponseArtifact, reconcileResponseArtifactCatalog, } from "../../../../core/transcripts.js";
6
- import { isGitRepo } from "../../../../git/branches.js";
6
+ import { observeGitRepository } from "../../../../git/branches.js";
7
7
  import { stableRepoRoot } from "../../../../core/worktree-path.js";
8
8
  import { buildCallResponse } from "../../../render/call.js";
9
9
  import { buildCallInput, effectiveDirectory, projectDirectory, revivePrompt } from "../../shared.js";
10
10
  export const handler = async (stdin, flags, signal, positional) => {
11
11
  const cwd = effectiveDirectory(flags);
12
- const artifactCwd = await projectDirectory(flags);
12
+ const artifactCwd = await projectDirectory(flags, "revive");
13
13
  const artifactId = positional[0];
14
- const isRepo = await isGitRepo(artifactCwd);
14
+ const repository = await observeGitRepository(artifactCwd);
15
+ if (repository.capability === "unknown")
16
+ throw repository.error;
17
+ const isRepo = repository.capability === "available" && repository.coordinate === "repository";
15
18
  const responseStoreCwd = isRepo ? await stableRepoRoot(artifactCwd) : await fs.realpath(artifactCwd);
16
19
  try {
17
20
  await reconcileResponseArtifactCatalog(responseStoreCwd);
@@ -4,11 +4,13 @@ import { isProjectionId } from "../../../../core/projection-core.js";
4
4
  import { FlowError } from "../../../../flow-error.js";
5
5
  import { renderProjectionFilteredStatus, renderStatusBoard } from "../../../render/status.js";
6
6
  import { textResponse } from "../../../render/shared.js";
7
- import { projectDirectory } from "../../shared.js";
7
+ import { projectDirectory, repositoryDirectory } from "../../shared.js";
8
8
  export const handler = async (_stdin, flags, _signal, positional) => {
9
9
  // Status is read-only and must open outside Git. projectDirectory still
10
10
  // honors --repo as a repository coordinate when the operator names one.
11
- const cwd = await projectDirectory(flags);
11
+ const cwd = flags.target === undefined
12
+ ? await projectDirectory(flags, "status")
13
+ : await repositoryDirectory(flags, "status target filtering");
12
14
  const target = flags.target === undefined ? undefined : normalizeTargetRef(flags.target);
13
15
  const board = await readKanshiBoard(cwd, Date.now(), {
14
16
  ...(target === undefined ? {} : { target }),
@@ -9,7 +9,9 @@ import { textResponse } from "../../../render/shared.js";
9
9
  import { buildWaitResponse } from "../../../render/wait.js";
10
10
  import { projectDirectory, repositoryDirectory } from "../../shared.js";
11
11
  export const handler = async (body, flags, signal, positional) => {
12
- const cwd = flags.contractId ? await repositoryDirectory(flags) : await projectDirectory(flags);
12
+ const cwd = flags.contractId
13
+ ? await repositoryDirectory(flags, "tell contract addressing")
14
+ : await projectDirectory(flags, "tell");
13
15
  const projectionAddress = positional[0];
14
16
  if (!projectionAddress)
15
17
  throw new FlowError("EMPTY_PARAM", "tell requires a projection address and message");
@@ -6,7 +6,9 @@ import { prependAddressReceipt } from "../../../render/address.js";
6
6
  import { buildWaitResponse } from "../../../render/wait.js";
7
7
  import { projectDirectory, repositoryDirectory } from "../../shared.js";
8
8
  export const handler = async (_stdin, flags, signal, positional) => {
9
- const cwd = flags.contractId ? await repositoryDirectory(flags) : await projectDirectory(flags);
9
+ const cwd = flags.contractId
10
+ ? await repositoryDirectory(flags, "wait contract addressing")
11
+ : await projectDirectory(flags, "wait");
10
12
  const address = flags.contractId ? await resolveContractAddress(cwd, flags.contractId, flags.contractAddressSource) : undefined;
11
13
  const operation = async () => {
12
14
  const projection = await resolveProjectionAddress({
@@ -1,6 +1,6 @@
1
1
  import { FlowError } from "../../flow-error.js";
2
2
  import { AddressResolutionError, buildResolutionAttempt, } from "../../core/addressing.js";
3
- import { isGitRepo } from "../../git/branches.js";
3
+ import { assertGitObservation, observeGitRepository } from "../../git/branches.js";
4
4
  import { stableRepoRoot } from "../../core/worktree-path.js";
5
5
  import { assertProjectionAlias } from "../../core/projection-alias.js";
6
6
  export function titleFromCallPrompt(prompt) {
@@ -44,9 +44,9 @@ export function revivePrompt(stdin) {
44
44
  export function effectiveDirectory(flags) {
45
45
  return flags.cwd ?? process.cwd();
46
46
  }
47
- export async function repositoryDirectory(flags) {
47
+ export async function repositoryDirectory(flags, operation) {
48
48
  const candidate = flags.repo ?? effectiveDirectory(flags);
49
- if (!(await isGitRepo(candidate))) {
49
+ if (assertGitObservation(await observeGitRepository(candidate), operation) === "non-repository") {
50
50
  if (flags.contractId) {
51
51
  const asTyped = flags.contractAddressSource === "at-address"
52
52
  ? `@${flags.contractId}`
@@ -64,8 +64,8 @@ export async function repositoryDirectory(flags) {
64
64
  }
65
65
  return flags.repo ? await stableRepoRoot(candidate) : candidate;
66
66
  }
67
- export async function projectDirectory(flags) {
67
+ export async function projectDirectory(flags, operation) {
68
68
  if (flags.repo)
69
- return await repositoryDirectory(flags);
69
+ return await repositoryDirectory(flags, operation);
70
70
  return effectiveDirectory(flags);
71
71
  }
@@ -2,7 +2,7 @@ import { renderCompletionCandidates, renderCompletionScript } from "../../../com
2
2
  import { textResponse } from "../../../render/shared.js";
3
3
  import { projectDirectory } from "../../shared.js";
4
4
  export const handler = async (_stdin, flags) => {
5
- const cwd = await projectDirectory(flags);
5
+ const cwd = await projectDirectory(flags, "completion");
6
6
  if (flags.complete) {
7
7
  return textResponse(await renderCompletionCandidates({ cwd, word: flags.word, previous: flags.previous }));
8
8
  }
@@ -0,0 +1,197 @@
1
+ import { FlowError } from "../flow-error.js";
2
+ import { getCommandMetadata } from "./commands/registry.js";
3
+ import { BOOLEAN_FLAGS, CONTROL_FLAGS, SHORT_FLAG_ALIASES, VALUE_FLAGS, } from "./flags.js";
4
+ import { normalizeContractAddress, parseWaitTimeout } from "./parse-metadata.js";
5
+ function allowedFlagsForParse(command) {
6
+ const meta = getCommandMetadata(command);
7
+ return meta.parseFlags ?? meta.flags;
8
+ }
9
+ export function parseFlagName(raw) {
10
+ return raw.slice(2);
11
+ }
12
+ export function assertAllowedFlag(command, name) {
13
+ if (!allowedFlagsForParse(command).includes(name)) {
14
+ throw new FlowError("EMPTY_PARAM", `option --${name} is not valid for ${command}`);
15
+ }
16
+ }
17
+ export function assignFlag(flags, name, value) {
18
+ switch (name) {
19
+ case "cwd":
20
+ flags.cwd = String(value);
21
+ return;
22
+ case "repo":
23
+ flags.repo = String(value);
24
+ return;
25
+ case "contract":
26
+ flags.contractId = normalizeContractAddress(String(value));
27
+ flags.contractAddressSource = "explicit-flag";
28
+ return;
29
+ case "akuma":
30
+ flags.akuma = String(value);
31
+ return;
32
+ case "projection":
33
+ flags.projection = String(value);
34
+ return;
35
+ case "alias":
36
+ flags.alias = String(value);
37
+ return;
38
+ case "timeout":
39
+ case "wait":
40
+ flags.timeoutMs = parseWaitTimeout(String(value));
41
+ return;
42
+ case "model":
43
+ flags.model = String(value);
44
+ return;
45
+ case "effort":
46
+ flags.effort = String(value);
47
+ return;
48
+ case "incognito":
49
+ flags.incognito = Boolean(value);
50
+ return;
51
+ case "bare":
52
+ flags.bare = Boolean(value);
53
+ return;
54
+ case "detach":
55
+ flags.detach = Boolean(value);
56
+ return;
57
+ case "draft-only":
58
+ flags.draftOnly = Boolean(value);
59
+ return;
60
+ case "exclusive":
61
+ flags.exclusive = Boolean(value);
62
+ return;
63
+ case "place":
64
+ if (flags.bindPlace !== undefined) {
65
+ throw new FlowError("EMPTY_PARAM", "option --place may only be specified once");
66
+ }
67
+ flags.bindPlace = String(value);
68
+ return;
69
+ case "objective":
70
+ if (flags.bindObjective !== undefined) {
71
+ throw new FlowError("EMPTY_PARAM", "option --objective may only be specified once");
72
+ }
73
+ flags.bindObjective = String(value);
74
+ return;
75
+ case "scope":
76
+ if (flags.bindScope !== undefined) {
77
+ throw new FlowError("EMPTY_PARAM", "option --scope may only be specified once");
78
+ }
79
+ flags.bindScope = String(value);
80
+ return;
81
+ case "checks":
82
+ if (flags.bindChecks !== undefined) {
83
+ throw new FlowError("EMPTY_PARAM", "option --checks may only be specified once");
84
+ }
85
+ flags.bindChecks = String(value);
86
+ return;
87
+ case "task":
88
+ if (flags.bindTaskId !== undefined) {
89
+ throw new FlowError("EMPTY_PARAM", "option --task may only be specified once");
90
+ }
91
+ flags.bindTaskId = String(value);
92
+ return;
93
+ case "after":
94
+ flags.after = [...(flags.after ?? []), String(value)];
95
+ return;
96
+ case "force":
97
+ flags.force = Boolean(value);
98
+ return;
99
+ case "reason":
100
+ flags.reason = String(value);
101
+ return;
102
+ case "dry-run":
103
+ flags.dryRun = Boolean(value);
104
+ return;
105
+ case "waive":
106
+ flags.waivePaths = [...(flags.waivePaths ?? []), String(value)];
107
+ return;
108
+ case "shell":
109
+ flags.shell = String(value);
110
+ return;
111
+ case "word":
112
+ flags.word = String(value);
113
+ return;
114
+ case "previous":
115
+ flags.previous = String(value);
116
+ return;
117
+ case "complete":
118
+ flags.complete = Boolean(value);
119
+ return;
120
+ case "all":
121
+ flags.all = Boolean(value);
122
+ return;
123
+ case "target":
124
+ if (flags.target !== undefined) {
125
+ throw new FlowError("EMPTY_PARAM", "option --target may only be specified once");
126
+ }
127
+ flags.target = String(value);
128
+ return;
129
+ case "json":
130
+ flags.json = Boolean(value);
131
+ return;
132
+ case "done":
133
+ flags.done = Boolean(value);
134
+ return;
135
+ case "pri": {
136
+ const raw = String(value);
137
+ if (!/^[0-3]$/.test(raw)) {
138
+ throw new FlowError("EMPTY_PARAM", "task priority must be an integer from 0 through 3");
139
+ }
140
+ if (flags.taskPri !== undefined) {
141
+ throw new FlowError("EMPTY_PARAM", "option --pri may only be specified once");
142
+ }
143
+ flags.taskPri = Number(raw);
144
+ return;
145
+ }
146
+ case "needs":
147
+ flags.taskNeeds = [...(flags.taskNeeds ?? []), String(value)];
148
+ return;
149
+ case "drop-needs":
150
+ flags.taskDropNeeds = [...(flags.taskDropNeeds ?? []), String(value)];
151
+ return;
152
+ case "from":
153
+ flags.taskFrom = [...(flags.taskFrom ?? []), String(value)];
154
+ return;
155
+ case "parent":
156
+ if (flags.taskParent !== undefined) {
157
+ throw new FlowError("EMPTY_PARAM", "option --parent may only be specified once");
158
+ }
159
+ flags.taskParent = String(value);
160
+ return;
161
+ case "no-parent":
162
+ flags.taskNoParent = Boolean(value);
163
+ return;
164
+ case "title":
165
+ if (flags.taskTitle !== undefined) {
166
+ throw new FlowError("EMPTY_PARAM", "option --title may only be specified once");
167
+ }
168
+ flags.taskTitle = String(value);
169
+ return;
170
+ case "body":
171
+ if (flags.taskBody !== undefined) {
172
+ throw new FlowError("EMPTY_PARAM", "option --body may only be specified once");
173
+ }
174
+ flags.taskBody = String(value);
175
+ return;
176
+ default:
177
+ throw new FlowError("EMPTY_PARAM", `unknown option: --${name}`);
178
+ }
179
+ }
180
+ export function readValueFlag(name, token, eq, tokens, index) {
181
+ if (eq !== -1) {
182
+ const value = token.slice(eq + 1);
183
+ if (value === "" && name !== "target" && name !== "body") {
184
+ throw new FlowError("EMPTY_PARAM", `option --${name} requires a value`);
185
+ }
186
+ return { value, nextIndex: index };
187
+ }
188
+ const value = tokens[index + 1];
189
+ if (value === undefined || value === "--" || value.startsWith("--")) {
190
+ throw new FlowError("EMPTY_PARAM", `option --${name} requires a value`);
191
+ }
192
+ if (value === "" && name !== "target" && name !== "body") {
193
+ throw new FlowError("EMPTY_PARAM", `option --${name} requires a value`);
194
+ }
195
+ return { value, nextIndex: index + 1 };
196
+ }
197
+ export { BOOLEAN_FLAGS, CONTROL_FLAGS, SHORT_FLAG_ALIASES, VALUE_FLAGS };
@@ -0,0 +1,129 @@
1
+ import { FlowError } from "../flow-error.js";
2
+ import { HELP_FLAGS, SHORT_FLAG_ALIASES, VALUE_FLAGS } from "./flags.js";
3
+ import { commandParseOrder, getCommandMetadata } from "./commands/registry.js";
4
+ import { TASK_SUBCOMMANDS } from "./types.js";
5
+ export function normalizeContractAddress(raw) {
6
+ const value = raw.startsWith("@") ? raw.slice(1) : raw;
7
+ if (!value.trim()) {
8
+ throw new FlowError("EMPTY_PARAM", "contract address cannot be empty");
9
+ }
10
+ return value.trim();
11
+ }
12
+ export function isCallCommand(command) {
13
+ return command === "call";
14
+ }
15
+ export function isPetitionDryRunInvocation(tokens) {
16
+ const boundary = tokens.indexOf("--");
17
+ const head = boundary === -1 ? tokens : tokens.slice(0, boundary);
18
+ if (!head.some((token) => token === "--dry-run" || token.startsWith("--dry-run="))) {
19
+ return false;
20
+ }
21
+ for (let index = 0; index < head.length; index += 1) {
22
+ const token = head[index];
23
+ if (HELP_FLAGS.has(token) || token.startsWith("@")) {
24
+ continue;
25
+ }
26
+ if (/^-[A-Za-z]$/.test(token)) {
27
+ const name = SHORT_FLAG_ALIASES[token.slice(1)];
28
+ if (name && VALUE_FLAGS.has(name)) {
29
+ index += 1;
30
+ }
31
+ continue;
32
+ }
33
+ if (token.startsWith("--")) {
34
+ const eq = token.indexOf("=");
35
+ const name = (eq === -1 ? token : token.slice(0, eq)).slice(2);
36
+ if (eq === -1 && VALUE_FLAGS.has(name)) {
37
+ index += 1;
38
+ }
39
+ continue;
40
+ }
41
+ return token === "petition";
42
+ }
43
+ return false;
44
+ }
45
+ export function commandUsesPromptInput(command) {
46
+ return isCallCommand(command) || command === "tell" || command === "revive";
47
+ }
48
+ export function parseWaitTimeout(value) {
49
+ const matched = /^(?<amount>[1-9][0-9]*)(?<unit>[smh])$/.exec(value);
50
+ if (!matched?.groups) {
51
+ throw new FlowError("EMPTY_PARAM", "wait timeout must use <positive integer><s|m|h>, for example 45s, 5m, or 2h (maximum 24h)");
52
+ }
53
+ const amount = Number(matched.groups.amount);
54
+ const multiplier = matched.groups.unit === "s" ? 1_000 : matched.groups.unit === "m" ? 60_000 : 3_600_000;
55
+ const timeoutMs = amount * multiplier;
56
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs > 24 * 3_600_000) {
57
+ throw new FlowError("EMPTY_PARAM", "wait timeout must use <positive integer><s|m|h>, for example 45s, 5m, or 2h (maximum 24h)");
58
+ }
59
+ return timeoutMs;
60
+ }
61
+ export function commandPositionalRange(command) {
62
+ if (command === "akuma view") {
63
+ return { min: 1, max: 1, label: "<name>" };
64
+ }
65
+ if (command === "log") {
66
+ return { min: 0, max: 0, label: "" };
67
+ }
68
+ if (isCallCommand(command)) {
69
+ return { min: 1, max: 1, label: "<BODY|->" };
70
+ }
71
+ if (command === "tell") {
72
+ return { min: 2, max: 2, label: "<PROJECTION> <BODY|->" };
73
+ }
74
+ if (command === "revive") {
75
+ return { min: 1, max: 2, label: "<ARTIFACT> [BODY|-]" };
76
+ }
77
+ if (command === "wait") {
78
+ return { min: 1, max: 1, label: "<projection-address>" };
79
+ }
80
+ if (command === "kill") {
81
+ return { min: 1, max: 1, label: "<projection-address>" };
82
+ }
83
+ if (command === "status") {
84
+ return { min: 0, max: 1, label: "[projection-id]" };
85
+ }
86
+ if (command === "task add") {
87
+ return { min: 1, max: 1, label: "<BODY|->" };
88
+ }
89
+ if (command === "task view" || command === "task start" || command === "task drop") {
90
+ return { min: 1, max: 1, label: "<ID>" };
91
+ }
92
+ if (command === "task update") {
93
+ return { min: 1, max: 1, label: "<ID>" };
94
+ }
95
+ if (command === "task stop" || command === "task done") {
96
+ return { min: 0, max: 1, label: "[ID]" };
97
+ }
98
+ return { min: 0, max: 0 };
99
+ }
100
+ export function commandRequiresStdin(command) {
101
+ return getCommandMetadata(command).stdin === "required";
102
+ }
103
+ export function resolveCommand(tokens) {
104
+ if (tokens[0] === "petition" && (tokens[1] === "claim" || tokens[1] === "forfeit")) {
105
+ throw new FlowError("UNKNOWN_COMMAND", `unknown command: petition ${tokens[1]}`);
106
+ }
107
+ for (const command of commandParseOrder()) {
108
+ const parts = command.split(" ");
109
+ if (parts.every((part, index) => tokens[index] === part)) {
110
+ return { command, rest: tokens.slice(parts.length) };
111
+ }
112
+ }
113
+ const [head] = tokens;
114
+ if (head === "skills") {
115
+ return { command: "skills", rest: tokens.slice(1) };
116
+ }
117
+ if (!head) {
118
+ throw new FlowError("EMPTY_PARAM", "command cannot be empty");
119
+ }
120
+ throw new FlowError("UNKNOWN_COMMAND", `unknown command: ${head ?? ""}`.trim());
121
+ }
122
+ export function splitAtLiteralBoundary(tokens) {
123
+ const boundary = tokens.indexOf("--");
124
+ if (boundary === -1) {
125
+ return { head: tokens, payload: [] };
126
+ }
127
+ return { head: tokens.slice(0, boundary), payload: tokens.slice(boundary + 1) };
128
+ }
129
+ export { TASK_SUBCOMMANDS };
@@ -0,0 +1,65 @@
1
+ import { FlowError } from "../flow-error.js";
2
+ import { assignFlag } from "./parse-flags.js";
3
+ import { normalizeContractAddress } from "./parse-metadata.js";
4
+ const COMMISSION_SELECTOR_COMMANDS = new Set([
5
+ "arc",
6
+ "amend",
7
+ "call",
8
+ "wait",
9
+ "tell",
10
+ "kill",
11
+ "log",
12
+ "renew",
13
+ "petition",
14
+ "forfeit",
15
+ ]);
16
+ function formatSelectorList(items) {
17
+ if (items.length <= 1) {
18
+ return items[0] ?? "";
19
+ }
20
+ if (items.length === 2) {
21
+ return `${items[0]} and ${items[1]}`;
22
+ }
23
+ return `${items.slice(0, -1).join(", ")}, and ${items[items.length - 1]}`;
24
+ }
25
+ export function applyCommissionSelector(command, flags, atTokens, contractFlagRaws) {
26
+ if (atTokens.length > 0 && contractFlagRaws.length > 0) {
27
+ throw new FlowError("EMPTY_PARAM", `commission address supplied twice: ${atTokens[0]} and --contract ${contractFlagRaws[0]}; use exactly one form`);
28
+ }
29
+ if (atTokens.length > 1) {
30
+ throw new FlowError("EMPTY_PARAM", `${command} accepts one commission address; got ${formatSelectorList(atTokens)}`);
31
+ }
32
+ if (contractFlagRaws.length > 1) {
33
+ throw new FlowError("EMPTY_PARAM", `${command} accepts one commission address; got ${formatSelectorList(contractFlagRaws.map((raw) => `--contract ${raw}`))}`);
34
+ }
35
+ if (atTokens.length === 1) {
36
+ flags.contractId = normalizeContractAddress(atTokens[0]);
37
+ flags.contractAddressSource = "at-address";
38
+ }
39
+ else if (contractFlagRaws.length === 1) {
40
+ assignFlag(flags, "contract", contractFlagRaws[0]);
41
+ }
42
+ }
43
+ export function assertCommissionSelectorAllowed(command, flags) {
44
+ if (flags.contractId === undefined || COMMISSION_SELECTOR_COMMANDS.has(command)) {
45
+ return;
46
+ }
47
+ throw new FlowError("EMPTY_PARAM", command === "bind"
48
+ ? `bind creates a commission and does not accept @${flags.contractId}; use -C/--cwd to select the repository`
49
+ : `contract address @${flags.contractId} is not valid for ${command}`);
50
+ }
51
+ export function assertProjectionSelectorPresent(command, flags, atTokens, contractFlagRaws, restLength, payloadLength) {
52
+ if ((command !== "wait" && command !== "tell") || flags.contractId === undefined) {
53
+ return;
54
+ }
55
+ const selectorSpelling = flags.contractAddressSource === "at-address"
56
+ ? atTokens[0] ?? `@${flags.contractId}`
57
+ : `--contract ${contractFlagRaws[0] ?? flags.contractId}`;
58
+ const message = `${command} requires a projection id or alias; ${selectorSpelling} only limits projection lookup, so add the projection address`;
59
+ if (command === "wait" && restLength + payloadLength === 0) {
60
+ throw new FlowError("EMPTY_PARAM", message);
61
+ }
62
+ if (command === "tell" && restLength === 0) {
63
+ throw new FlowError("EMPTY_PARAM", message);
64
+ }
65
+ }