@paradigma-inc/flywheel 0.1.73 → 0.1.77
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/README.md +29 -0
- package/package.json +1 -1
- package/skills/flywheel/compute/managed-compute.md +1 -1
- package/skills/flywheel/references/INTERFACES.md +2 -1
- package/skills/flywheel/references/experiment-design-protocol.md +5 -5
- package/skills/flywheel/references/flywheel-cli-tool-map.md +1 -3
- package/skills/flywheel/references/flywheel-mcp-tool-map.md +5 -7
- package/skills/flywheel/usage-and-workflows/what-to-do-with-flywheel.md +2 -2
- package/skills/flywheel/web-ui/flywheel-webui-map.md +2 -3
- package/skills/flywheel/web-ui/the-flywheel-web-ui.md +1 -1
- package/skills/flywheel-auto/SKILL.md +9 -13
- package/skills/flywheel-auto/references/INTERFACES.md +2 -1
- package/skills/flywheel-auto/references/experiment-design-protocol.md +5 -5
- package/skills/flywheel-auto/references/flywheel-cli-tool-map.md +0 -2
- package/skills/flywheel-auto/references/flywheel-mcp-tool-map.md +4 -6
- package/skills/flywheel-lookahead/SKILL.md +7 -9
- package/skills/flywheel-lookahead/references/INTERFACES.md +2 -1
- package/skills/flywheel-lookahead/references/flywheel-cli-tool-map.md +0 -2
- package/skills/flywheel-lookahead/references/flywheel-mcp-tool-map.md +4 -6
- package/skills/flywheel-reproduce/SKILL.md +7 -8
- package/skills/flywheel-reproduce/references/INTERFACES.md +2 -1
- package/skills/flywheel-reproduce/references/experiment-design-protocol.md +5 -5
- package/skills/flywheel-reproduce/references/flywheel-cli-tool-map.md +0 -2
- package/skills/flywheel-reproduce/references/flywheel-mcp-tool-map.md +4 -6
- package/skills/flywheel-to-graph/SKILL.md +3 -4
- package/skills/flywheel-to-graph/references/INTERFACES.md +2 -1
- package/skills/flywheel-to-graph/references/flywheel-cli-tool-map.md +0 -2
- package/skills/flywheel-to-graph/references/flywheel-mcp-tool-map.md +4 -6
- package/skills/flywheel-tree/SKILL.md +1 -2
- package/skills/flywheel-tree/references/workflow.md +1 -5
- package/skills/flywheel-tree/scripts/render_tree.py +2 -23
- package/src/cli.mjs +55 -0
- package/src/completion.mjs +326 -0
- package/src/public-command-metadata.mjs +240 -0
- package/src/runtime/delegate.mjs +2 -2
- package/src/runtime/required-runtime-commands.json +0 -2
- package/src/runtime/vendor/flywheel-cli-dist/commands/admin-graphs-export-stream.js +0 -3
- package/src/runtime/vendor/flywheel-cli-dist/commands/admin-graphs-export-stream.js.map +1 -1
- package/src/runtime/vendor/flywheel-cli-dist/commands/admin-graphs-list.js +0 -3
- package/src/runtime/vendor/flywheel-cli-dist/commands/admin-graphs-list.js.map +1 -1
- package/src/runtime/vendor/flywheel-cli-dist/commands/help-metadata.d.ts +32 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/help-metadata.js +112 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/help-metadata.js.map +1 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/help.d.ts +2 -13
- package/src/runtime/vendor/flywheel-cli-dist/commands/help.js +6 -128
- package/src/runtime/vendor/flywheel-cli-dist/commands/help.js.map +1 -1
- package/src/runtime/vendor/flywheel-cli-dist/commands/index.js +0 -4
- package/src/runtime/vendor/flywheel-cli-dist/commands/index.js.map +1 -1
- package/src/runtime/vendor/flywheel-cli-dist/commands/nodes-resolve-slug.js +1 -1
- package/src/runtime/vendor/flywheel-cli-dist/commands/nodes-resolve-slug.js.map +1 -1
- package/src/runtime/vendor/flywheel-cli-dist/commands/registry/admin.js +1 -3
- package/src/runtime/vendor/flywheel-cli-dist/commands/registry/admin.js.map +1 -1
- package/src/runtime/vendor/flywheel-cli-dist/commands/registry/lifecycle.js +0 -36
- package/src/runtime/vendor/flywheel-cli-dist/commands/registry/lifecycle.js.map +1 -1
- package/src/runtime/vendor/flywheel-cli-dist/commands/registry.d.ts +1 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/registry.js +0 -8
- package/src/runtime/vendor/flywheel-cli-dist/commands/registry.js.map +1 -1
- package/src/runtime/vendor/flywheel-cli-dist/commands/usage-spec.d.ts +6 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/usage-spec.js +342 -0
- package/src/runtime/vendor/flywheel-cli-dist/commands/usage-spec.js.map +1 -0
- package/src/runtime/vendor/flywheel-cli-dist/main.js +129 -2
- package/src/runtime/vendor/flywheel-cli-dist/main.js.map +1 -1
- package/src/runtime/vendor/flywheel-cli-dist/output/format.js +0 -1
- package/src/runtime/vendor/flywheel-cli-dist/output/format.js.map +1 -1
- package/src/runtime/vendor/manifest.json +7 -7
- package/src/unified-cli.mjs +23 -46
- package/src/runtime/vendor/flywheel-cli-dist/commands/nodes-stage-backfill-hypothesis.d.ts +0 -2
- package/src/runtime/vendor/flywheel-cli-dist/commands/nodes-stage-backfill-hypothesis.js +0 -7
- package/src/runtime/vendor/flywheel-cli-dist/commands/nodes-stage-backfill-hypothesis.js.map +0 -1
- package/src/runtime/vendor/flywheel-cli-dist/commands/nodes-stage-backfill-insights.d.ts +0 -2
- package/src/runtime/vendor/flywheel-cli-dist/commands/nodes-stage-backfill-insights.js +0 -7
- package/src/runtime/vendor/flywheel-cli-dist/commands/nodes-stage-backfill-insights.js.map +0 -1
|
@@ -123,8 +123,6 @@ These commands have no MCP counterpart. They cover auth/profile management, acco
|
|
|
123
123
|
- `flywheel nodes:create` (mutating; scopes: `write`): Create a minimal node (title only) and emit graph-change event.
|
|
124
124
|
- `flywheel nodes:files` (read; scopes: `read`): List files attached to a node.
|
|
125
125
|
- `flywheel nodes:sharing:summaries` (read; scopes: `read`): Read access summaries for multiple nodes in one batch.
|
|
126
|
-
- `flywheel nodes:stage:backfill:hypothesis` (mutating; scopes: `write`): Derive hypothesis from staged node content.
|
|
127
|
-
- `flywheel nodes:stage:backfill:insights` (mutating; scopes: `write`): Extract structured insights from staged node content.
|
|
128
126
|
|
|
129
127
|
### Artifact helpers (CLI-only)
|
|
130
128
|
|
|
@@ -8,19 +8,17 @@ Flywheel is a graph-based system for tracking research work, decisions, and evid
|
|
|
8
8
|
|
|
9
9
|
- Use `flywheel_get_contract` + `flywheel_get_contract_section` as canonical contract references.
|
|
10
10
|
- Node state is canonical durable state; avoid relying on ephemeral transcript state.
|
|
11
|
-
- Node
|
|
11
|
+
- Node body state is `title`, Markdown `content`, and optional `summary`; represent observations, decisions, experiments, and evidence with Markdown structure, tags, artifacts, and executions.
|
|
12
12
|
- Node references include immutable `node_id` and optional immutable `slug_name`; prefer communicating both together for human clarity and disambiguation.
|
|
13
|
-
- Insight nodes should represent conceptual observations (theoretical insights, intuitions, motivations, decision-relevant framing); empirical nodes should represent experiments with explicit hypotheses and measured outcomes.
|
|
14
13
|
- Graph topology should encode logical/causal relations between concepts and experiments. Avoid defaulting to shallow root-only branching unless work items are truly independent.
|
|
15
14
|
- Node staging is local (non-canonical) and commit is the only canonical persistence boundary (`commit_new_node`, `commit_node`).
|
|
16
15
|
- Mutating node writes are optimistic-locking operations: read latest state, pass `expected_revision` or `base_committed_revision` as required, and handle `409 conflict` with explicit reconciliation.
|
|
17
16
|
- Mutating operations are idempotent; MCP tool transport auto-manages `Idempotency-Key` on mutating tool calls.
|
|
18
17
|
- Existing-node field editing may use a session-scoped stage lease (`flywheel_acquire_stage_lease`, `flywheel_heartbeat_stage_lease`, `flywheel_release_stage_lease`) to coordinate local staged state before commit.
|
|
19
18
|
- Existing-node commit publishes a full staged payload under an active stage lease: commit requests require `stage_session_id`, `base_committed_revision`, and `staged_payload`; conflicts on stale committed revisions are surfaced directly and are not transport-retried.
|
|
20
|
-
-
|
|
21
|
-
- Committed node state must still satisfy strict contract (`summary` required; `empirical` requires hypothesis plus artifacts or `no_artifacts_reason`; `insight` requires non-empty insights).
|
|
19
|
+
- Committed node state uses the same canonical body fields for every node; `summary` may be empty when the node body is intentionally represented by content, artifacts, tags, or executions.
|
|
22
20
|
- When code is involved, pass `repo_url`/`branch_name`/`head_commit_sha` and align git structure with graph topology where practical (without forcing one-to-one mapping).
|
|
23
|
-
-
|
|
21
|
+
- Content, summaries, and artifacts should be reproduction-grade: enough setup, method, evidence, and interpretation for another reader to reproduce or audit results.
|
|
24
22
|
- Empirical workflow is hypothesis-driven: launch execution, inspect outcomes, publish evidence artifacts, and commit only after terminal status.
|
|
25
23
|
- For empirical work, publish evidence with `flywheel_prepare_artifact_uploads`, upload raw file bytes, then `flywheel_finalize_artifact_uploads` before commit.
|
|
26
24
|
- Artifact metadata records expose a non-empty `title` suitable for display labels; title normalization must never derive from `storage_url`.
|
|
@@ -135,7 +133,7 @@ Flywheel is a graph-based system for tracking research work, decisions, and evid
|
|
|
135
133
|
8. `flywheel_list_executions`: Poll until execution reaches terminal status.
|
|
136
134
|
9. `flywheel_prepare_artifact_uploads`: Prepare signed upload URLs for empirical evidence.
|
|
137
135
|
10. `raw_file_upload`: Upload raw bytes to each signed URL with required headers.
|
|
138
|
-
11. `flywheel_finalize_artifact_uploads`: Finalize prepared uploads before commit
|
|
136
|
+
11. `flywheel_finalize_artifact_uploads`: Finalize prepared uploads before commit when the work produced evidence artifacts.
|
|
139
137
|
12. `flywheel_commit_node`: Commit terminal empirical node once contract requirements are satisfied.
|
|
140
138
|
|
|
141
139
|
## Runtime Guidance
|
|
@@ -64,8 +64,7 @@ Before authoring, recover or establish these inputs:
|
|
|
64
64
|
- Create a new node with `flywheel_commit_new_node` (MCP) or `flywheel nodes:commit-new` (CLI) when needed.
|
|
65
65
|
- Update an existing node with `flywheel_get_node` + `flywheel_acquire_stage_lease` + `flywheel_commit_node` (MCP) or `flywheel nodes:get` + `flywheel nodes:stage:lease:acquire` + `flywheel nodes:commit` (CLI) when continuing work.
|
|
66
66
|
4. Put the primary page markdown or source narrative in the node body/readme field (`content`).
|
|
67
|
-
-
|
|
68
|
-
- Populate `hypothesis`, `insights`, `outcome`, and similar fields only when the source actually supports them.
|
|
67
|
+
- Put claims, rationale, methods, outcomes, and continuation notes in `content`; keep `summary` concise.
|
|
69
68
|
5. Publish supporting files with the artifact upload flow.
|
|
70
69
|
- For exact artifact contract details, load [references/ARTIFACTS.md](references/ARTIFACTS.md).
|
|
71
70
|
- Use `flywheel_prepare_artifact_uploads` (MCP) or `flywheel artifacts:upload:prepare` (CLI).
|
|
@@ -90,7 +89,7 @@ When the skill completes a pass, it should leave behind:
|
|
|
90
89
|
|
|
91
90
|
## Persisted Authoring Contract
|
|
92
91
|
|
|
93
|
-
When the source-to-graph work spans multiple pages or multiple passes, use a dedicated
|
|
92
|
+
When the source-to-graph work spans multiple pages or multiple passes, use a dedicated node as the durable authoring controller for that corpus. Do not rely on unstated product fields or fresh chat context.
|
|
94
93
|
|
|
95
94
|
For the canonical entity model and public contract terminology behind this controller, load [references/INTERFACES.md](references/INTERFACES.md).
|
|
96
95
|
|
|
@@ -99,7 +98,7 @@ Use this storage convention:
|
|
|
99
98
|
1. Reuse an existing authoring control node when one already governs the same corpus; otherwise create one with `flywheel_commit_new_node` (MCP) or `flywheel nodes:commit-new` (CLI).
|
|
100
99
|
2. Put the canonical authoring contract in node `content`.
|
|
101
100
|
3. Put a one-line synopsis in `summary`.
|
|
102
|
-
4. Put the current mapping rationale or continuation note in `
|
|
101
|
+
4. Put the current mapping rationale or continuation note in `content`.
|
|
103
102
|
5. Before committing control-node edits, acquire or refresh the stage lease with `flywheel_acquire_stage_lease` (and `flywheel_heartbeat_stage_lease` for long edits) — or in CLI mode, `flywheel nodes:stage:lease:acquire` and `flywheel nodes:stage:lease:heartbeat`.
|
|
104
103
|
6. Read the control node with `flywheel_get_node` (MCP) or `flywheel nodes:get` (CLI) before later passes that need a fresh `expected_revision`.
|
|
105
104
|
7. Commit the control node with `flywheel_commit_node` (MCP) or `flywheel nodes:commit` (CLI) once the contract is coherent, even if downstream source nodes remain staged or in progress.
|
|
@@ -19,9 +19,10 @@ Canonical reference for Flywheel MCP public interfaces and contract pointers.
|
|
|
19
19
|
## Canonical Entity Model
|
|
20
20
|
|
|
21
21
|
- Canonical durable entity: `node`.
|
|
22
|
-
-
|
|
22
|
+
- Canonical node body fields: `title`, Markdown `content`, and optional `summary`.
|
|
23
23
|
- Node references: `node_id`, `slug_name`.
|
|
24
24
|
- `slug_name` format: `adjective-noun-####` (immutable when present).
|
|
25
|
+
- Removed typed node body fields (`kind`, `node_type`, `hypothesis`, `insights`, `no_artifacts_reason`) are not write fields.
|
|
25
26
|
- Approval sessions and compute grants are distinct entities; approvals are session-scoped bindings.
|
|
26
27
|
- Campaign projections are derived from root config and attempt submission JSON artifacts.
|
|
27
28
|
|
|
@@ -123,8 +123,6 @@ These commands have no MCP counterpart. They cover auth/profile management, acco
|
|
|
123
123
|
- `flywheel nodes:create` (mutating; scopes: `write`): Create a minimal node (title only) and emit graph-change event.
|
|
124
124
|
- `flywheel nodes:files` (read; scopes: `read`): List files attached to a node.
|
|
125
125
|
- `flywheel nodes:sharing:summaries` (read; scopes: `read`): Read access summaries for multiple nodes in one batch.
|
|
126
|
-
- `flywheel nodes:stage:backfill:hypothesis` (mutating; scopes: `write`): Derive hypothesis from staged node content.
|
|
127
|
-
- `flywheel nodes:stage:backfill:insights` (mutating; scopes: `write`): Extract structured insights from staged node content.
|
|
128
126
|
|
|
129
127
|
### Artifact helpers (CLI-only)
|
|
130
128
|
|
|
@@ -8,19 +8,17 @@ Flywheel is a graph-based system for tracking research work, decisions, and evid
|
|
|
8
8
|
|
|
9
9
|
- Use `flywheel_get_contract` + `flywheel_get_contract_section` as canonical contract references.
|
|
10
10
|
- Node state is canonical durable state; avoid relying on ephemeral transcript state.
|
|
11
|
-
- Node
|
|
11
|
+
- Node body state is `title`, Markdown `content`, and optional `summary`; represent observations, decisions, experiments, and evidence with Markdown structure, tags, artifacts, and executions.
|
|
12
12
|
- Node references include immutable `node_id` and optional immutable `slug_name`; prefer communicating both together for human clarity and disambiguation.
|
|
13
|
-
- Insight nodes should represent conceptual observations (theoretical insights, intuitions, motivations, decision-relevant framing); empirical nodes should represent experiments with explicit hypotheses and measured outcomes.
|
|
14
13
|
- Graph topology should encode logical/causal relations between concepts and experiments. Avoid defaulting to shallow root-only branching unless work items are truly independent.
|
|
15
14
|
- Node staging is local (non-canonical) and commit is the only canonical persistence boundary (`commit_new_node`, `commit_node`).
|
|
16
15
|
- Mutating node writes are optimistic-locking operations: read latest state, pass `expected_revision` or `base_committed_revision` as required, and handle `409 conflict` with explicit reconciliation.
|
|
17
16
|
- Mutating operations are idempotent; MCP tool transport auto-manages `Idempotency-Key` on mutating tool calls.
|
|
18
17
|
- Existing-node field editing may use a session-scoped stage lease (`flywheel_acquire_stage_lease`, `flywheel_heartbeat_stage_lease`, `flywheel_release_stage_lease`) to coordinate local staged state before commit.
|
|
19
18
|
- Existing-node commit publishes a full staged payload under an active stage lease: commit requests require `stage_session_id`, `base_committed_revision`, and `staged_payload`; conflicts on stale committed revisions are surfaced directly and are not transport-retried.
|
|
20
|
-
-
|
|
21
|
-
- Committed node state must still satisfy strict contract (`summary` required; `empirical` requires hypothesis plus artifacts or `no_artifacts_reason`; `insight` requires non-empty insights).
|
|
19
|
+
- Committed node state uses the same canonical body fields for every node; `summary` may be empty when the node body is intentionally represented by content, artifacts, tags, or executions.
|
|
22
20
|
- When code is involved, pass `repo_url`/`branch_name`/`head_commit_sha` and align git structure with graph topology where practical (without forcing one-to-one mapping).
|
|
23
|
-
-
|
|
21
|
+
- Content, summaries, and artifacts should be reproduction-grade: enough setup, method, evidence, and interpretation for another reader to reproduce or audit results.
|
|
24
22
|
- Empirical workflow is hypothesis-driven: launch execution, inspect outcomes, publish evidence artifacts, and commit only after terminal status.
|
|
25
23
|
- For empirical work, publish evidence with `flywheel_prepare_artifact_uploads`, upload raw file bytes, then `flywheel_finalize_artifact_uploads` before commit.
|
|
26
24
|
- Artifact metadata records expose a non-empty `title` suitable for display labels; title normalization must never derive from `storage_url`.
|
|
@@ -135,7 +133,7 @@ Flywheel is a graph-based system for tracking research work, decisions, and evid
|
|
|
135
133
|
8. `flywheel_list_executions`: Poll until execution reaches terminal status.
|
|
136
134
|
9. `flywheel_prepare_artifact_uploads`: Prepare signed upload URLs for empirical evidence.
|
|
137
135
|
10. `raw_file_upload`: Upload raw bytes to each signed URL with required headers.
|
|
138
|
-
11. `flywheel_finalize_artifact_uploads`: Finalize prepared uploads before commit
|
|
136
|
+
11. `flywheel_finalize_artifact_uploads`: Finalize prepared uploads before commit when the work produced evidence artifacts.
|
|
139
137
|
12. `flywheel_commit_node`: Commit terminal empirical node once contract requirements are satisfied.
|
|
140
138
|
|
|
141
139
|
## Runtime Guidance
|
|
@@ -50,9 +50,8 @@ If no node id or slug is explicitly provided:
|
|
|
50
50
|
- In chat responses, wrap renderer stdout in a fenced `text` code block.
|
|
51
51
|
- Do not alter indentation or rewrite line prefixes.
|
|
52
52
|
4. Keep visual conventions produced by the renderer:
|
|
53
|
-
- Node label: `name
|
|
53
|
+
- Node label: `name | slugname` when slug exists.
|
|
54
54
|
- Omit `| slugname` entirely when slug is missing.
|
|
55
|
-
- `[insight]` is pastel red, `[empirical]` is pastel yellow, `[untyped]` is gray.
|
|
56
55
|
- `slugname` is always gray.
|
|
57
56
|
|
|
58
57
|
## Result Contract
|
|
@@ -95,13 +95,9 @@ Preserve renderer stdout exactly. Do not rewrite formatting.
|
|
|
95
95
|
Expected output format:
|
|
96
96
|
|
|
97
97
|
- Terminal tree with box-drawing characters
|
|
98
|
-
- Node label format: `name
|
|
98
|
+
- Node label format: `name | slugname` when slug exists, otherwise `name`
|
|
99
99
|
- Duplicate views for multi-parent nodes
|
|
100
100
|
- One unique color per multi-parent set
|
|
101
|
-
- Kind colors are fixed:
|
|
102
|
-
- `[insight]` pastel red
|
|
103
|
-
- `[empirical]` pastel yellow
|
|
104
|
-
- `[untyped]` gray
|
|
105
101
|
- `slugname` segment is always gray
|
|
106
102
|
- Summary with exactly:
|
|
107
103
|
- `root: <title>`
|
|
@@ -42,8 +42,6 @@ DEFAULT_COLORS = [
|
|
|
42
42
|
]
|
|
43
43
|
DEFAULT_RESET = "\u001b[0m"
|
|
44
44
|
GRAY = "\u001b[38;5;245m"
|
|
45
|
-
PASTEL_RED = "\u001b[38;5;217m"
|
|
46
|
-
PASTEL_YELLOW = "\u001b[38;5;229m"
|
|
47
45
|
|
|
48
46
|
BOX_MID = "├── "
|
|
49
47
|
BOX_LAST = "└── "
|
|
@@ -53,7 +51,6 @@ BOX_SPACE = " "
|
|
|
53
51
|
|
|
54
52
|
class Node(NamedTuple):
|
|
55
53
|
title: str
|
|
56
|
-
kind: str
|
|
57
54
|
slug_name: str
|
|
58
55
|
parent_ids: Tuple[str, ...]
|
|
59
56
|
child_ids: Tuple[str, ...]
|
|
@@ -138,8 +135,6 @@ def _build_node_map(nodes_payload: Iterable[dict]) -> Dict[str, Node]:
|
|
|
138
135
|
continue
|
|
139
136
|
|
|
140
137
|
title = _clean_text(raw.get("title")) or node_id
|
|
141
|
-
kind_raw = raw.get("kind", raw.get("node_type", ""))
|
|
142
|
-
kind = _clean_text(kind_raw) or "untyped"
|
|
143
138
|
slug_name_raw = raw.get("slug_name", raw.get("slug"))
|
|
144
139
|
slug_name = _clean_text(slug_name_raw)
|
|
145
140
|
|
|
@@ -155,7 +150,6 @@ def _build_node_map(nodes_payload: Iterable[dict]) -> Dict[str, Node]:
|
|
|
155
150
|
|
|
156
151
|
node_map[node_id] = Node(
|
|
157
152
|
title=title,
|
|
158
|
-
kind=kind,
|
|
159
153
|
slug_name=slug_name,
|
|
160
154
|
parent_ids=parent_ids,
|
|
161
155
|
child_ids=child_ids,
|
|
@@ -255,7 +249,6 @@ def _iter_render_lines(
|
|
|
255
249
|
|
|
256
250
|
def line_for(node_id: str) -> str:
|
|
257
251
|
node = node_map[node_id]
|
|
258
|
-
kind_token = f"[{node.kind}]"
|
|
259
252
|
seen_count = seen_multi_parent.get(node_id)
|
|
260
253
|
has_multi_parent = seen_count is not None
|
|
261
254
|
mp_color = multi_parent_color[node_id] if has_multi_parent else ""
|
|
@@ -267,27 +260,13 @@ def _iter_render_lines(
|
|
|
267
260
|
suffix = ""
|
|
268
261
|
|
|
269
262
|
if not use_color:
|
|
270
|
-
rendered =
|
|
263
|
+
rendered = node.title
|
|
271
264
|
if node.slug_name:
|
|
272
265
|
rendered += f" | {node.slug_name}"
|
|
273
266
|
rendered += suffix
|
|
274
267
|
return rendered
|
|
275
268
|
|
|
276
|
-
|
|
277
|
-
kind_color = PASTEL_RED
|
|
278
|
-
elif node.kind == "empirical":
|
|
279
|
-
kind_color = PASTEL_YELLOW
|
|
280
|
-
elif node.kind == "untyped":
|
|
281
|
-
kind_color = GRAY
|
|
282
|
-
else:
|
|
283
|
-
kind_color = ""
|
|
284
|
-
|
|
285
|
-
title_part = (
|
|
286
|
-
f"{mp_color}{node.title}{reset}" if has_multi_parent else node.title
|
|
287
|
-
)
|
|
288
|
-
kind_part = f"{kind_color}{kind_token}{reset}" if kind_color else kind_token
|
|
289
|
-
|
|
290
|
-
rendered = f"{title_part} {kind_part}"
|
|
269
|
+
rendered = f"{mp_color}{node.title}{reset}" if has_multi_parent else node.title
|
|
291
270
|
if node.slug_name:
|
|
292
271
|
rendered += f" {GRAY}| {node.slug_name}{reset}"
|
|
293
272
|
if suffix:
|
package/src/cli.mjs
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
isSkillOnlyHost,
|
|
16
16
|
normalizeHosts,
|
|
17
17
|
} from "./agents.mjs";
|
|
18
|
+
import { generateCompletionScript } from "./completion.mjs";
|
|
18
19
|
import {
|
|
19
20
|
readEntryServerUrl,
|
|
20
21
|
readNamedConfigEntry,
|
|
@@ -45,6 +46,7 @@ import {
|
|
|
45
46
|
buildGuidedSkillInstallPrompt,
|
|
46
47
|
runMcpModeCommand,
|
|
47
48
|
} from "./setup/modes/mcp-command.mjs";
|
|
49
|
+
import { COMPLETION_SHELL_CHOICES } from "./public-command-metadata.mjs";
|
|
48
50
|
import { findFlywheelMcpEntryNames } from "./setup/shared/prior-mode-detect.mjs";
|
|
49
51
|
import { renderSetupSummary } from "./setup/shared/summary.mjs";
|
|
50
52
|
|
|
@@ -475,6 +477,20 @@ async function runSetupCommandViaRouter(options) {
|
|
|
475
477
|
});
|
|
476
478
|
}
|
|
477
479
|
|
|
480
|
+
async function runCompletionCommand(shell, options) {
|
|
481
|
+
try {
|
|
482
|
+
const script = await generateCompletionScript({
|
|
483
|
+
shell,
|
|
484
|
+
includeBashCompletionLib: options.includeBashCompletionLib === true,
|
|
485
|
+
});
|
|
486
|
+
process.stdout.write(script);
|
|
487
|
+
} catch (error) {
|
|
488
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
489
|
+
process.stderr.write(`${message}\n`);
|
|
490
|
+
process.exitCode = 1;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
478
494
|
function parseUninstallScope(value) {
|
|
479
495
|
const normalized = String(value || "")
|
|
480
496
|
.trim()
|
|
@@ -1146,6 +1162,45 @@ Examples:
|
|
|
1146
1162
|
`,
|
|
1147
1163
|
);
|
|
1148
1164
|
|
|
1165
|
+
program
|
|
1166
|
+
.command("completion <shell>")
|
|
1167
|
+
.description("Generate a shell completion script")
|
|
1168
|
+
.addHelpText(
|
|
1169
|
+
"before",
|
|
1170
|
+
`${pc.bold(pc.cyan("✻ Flywheel Completion"))}
|
|
1171
|
+
|
|
1172
|
+
Generate shell completion scripts from the public Flywheel Usage spec.
|
|
1173
|
+
Supported shells: ${COMPLETION_SHELL_CHOICES.join(", ")}.
|
|
1174
|
+
Requires the official Usage CLI executable on PATH.
|
|
1175
|
+
`,
|
|
1176
|
+
)
|
|
1177
|
+
.option(
|
|
1178
|
+
"--include-bash-completion-lib",
|
|
1179
|
+
"Include Usage's bash completion library in bash output",
|
|
1180
|
+
)
|
|
1181
|
+
.addHelpText(
|
|
1182
|
+
"after",
|
|
1183
|
+
`
|
|
1184
|
+
Usage CLI prerequisite:
|
|
1185
|
+
${pc.green("mise use -g usage")}
|
|
1186
|
+
${pc.green("brew install usage")}
|
|
1187
|
+
${pc.green("cargo install usage-cli")}
|
|
1188
|
+
${pc.green("cargo binstall usage-cli")}
|
|
1189
|
+
${pc.green("pacman -S usage")}
|
|
1190
|
+
|
|
1191
|
+
Examples:
|
|
1192
|
+
${pc.green("flywheel completion bash --include-bash-completion-lib > ~/.local/share/bash-completion/completions/flywheel")}
|
|
1193
|
+
${pc.green("flywheel completion zsh > ~/.zsh/completions/_flywheel")}
|
|
1194
|
+
${pc.green("flywheel completion fish > ~/.config/fish/completions/flywheel.fish")}
|
|
1195
|
+
${pc.green("flywheel completion powershell > flywheel.ps1")}
|
|
1196
|
+
${pc.green("flywheel completion nu > ~/.config/nushell/autoload/flywheel.nu")}
|
|
1197
|
+
${pc.green("flywheel completion nushell > ~/.config/nushell/autoload/flywheel.nu")}
|
|
1198
|
+
`,
|
|
1199
|
+
)
|
|
1200
|
+
.action(async (shell, options) => {
|
|
1201
|
+
await runCompletionCommand(shell, options);
|
|
1202
|
+
});
|
|
1203
|
+
|
|
1149
1204
|
program
|
|
1150
1205
|
.command("setup")
|
|
1151
1206
|
.description("Set up Flywheel for your AI coding host")
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { accessSync, constants } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
loadPackageVersion,
|
|
7
|
+
usageExtrasArg,
|
|
8
|
+
usageVersionArg,
|
|
9
|
+
} from "./public-command-metadata.mjs";
|
|
10
|
+
import { runRuntimeCli } from "./runtime/delegate.mjs";
|
|
11
|
+
|
|
12
|
+
export const SUPPORTED_COMPLETION_SHELLS = [
|
|
13
|
+
"bash",
|
|
14
|
+
"zsh",
|
|
15
|
+
"fish",
|
|
16
|
+
"powershell",
|
|
17
|
+
"nu",
|
|
18
|
+
"nushell",
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const DEFAULT_WINDOWS_PATHEXT = ".COM;.EXE;.BAT;.CMD";
|
|
22
|
+
|
|
23
|
+
const SUPPORTED_COMPLETION_SHELLS_FOR_ERRORS = [
|
|
24
|
+
"bash",
|
|
25
|
+
"fish",
|
|
26
|
+
"nu",
|
|
27
|
+
"nushell",
|
|
28
|
+
"powershell",
|
|
29
|
+
"zsh",
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
const USAGE_INSTALL_HINTS = [
|
|
33
|
+
"mise use -g usage",
|
|
34
|
+
"brew install usage",
|
|
35
|
+
"cargo install usage-cli",
|
|
36
|
+
"cargo binstall usage-cli",
|
|
37
|
+
"pacman -S usage",
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
const DEFAULT_USAGE_BIN = "usage";
|
|
41
|
+
const MAX_COMPLETION_BUFFER_BYTES = 50 * 1024 * 1024;
|
|
42
|
+
|
|
43
|
+
function supportedShellList() {
|
|
44
|
+
return SUPPORTED_COMPLETION_SHELLS_FOR_ERRORS.join(", ");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function missingUsageMessage(name) {
|
|
48
|
+
return [
|
|
49
|
+
`official Usage CLI not found or not executable: ${name}`,
|
|
50
|
+
"Install usage with one of:",
|
|
51
|
+
...USAGE_INSTALL_HINTS.map((hint) => ` ${hint}`),
|
|
52
|
+
].join("\n");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function isExecutable(filePath) {
|
|
56
|
+
try {
|
|
57
|
+
accessSync(filePath, constants.X_OK);
|
|
58
|
+
return true;
|
|
59
|
+
} catch {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function hasPathSeparator(value) {
|
|
65
|
+
return (
|
|
66
|
+
value.includes(path.sep) ||
|
|
67
|
+
value.includes(path.posix.sep) ||
|
|
68
|
+
value.includes(path.win32.sep)
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function pathextCandidates(env, platform) {
|
|
73
|
+
const useWindowsDefaultPathext =
|
|
74
|
+
platform === "win32" && String(env.PATHEXT ?? "").trim() === "";
|
|
75
|
+
const rawPathext =
|
|
76
|
+
useWindowsDefaultPathext
|
|
77
|
+
? DEFAULT_WINDOWS_PATHEXT
|
|
78
|
+
: String(env.PATHEXT ?? "");
|
|
79
|
+
const candidates = [];
|
|
80
|
+
const seen = new Set();
|
|
81
|
+
for (const rawExtension of rawPathext.split(";")) {
|
|
82
|
+
const trimmed = rawExtension.trim();
|
|
83
|
+
if (!trimmed) continue;
|
|
84
|
+
const extension = trimmed.startsWith(".") ? trimmed : `.${trimmed}`;
|
|
85
|
+
const variants = useWindowsDefaultPathext
|
|
86
|
+
? [extension.toLowerCase(), extension, extension.toUpperCase()]
|
|
87
|
+
: [extension, extension.toLowerCase(), extension.toUpperCase()];
|
|
88
|
+
for (const candidate of variants) {
|
|
89
|
+
if (!seen.has(candidate)) {
|
|
90
|
+
candidates.push(candidate);
|
|
91
|
+
seen.add(candidate);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return candidates;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function executableNameCandidates(name, env, platform = process.platform) {
|
|
99
|
+
const candidates = [name];
|
|
100
|
+
const lowerName = name.toLowerCase();
|
|
101
|
+
const seen = new Set(candidates);
|
|
102
|
+
for (const extension of pathextCandidates(env, platform)) {
|
|
103
|
+
if (lowerName.endsWith(extension.toLowerCase())) continue;
|
|
104
|
+
const candidate = `${name}${extension}`;
|
|
105
|
+
if (!seen.has(candidate)) {
|
|
106
|
+
candidates.push(candidate);
|
|
107
|
+
seen.add(candidate);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return candidates;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function sanitizeUsageEnv(env = process.env) {
|
|
114
|
+
const sanitized = {};
|
|
115
|
+
for (const [key, value] of Object.entries(env)) {
|
|
116
|
+
if (key.toUpperCase().startsWith("FLYWHEEL_")) continue;
|
|
117
|
+
if (value !== undefined) {
|
|
118
|
+
sanitized[key] = value;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return sanitized;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function isWindowsCommandShim(filePath, platform) {
|
|
125
|
+
return (
|
|
126
|
+
platform === "win32" &&
|
|
127
|
+
[".bat", ".cmd"].includes(path.extname(filePath).toLowerCase())
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function quoteWindowsCommandPath(filePath) {
|
|
132
|
+
return `"${String(filePath).replaceAll('"', '""')}"`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function resolveWindowsCommandShell(env) {
|
|
136
|
+
return env.ComSpec || env.COMSPEC || "cmd.exe";
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function buildUsageInvocation({ usagePath, args, env, platform }) {
|
|
140
|
+
if (!isWindowsCommandShim(usagePath, platform)) {
|
|
141
|
+
return { command: usagePath, args };
|
|
142
|
+
}
|
|
143
|
+
const commandLine = `"${[quoteWindowsCommandPath(usagePath), ...args].join(" ")}"`;
|
|
144
|
+
return {
|
|
145
|
+
command: resolveWindowsCommandShell(env),
|
|
146
|
+
args: ["/d", "/s", "/c", commandLine],
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function normalizeCompletionShell(value) {
|
|
151
|
+
if (value === "nushell") {
|
|
152
|
+
return "nu";
|
|
153
|
+
}
|
|
154
|
+
if (SUPPORTED_COMPLETION_SHELLS.includes(value)) {
|
|
155
|
+
return value;
|
|
156
|
+
}
|
|
157
|
+
throw new Error(
|
|
158
|
+
`unsupported completion shell "${value}". Supported shells: ${supportedShellList()}`,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function buildUsageCompletionArgs({
|
|
163
|
+
shell,
|
|
164
|
+
includeBashCompletionLib = false,
|
|
165
|
+
}) {
|
|
166
|
+
const normalizedShell = normalizeCompletionShell(shell);
|
|
167
|
+
if (includeBashCompletionLib && normalizedShell !== "bash") {
|
|
168
|
+
throw new Error("--include-bash-completion-lib is only supported for bash");
|
|
169
|
+
}
|
|
170
|
+
const args = ["generate", "completion", normalizedShell, "flywheel", "-f", "-"];
|
|
171
|
+
if (includeBashCompletionLib) {
|
|
172
|
+
args.push("--include-bash-completion-lib");
|
|
173
|
+
}
|
|
174
|
+
return args;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function findExecutable(
|
|
178
|
+
name,
|
|
179
|
+
env = process.env,
|
|
180
|
+
platform = process.platform,
|
|
181
|
+
) {
|
|
182
|
+
if (!name) {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
if (path.isAbsolute(name) || hasPathSeparator(name)) {
|
|
186
|
+
return isExecutable(name) ? name : null;
|
|
187
|
+
}
|
|
188
|
+
const delimiter = platform === "win32" ? ";" : path.delimiter;
|
|
189
|
+
for (const entry of String(env.PATH ?? "").split(delimiter)) {
|
|
190
|
+
if (!entry) continue;
|
|
191
|
+
for (const candidateName of executableNameCandidates(name, env, platform)) {
|
|
192
|
+
const candidate = path.join(entry, candidateName);
|
|
193
|
+
if (isExecutable(candidate)) {
|
|
194
|
+
return candidate;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function capturePublicUsageSpec() {
|
|
202
|
+
let stdout = "";
|
|
203
|
+
let stderr = "";
|
|
204
|
+
const version = await loadPackageVersion();
|
|
205
|
+
const exitCode = await runRuntimeCli([
|
|
206
|
+
"--usage",
|
|
207
|
+
usageExtrasArg(),
|
|
208
|
+
usageVersionArg(version),
|
|
209
|
+
], {
|
|
210
|
+
stdout: (chunk) => {
|
|
211
|
+
stdout += chunk instanceof Uint8Array ? Buffer.from(chunk).toString("utf8") : chunk;
|
|
212
|
+
},
|
|
213
|
+
stderr: (chunk) => {
|
|
214
|
+
stderr += chunk instanceof Uint8Array ? Buffer.from(chunk).toString("utf8") : chunk;
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
if (exitCode !== 0) {
|
|
218
|
+
const detail = stderr.trim() ? `: ${stderr.trim()}` : "";
|
|
219
|
+
throw new Error(`failed to capture Flywheel Usage spec${detail}`);
|
|
220
|
+
}
|
|
221
|
+
return stdout;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function runUsageCompletion({ command, args, usageSpec, env }) {
|
|
225
|
+
return awaitChildProcess({
|
|
226
|
+
child: spawn(command, args, {
|
|
227
|
+
env: sanitizeUsageEnv(env),
|
|
228
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
229
|
+
}),
|
|
230
|
+
input: usageSpec,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function awaitChildProcess({ child, input }) {
|
|
235
|
+
return new Promise((resolve, reject) => {
|
|
236
|
+
let stdout = "";
|
|
237
|
+
let stderr = "";
|
|
238
|
+
let settled = false;
|
|
239
|
+
let stdinError = null;
|
|
240
|
+
|
|
241
|
+
function settle(callback, value) {
|
|
242
|
+
if (settled) return;
|
|
243
|
+
settled = true;
|
|
244
|
+
callback(value);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function append(streamName, chunk) {
|
|
248
|
+
if (streamName === "stdout") {
|
|
249
|
+
stdout += chunk.toString("utf8");
|
|
250
|
+
if (Buffer.byteLength(stdout, "utf8") > MAX_COMPLETION_BUFFER_BYTES) {
|
|
251
|
+
child.kill();
|
|
252
|
+
settle(reject, new Error("usage completion stdout exceeded 50 MiB"));
|
|
253
|
+
}
|
|
254
|
+
} else {
|
|
255
|
+
stderr += chunk.toString("utf8");
|
|
256
|
+
if (Buffer.byteLength(stderr, "utf8") > MAX_COMPLETION_BUFFER_BYTES) {
|
|
257
|
+
child.kill();
|
|
258
|
+
settle(reject, new Error("usage completion stderr exceeded 50 MiB"));
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
child.stdout.on("data", (chunk) => append("stdout", chunk));
|
|
264
|
+
child.stderr.on("data", (chunk) => append("stderr", chunk));
|
|
265
|
+
child.stdin.on("error", (error) => {
|
|
266
|
+
if (error.code === "EPIPE") {
|
|
267
|
+
stdinError = error;
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
child.kill();
|
|
271
|
+
settle(reject, error);
|
|
272
|
+
});
|
|
273
|
+
child.on("error", (error) => settle(reject, error));
|
|
274
|
+
child.on("close", (code, signal) => {
|
|
275
|
+
if (settled) return;
|
|
276
|
+
if (code === 0) {
|
|
277
|
+
if (stdinError) {
|
|
278
|
+
settle(
|
|
279
|
+
reject,
|
|
280
|
+
new Error(
|
|
281
|
+
`usage stdin closed before Flywheel Usage spec was written: ${stdinError.message}`,
|
|
282
|
+
),
|
|
283
|
+
);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
settle(resolve, stdout);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
const detail = stderr.trim() ? `\n${stderr.trim()}` : "";
|
|
290
|
+
const status = signal ? `signal ${signal}` : `exit code ${code}`;
|
|
291
|
+
settle(reject, new Error(`usage generate completion failed with ${status}.${detail}`));
|
|
292
|
+
});
|
|
293
|
+
child.stdin.end(input);
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export async function generateCompletionScript({
|
|
298
|
+
shell,
|
|
299
|
+
includeBashCompletionLib = false,
|
|
300
|
+
usageBin,
|
|
301
|
+
env = process.env,
|
|
302
|
+
platform = process.platform,
|
|
303
|
+
usageSpec,
|
|
304
|
+
captureUsageSpec = capturePublicUsageSpec,
|
|
305
|
+
} = {}) {
|
|
306
|
+
const args = buildUsageCompletionArgs({ shell, includeBashCompletionLib });
|
|
307
|
+
const usageName = usageBin ?? env.USAGE_BIN ?? DEFAULT_USAGE_BIN;
|
|
308
|
+
const usagePath = findExecutable(usageName, env, platform);
|
|
309
|
+
if (!usagePath) {
|
|
310
|
+
throw new Error(missingUsageMessage(usageName));
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const spec = usageSpec ?? (await captureUsageSpec());
|
|
314
|
+
const usageInvocation = buildUsageInvocation({
|
|
315
|
+
usagePath,
|
|
316
|
+
args,
|
|
317
|
+
env,
|
|
318
|
+
platform,
|
|
319
|
+
});
|
|
320
|
+
return await runUsageCompletion({
|
|
321
|
+
command: usageInvocation.command,
|
|
322
|
+
args: usageInvocation.args,
|
|
323
|
+
usageSpec: spec,
|
|
324
|
+
env,
|
|
325
|
+
});
|
|
326
|
+
}
|