opencode-teammate 0.1.0-next.1

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 (50) hide show
  1. package/.bunli/commands.gen.ts +87 -0
  2. package/.github/workflows/ci.yml +31 -0
  3. package/.github/workflows/release.yml +140 -0
  4. package/.oxfmtrc.json +3 -0
  5. package/.oxlintrc.json +4 -0
  6. package/.zed/settings.json +76 -0
  7. package/README.md +15 -0
  8. package/bunli.config.ts +11 -0
  9. package/bunup.config.ts +31 -0
  10. package/package.json +36 -0
  11. package/src/adapters/assets/index.ts +1 -0
  12. package/src/adapters/assets/specifications.ts +70 -0
  13. package/src/adapters/beads/agents.ts +105 -0
  14. package/src/adapters/beads/config.ts +17 -0
  15. package/src/adapters/beads/index.ts +4 -0
  16. package/src/adapters/beads/issues.ts +156 -0
  17. package/src/adapters/beads/specifications.ts +55 -0
  18. package/src/adapters/environments/index.ts +43 -0
  19. package/src/adapters/environments/worktrees.ts +78 -0
  20. package/src/adapters/teammates/index.ts +15 -0
  21. package/src/assets/agent/planner.md +196 -0
  22. package/src/assets/command/brainstorm.md +60 -0
  23. package/src/assets/command/specify.md +135 -0
  24. package/src/assets/command/work.md +247 -0
  25. package/src/assets/index.ts +37 -0
  26. package/src/cli/commands/manifest.ts +6 -0
  27. package/src/cli/commands/spec/sync.ts +47 -0
  28. package/src/cli/commands/work.ts +110 -0
  29. package/src/cli/index.ts +11 -0
  30. package/src/plugin.ts +45 -0
  31. package/src/tools/i-am-done.ts +44 -0
  32. package/src/tools/i-am-stuck.ts +49 -0
  33. package/src/tools/index.ts +2 -0
  34. package/src/use-cases/index.ts +5 -0
  35. package/src/use-cases/inject-beads-issue.ts +97 -0
  36. package/src/use-cases/sync-specifications.ts +48 -0
  37. package/src/use-cases/sync-teammates.ts +35 -0
  38. package/src/use-cases/track-specs.ts +91 -0
  39. package/src/use-cases/work-on-issue.ts +110 -0
  40. package/src/utils/chain.ts +60 -0
  41. package/src/utils/frontmatter.spec.ts +491 -0
  42. package/src/utils/frontmatter.ts +317 -0
  43. package/src/utils/opencode.ts +102 -0
  44. package/src/utils/polling.ts +41 -0
  45. package/src/utils/projects.ts +35 -0
  46. package/src/utils/shell/client.spec.ts +106 -0
  47. package/src/utils/shell/client.ts +117 -0
  48. package/src/utils/shell/error.ts +29 -0
  49. package/src/utils/shell/index.ts +2 -0
  50. package/tsconfig.json +9 -0
@@ -0,0 +1,87 @@
1
+ // This file was automatically generated by Bunli.
2
+ // You should NOT make any changes in this file as it will be overwritten.
3
+
4
+ import type { Command, CLI, GeneratedOptionMeta, RegisteredCommands, CommandOptions, GeneratedCommandMeta } from '@bunli/core'
5
+ import { createGeneratedHelpers, registerGeneratedStore } from '@bunli/core'
6
+
7
+ import SpecSync from '../src/cli/commands/spec/sync.js'
8
+ import Work from '../src/cli/commands/work.js'
9
+
10
+ // Narrow list of command names to avoid typeof-cycles in types
11
+ const names = ['spec/sync', 'work'] as const
12
+ type GeneratedNames = typeof names[number]
13
+
14
+ const modules: Record<GeneratedNames, Command<any>> = {
15
+ 'spec/sync': SpecSync,
16
+ 'work': Work
17
+ } as const
18
+
19
+ const metadata: Record<GeneratedNames, GeneratedCommandMeta> = {
20
+ 'spec/sync': {
21
+ name: 'spec/sync',
22
+ description: 'Syncs the specifications with beads epics',
23
+ options: {
24
+ 'target': { type: 'z.string.transform.optional', required: false, hasDefault: false, description: 'Specification file or directory to sync', short: 't', isTransform: true, fileType: 'directory', schema: {"type":"zod","method":"optional","args":[]}, validator: '(val) => true' },
25
+ 'watch': { type: 'z.boolean.default', required: true, hasDefault: true, default: false, description: 'Watch target for changes', short: 'w', schema: {"type":"zod","method":"default","args":[{"type":"unknown","raw":{"type":"BooleanLiteral","start":586,"end":591,"loc":{"start":{"line":22,"column":38,"index":586},"end":{"line":22,"column":43,"index":591}},"value":false}}]}, validator: '(val) => true' }
26
+ },
27
+ path: '../src/cli/commands/spec/sync'
28
+ },
29
+ 'work': {
30
+ name: 'work',
31
+ description: 'Starts RALPH loop on opened tasks',
32
+ alias: ["ralph"],
33
+ options: {
34
+ 'concurrency': { type: 'z.coerce.number.min.default', required: true, hasDefault: true, default: 1, description: 'How many workers can be spawned at the same time', short: 'c', min: 1, minLength: 1, schema: {"type":"zod","method":"default","args":[{"type":"unknown","raw":{"type":"NumericLiteral","start":862,"end":863,"loc":{"start":{"line":22,"column":57,"index":862},"end":{"line":22,"column":58,"index":863}},"extra":{"rawValue":1,"raw":"1"},"value":1}}]}, validator: '(val) => true' },
35
+ 'task': { type: 'z.string.optional', required: false, hasDefault: false, description: 'Task (including subtasks) to work on. When not provided, workers can be spawned for any ready tasks.', short: 't', schema: {"type":"zod","method":"optional","args":[]}, validator: '(val) => true' },
36
+ 'project': { type: 'z.string.optional', required: false, hasDefault: false, description: 'Project to work on. When not provided, workers can be spawned for any ready tasks.', short: 'p', schema: {"type":"zod","method":"optional","args":[]}, validator: '(val) => true' }
37
+ },
38
+ path: '../src/cli/commands/work'
39
+ }
40
+ } as const
41
+
42
+ export const generated = registerGeneratedStore(createGeneratedHelpers(modules, metadata))
43
+
44
+ export const commands = generated.commands
45
+ export const commandMeta = generated.metadata
46
+
47
+ export interface GeneratedCLI {
48
+ register(cli?: CLI<any>): GeneratedCLI
49
+ list(): Array<{
50
+ name: GeneratedNames
51
+ command: (typeof modules)[GeneratedNames]
52
+ metadata: (typeof metadata)[GeneratedNames]
53
+ }>
54
+ get<Name extends GeneratedNames>(name: Name): (typeof modules)[Name]
55
+ getMetadata<Name extends GeneratedNames>(name: Name): (typeof metadata)[Name]
56
+ getFlags<Name extends keyof RegisteredCommands & string>(name: Name): CommandOptions<Name>
57
+ getFlagsMeta<Name extends GeneratedNames>(name: Name): Record<string, GeneratedOptionMeta>
58
+ withCLI(cli: CLI<any>): { execute(name: string, options: unknown): Promise<void> }
59
+ }
60
+
61
+ export const cli: GeneratedCLI = {
62
+ register: (cliInstance?: CLI<any>) => { generated.register(cliInstance); return cli },
63
+ list: () => generated.list(),
64
+ get: <Name extends GeneratedNames>(name: Name) => generated.get(name),
65
+ getMetadata: <Name extends GeneratedNames>(name: Name) => generated.getMetadata(name),
66
+ getFlags: <Name extends keyof RegisteredCommands & string>(name: Name) => generated.getFlags(name) as CommandOptions<Name>,
67
+ getFlagsMeta: <Name extends GeneratedNames>(name: Name) => generated.getFlagsMeta(name),
68
+ withCLI: (cliInstance) => generated.withCLI(cliInstance)
69
+ }
70
+
71
+ // Enhanced helper functions
72
+ export const listCommands = () => generated.list().map(c => c.name)
73
+ export const getCommandApi = <Name extends GeneratedNames>(name: Name) => generated.getMetadata(name)
74
+ export const getTypedFlags = <Name extends GeneratedNames>(name: Name) => generated.getFlags(name) as CommandOptions<Name>
75
+ export const validateCommand = <Name extends GeneratedNames>(name: Name, flags: Record<string, unknown>) => generated.validateCommand(name, flags)
76
+ export const findCommandByName = <Name extends GeneratedNames>(name: Name) => generated.findByName(name)
77
+ export const findCommandsByDescription = (searchTerm: string) => generated.findByDescription(searchTerm)
78
+ export const getCommandNames = () => generated.getCommandNames()
79
+
80
+ // Auto-register on import for zero-config usage
81
+ export default cli
82
+
83
+ // Ensure module augmentation happens on import
84
+ declare module '@bunli/core' {
85
+ // Precise key mapping without typeof cycles
86
+ interface RegisteredCommands extends Record<GeneratedNames, Command<any>> {}
87
+ }
@@ -0,0 +1,31 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches:
7
+ - main
8
+
9
+ jobs:
10
+ checks:
11
+ name: Formatting & Lint
12
+ runs-on: ubuntu-latest
13
+
14
+ steps:
15
+ - name: Checkout
16
+ uses: actions/checkout@v4
17
+
18
+ - name: Setup Bun
19
+ uses: oven-sh/setup-bun@v2
20
+
21
+ - name: Install dependencies
22
+ run: bun install --frozen-lockfile
23
+
24
+ - name: Generate
25
+ run: bun run generate
26
+
27
+ - name: Check formatting
28
+ run: bun run fmt --check
29
+
30
+ - name: Run lint (includes type checks)
31
+ run: bun run lint
@@ -0,0 +1,140 @@
1
+ name: Release (next)
2
+
3
+ on:
4
+ workflow_run:
5
+ workflows: ["CI"]
6
+ types: [completed]
7
+
8
+ concurrency:
9
+ group: release-next-main
10
+ cancel-in-progress: false
11
+
12
+ jobs:
13
+ release-next:
14
+ if: >
15
+ github.event.workflow_run.conclusion == 'success' &&
16
+ github.event.workflow_run.head_branch == 'main'
17
+ runs-on: ubuntu-latest
18
+ permissions:
19
+ contents: write
20
+ id-token: write
21
+
22
+ env:
23
+ SOURCE_SHA: ${{ github.event.workflow_run.head_sha }}
24
+ RELEASE_BRANCH: release
25
+
26
+ steps:
27
+ - name: Checkout triggering main commit
28
+ uses: actions/checkout@v4
29
+ with:
30
+ fetch-depth: 0
31
+ ref: ${{ env.SOURCE_SHA }}
32
+
33
+ - name: Setup Bun
34
+ uses: oven-sh/setup-bun@v2
35
+
36
+ - name: Install dependencies
37
+ run: bun install --frozen-lockfile
38
+
39
+ - name: Prepare local release branch
40
+ shell: bash
41
+ run: |
42
+ git fetch origin --tags
43
+ if git ls-remote --heads origin "${RELEASE_BRANCH}" | grep -q "${RELEASE_BRANCH}$"; then
44
+ git fetch origin "${RELEASE_BRANCH}:${RELEASE_BRANCH}"
45
+ git checkout "${RELEASE_BRANCH}"
46
+ else
47
+ git checkout --orphan "${RELEASE_BRANCH}"
48
+ git rm -rf .
49
+ git checkout "${SOURCE_SHA}" -- .
50
+ git config user.name "github-actions[bot]"
51
+ git config user.email "github-actions[bot]@users.noreply.github.com"
52
+ git commit -m "chore(next): initialize ${RELEASE_BRANCH} from ${SOURCE_SHA:0:7}"
53
+ fi
54
+
55
+ - name: Sync source tree from triggering main commit
56
+ shell: bash
57
+ run: |
58
+ git rm -rf .
59
+ git checkout "${SOURCE_SHA}" -- .
60
+
61
+ - name: Read package name
62
+ id: pkg
63
+ shell: bash
64
+ run: |
65
+ PKG_NAME="$(node -p "require('./package.json').name")"
66
+ echo "name=${PKG_NAME}" >> "$GITHUB_OUTPUT"
67
+ echo "Package: ${PKG_NAME}"
68
+
69
+ - name: Resolve semver base from npm latest
70
+ id: base
71
+ shell: bash
72
+ run: |
73
+ PKG="${{ steps.pkg.outputs.name }}"
74
+ LATEST="$(npm view "${PKG}" version 2>/dev/null || true)"
75
+
76
+ if [ -z "${LATEST}" ]; then
77
+ # First ever publish for this package
78
+ MAJOR=0
79
+ MINOR=1
80
+ PATCH=0
81
+ else
82
+ # Expect X.Y.Z on latest channel
83
+ IFS='.' read -r MAJOR MINOR PATCH <<< "${LATEST}"
84
+ PATCH=$((PATCH + 1))
85
+ fi
86
+
87
+ BASE="${MAJOR}.${MINOR}.${PATCH}"
88
+ echo "base=${BASE}" >> "$GITHUB_OUTPUT"
89
+ echo "Base next line: ${BASE}"
90
+
91
+ - name: Compute next prerelease version
92
+ id: ver
93
+ shell: bash
94
+ run: |
95
+ BASE="${{ steps.base.outputs.base }}"
96
+
97
+ # tags look like: 1.2.1-next.1, 1.2.1-next.2, ...
98
+ LAST_TAG="$(git tag --list "${BASE}-next.*" --sort=-v:refname | head -n1 || true)"
99
+
100
+ if [ -z "${LAST_TAG}" ]; then
101
+ X=1
102
+ else
103
+ LAST_NUM="${LAST_TAG##*.}"
104
+ if [ -z "${LAST_NUM}" ] || [ "${LAST_NUM}" = "${LAST_TAG}" ]; then
105
+ X=1
106
+ else
107
+ X=$((LAST_NUM + 1))
108
+ fi
109
+ fi
110
+
111
+ VERSION="${BASE}-next.${X}+sha.${SOURCE_SHA:0:7}"
112
+
113
+ echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
114
+ echo "Computed next version: ${VERSION}"
115
+
116
+ - name: Bump package version
117
+ run: bun pm version "${{ steps.ver.outputs.version }}" --no-git-tag-version
118
+
119
+ - name: Build
120
+ run: bun run build
121
+
122
+ - name: Commit release on release branch
123
+ shell: bash
124
+ run: |
125
+ VERSION="${{ steps.ver.outputs.version }}"
126
+ git config user.name "github-actions[bot]"
127
+ git config user.email "github-actions[bot]@users.noreply.github.com"
128
+
129
+ git add -A
130
+ git commit -m "release(next): v${VERSION}"
131
+
132
+ # - name: Publish to npm (next dist-tag)
133
+ # env:
134
+ # NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }}
135
+ # run: bun publish --access public --tag next
136
+
137
+ - name: Push release history
138
+ shell: bash
139
+ run: |
140
+ git push origin "${RELEASE_BRANCH}"
package/.oxfmtrc.json ADDED
@@ -0,0 +1,3 @@
1
+ {
2
+ "ignorePatterns": [".bunli/**"]
3
+ }
package/.oxlintrc.json ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "./node_modules/oxlint/configuration_schema.json",
3
+ "ignorePatterns": [".bunli/**"]
4
+ }
@@ -0,0 +1,76 @@
1
+ // Folder-specific settings
2
+ //
3
+ // For a full list of overridable settings, and general information on folder-specific settings,
4
+ // see the documentation: https://zed.dev/docs/configuring-zed#settings-files
5
+ {
6
+ "format_on_save": "on",
7
+ "autosave": "on_focus_change",
8
+ "code_actions_on_format": {
9
+ "source.fixAll.oxlint": true,
10
+ "source.organizeImports.oxlint": true,
11
+ "source.removeUnusedImports": true
12
+ },
13
+ "languages": {
14
+ "JavaScript": {
15
+ "formatter": {
16
+ "language_server": {
17
+ "name": "oxfmt"
18
+ }
19
+ }
20
+ },
21
+ "TypeScript": {
22
+ "formatter": {
23
+ "language_server": {
24
+ "name": "oxfmt"
25
+ }
26
+ }
27
+ },
28
+ "JSON": {
29
+ "formatter": {
30
+ "language_server": {
31
+ "name": "oxfmt"
32
+ }
33
+ }
34
+ },
35
+ "JSONC": {
36
+ "formatter": {
37
+ "language_server": {
38
+ "name": "oxfmt"
39
+ }
40
+ }
41
+ },
42
+ "YAML": {
43
+ "formatter": {
44
+ "language_server": {
45
+ "name": "oxfmt"
46
+ }
47
+ }
48
+ },
49
+ "Markdown": {
50
+ "formatter": {
51
+ "language_server": {
52
+ "name": "oxfmt"
53
+ }
54
+ }
55
+ }
56
+ },
57
+ "lsp": {
58
+ "oxlint": {
59
+ "initialization_options": {
60
+ "typeAware": true,
61
+ "run": "onType",
62
+ "fixKind": "safe_fix"
63
+ }
64
+ },
65
+ "oxfmt": {
66
+ "initialization_options": {
67
+ "fmt.experimental": true,
68
+ "settings": {
69
+ "run": "onSave",
70
+ "fmt.experimental": true
71
+ }
72
+ }
73
+ }
74
+ },
75
+ "file_scan_exclusions": ["**/.git", "**/.DS_Store", "**/Thumbs.db"]
76
+ }
package/README.md ADDED
@@ -0,0 +1,15 @@
1
+ # opencode-teammate
2
+
3
+ To install dependencies:
4
+
5
+ ```bash
6
+ bun install
7
+ ```
8
+
9
+ To run:
10
+
11
+ ```bash
12
+ bun run index.ts
13
+ ```
14
+
15
+ This project was created using `bun init` in bun v1.3.3. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
@@ -0,0 +1,11 @@
1
+ import { defineConfig } from "@bunli/core";
2
+
3
+ import { version } from "./package.json";
4
+
5
+ export default defineConfig({
6
+ name: "mate",
7
+ version,
8
+ commands: {
9
+ directory: "./src/cli/commands",
10
+ },
11
+ });
@@ -0,0 +1,31 @@
1
+ import { defineConfig } from "bunup";
2
+
3
+ export default defineConfig([
4
+ {
5
+ name: "plugin",
6
+ entry: "src/plugin.ts",
7
+ format: "esm",
8
+ target: "bun",
9
+ outDir: "dist",
10
+ noExternal: ["radashi"],
11
+ drop: ["console"],
12
+ dts: {
13
+ tsgo: true,
14
+ },
15
+ },
16
+ {
17
+ name: "cli",
18
+ entry: "src/cli/index.ts",
19
+ format: "esm",
20
+ target: "bun",
21
+ noExternal: ["radashi"],
22
+ outDir: "dist/bin",
23
+ minify: true,
24
+ splitting: false,
25
+ clean: false,
26
+ onSuccess: `
27
+ mv dist/bin/index.js dist/bin/mate
28
+ chmod +x dist/bin/mate
29
+ `,
30
+ },
31
+ ]);
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "opencode-teammate",
3
+ "version": "0.1.0-next.1",
4
+ "private": false,
5
+ "bin": {
6
+ "mate": "./src/cli/index.ts"
7
+ },
8
+ "type": "module",
9
+ "module": "src/plugin.ts",
10
+ "imports": {
11
+ "#utils/*": "./src/utils/*"
12
+ },
13
+ "scripts": {
14
+ "generate": "bunli generate",
15
+ "cli:dev": "bunli dev",
16
+ "build": "bunup",
17
+ "lint": "oxlint --type-aware --type-check",
18
+ "fmt": "oxfmt"
19
+ },
20
+ "dependencies": {
21
+ "@bunli/core": "^0.5.4",
22
+ "bunli": "^0.5.3",
23
+ "radashi": "~12.7.1",
24
+ "zod": "^4.3.6"
25
+ },
26
+ "devDependencies": {
27
+ "@opencode-ai/plugin": "^1.1.59",
28
+ "@tsconfig/bun": "^1.0.10",
29
+ "@types/bun": "~1.3.9",
30
+ "@typescript/native-preview": "^7.0.0-dev.20260212.1",
31
+ "bunup": "^0.16.26",
32
+ "oxfmt": "^0.32.0",
33
+ "oxlint": "^1.47.0",
34
+ "oxlint-tsgolint": "^0.12.2"
35
+ }
36
+ }
@@ -0,0 +1 @@
1
+ export * as specifications from "./specifications";
@@ -0,0 +1,70 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { withResolvers } from "radashi";
4
+ import { z } from "zod";
5
+
6
+ export const MARKDOWN = new Bun.Glob("**/*.md");
7
+
8
+ export type Specification = ReturnType<typeof createItem>;
9
+
10
+ export type Frontmatter = z.infer<typeof Frontmatter>;
11
+ export const Frontmatter = z.object({
12
+ issue: z.string().optional(),
13
+ });
14
+
15
+ export function use(options: { target: string; ignore?: Bun.Glob; watch: boolean }) {
16
+ if (options.watch) return watch(options);
17
+ else return scan(options);
18
+ }
19
+
20
+ export async function* watch(options: { target: string; ignore?: Bun.Glob }) {
21
+ const touched = new Map<string, number | bigint>();
22
+ const queue: Array<Specification> = [];
23
+ let resolver: PromiseWithResolvers<string> | undefined;
24
+
25
+ const watcher = fs.watch(options.target, async (event, filename) => {
26
+ if (event === "change" && filename) {
27
+ if (!MARKDOWN.match(filename)) return;
28
+ if (options.ignore?.match(filename)) return;
29
+
30
+ const item = createItem(options.target, filename);
31
+ const hash = Bun.hash(await item.file.text());
32
+ if (touched.get(filename) === hash) return;
33
+
34
+ touched.set(filename, hash);
35
+ queue.push(createItem(options.target, filename));
36
+
37
+ if (resolver) {
38
+ resolver.resolve(filename);
39
+ resolver = undefined;
40
+ }
41
+ }
42
+ });
43
+
44
+ try {
45
+ while (true) {
46
+ const item = queue.shift();
47
+
48
+ if (item) yield item;
49
+ else {
50
+ resolver = withResolvers();
51
+ await resolver.promise;
52
+ }
53
+ }
54
+ } finally {
55
+ watcher.close();
56
+ }
57
+ }
58
+
59
+ export async function* scan(options: { target: string; ignore?: Bun.Glob }) {
60
+ for await (const filename of MARKDOWN.scan({ cwd: options.target })) {
61
+ if (options.ignore?.match(filename)) continue;
62
+
63
+ yield createItem(options.target, filename);
64
+ }
65
+ }
66
+
67
+ function createItem(target: string, filename: string) {
68
+ const filepath = path.join(target, filename);
69
+ return { filename, filepath: path.parse(filepath), file: Bun.file(filepath) };
70
+ }
@@ -0,0 +1,105 @@
1
+ import { isEmpty, listify, sift } from "radashi";
2
+
3
+ // import * as shell from "#utils/shell/index.js";
4
+ import * as shell from "../../utils/shell/index.js";
5
+
6
+ import * as issues from "./issues";
7
+
8
+ export type AgentState = "idle" | "spawning" | "running" | "working" | "stuck" | "done" | "stopped";
9
+
10
+ export interface Agent {
11
+ id: string;
12
+ title: string;
13
+ rig: string | null;
14
+ role_bead: string | null;
15
+ role_type: string | null;
16
+ agent_state: AgentState;
17
+ }
18
+
19
+ export interface UpdatedAgentState {
20
+ agent: string;
21
+ agent_state: AgentState;
22
+ }
23
+
24
+ export interface StuckOptions {
25
+ context?: AgentContext;
26
+ reason: string;
27
+ }
28
+
29
+ export async function stuck(client: shell.client.ClientInput, slug: string, options: StuckOptions) {
30
+ const agent = await state(client, slug, "stuck");
31
+
32
+ await issues.update(
33
+ client,
34
+ { id: agent.agent },
35
+ {
36
+ "add-label": createContextLabels(options.context),
37
+ description: options.reason,
38
+ },
39
+ );
40
+
41
+ return agent;
42
+ }
43
+
44
+ export interface DoneOptions {
45
+ context?: AgentContext;
46
+ }
47
+
48
+ export async function done(
49
+ client: shell.client.ClientInput,
50
+ slug: string,
51
+ options: DoneOptions = {},
52
+ ) {
53
+ const agent = await state(client, slug, "done");
54
+
55
+ await issues.update(
56
+ client,
57
+ { id: agent.agent },
58
+ {
59
+ "add-label": createContextLabels(options.context),
60
+ description: "",
61
+ },
62
+ );
63
+
64
+ return agent;
65
+ }
66
+
67
+ export async function state(client: shell.client.ClientInput, slug: string, state: AgentState) {
68
+ const $ = shell.client.use(client);
69
+
70
+ const agent = await $<UpdatedAgentState>`bd agent state gram-${slug} ${state} --json`.json();
71
+
72
+ return agent;
73
+ }
74
+
75
+ export async function use(
76
+ client: shell.client.ClientInput,
77
+ input: { slug: string } | { id: string },
78
+ ) {
79
+ const $ = shell.client.use(client);
80
+
81
+ const id = "slug" in input ? `gram-${input.slug}` : input.id;
82
+
83
+ const agent = await $<Agent>`bd agent show ${id} --json`.json();
84
+
85
+ return agent;
86
+ }
87
+
88
+ interface AgentContext {
89
+ agent?: string;
90
+ model?: string;
91
+ provider?: string;
92
+ }
93
+
94
+ function createContextLabels(context?: AgentContext) {
95
+ if (!context) return undefined;
96
+
97
+ const labels = sift(
98
+ listify(
99
+ context as Record<string, string | undefined>,
100
+ (key, value) => value && `${key}:${value}`,
101
+ ),
102
+ );
103
+
104
+ return isEmpty(labels) ? undefined : labels;
105
+ }
@@ -0,0 +1,17 @@
1
+ // import * as shell from "#utils/shell/index.js";
2
+ import * as shell from "../../utils/shell/index.js";
3
+
4
+ export interface Config {
5
+ key: string;
6
+ value: string;
7
+ }
8
+
9
+ export async function issuePrefix(client: shell.client.ClientInput) {
10
+ return get(client, { key: "issue_prefix" });
11
+ }
12
+
13
+ export async function get(client: shell.client.ClientInput, input: { key: string }) {
14
+ const $ = shell.client.use(client);
15
+
16
+ return await $<Config>`bd config get ${input.key} --json`.json();
17
+ }
@@ -0,0 +1,4 @@
1
+ export * as agents from "./agents";
2
+ export * as specifications from "./specifications";
3
+ export * as issues from "./issues";
4
+ export * as config from "./config";