@doguyilmaz/konvoy 0.1.2 → 0.2.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/README.md +5 -0
- package/package.json +1 -1
- package/src/cli.ts +9 -0
- package/src/commands/new.ts +9 -6
- package/src/commands/rename.ts +49 -0
- package/src/commands/table.ts +1 -0
- package/src/core/prelude.ts +1 -1
- package/src/paths.ts +2 -0
- package/src/store/queries.ts +4 -0
package/README.md
CHANGED
|
@@ -41,6 +41,7 @@ to download; the npm package is a few kilobytes of source and runs on the Bun yo
|
|
|
41
41
|
|
|
42
42
|
```bash
|
|
43
43
|
konvoy new "refactor the auth layer"
|
|
44
|
+
konvoy new # no goal: named after the directory, like sinkaf-8f3a
|
|
44
45
|
konvoy send codex "start with the token refresh path"
|
|
45
46
|
konvoy ls
|
|
46
47
|
konvoy resume # make a session current again and show its roster
|
|
@@ -52,6 +53,7 @@ konvoy attach kiro --id cli_8a1… # adopt a session you started in kiro's own
|
|
|
52
53
|
konvoy doctor
|
|
53
54
|
konvoy update --all # every agent CLI; konvoy itself follows its install channel (see Install)
|
|
54
55
|
konvoy rm stale-slug --yes
|
|
56
|
+
konvoy rename stale-slug token-refresh # the session's .konvoy folder follows
|
|
55
57
|
konvoy version
|
|
56
58
|
konvoy dashboard --port 4000 # local page with the same numbers as `usage --chart`
|
|
57
59
|
```
|
|
@@ -164,6 +166,9 @@ Global `~/.config/konvoy/config.jsonc`, per project `.konvoy/config.jsonc`. The
|
|
|
164
166
|
file wins. `konvoy config get` shows each agent's resolved settings and whether a value came
|
|
165
167
|
from that agent, from `defaults`, or from konvoy's own built-in.
|
|
166
168
|
|
|
169
|
+
`konvoy new` also writes `.konvoy/.gitignore` (`*`, then `!config.jsonc`), so a session's `CONTEXT.md`
|
|
170
|
+
and `LEDGER.md` never reach git while the project config can be committed.
|
|
171
|
+
|
|
167
172
|
```jsonc
|
|
168
173
|
{
|
|
169
174
|
"defaults": { "effort": "high", "permission": "edit" },
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -18,6 +18,7 @@ import { cmdUpdate } from './commands/update'
|
|
|
18
18
|
import { cmdConfig } from './commands/config'
|
|
19
19
|
import { cmdResume } from './commands/resume'
|
|
20
20
|
import { cmdRm } from './commands/rm'
|
|
21
|
+
import { cmdRename } from './commands/rename'
|
|
21
22
|
import { cmdUsage } from './commands/usage'
|
|
22
23
|
import { cmdDashboard } from './commands/dashboard'
|
|
23
24
|
import { formatCommandList, resolveCommandName, type CommandName } from './commands/table'
|
|
@@ -78,6 +79,14 @@ const handlers: Record<CommandName, Handler> = {
|
|
|
78
79
|
const [action, key, value] = rest
|
|
79
80
|
return cmdConfig(ctx.cfg, ctx.cwd, action ?? 'get', key, value, { global: ctx.args.flags.global === true })
|
|
80
81
|
},
|
|
82
|
+
rename: (ctx, rest) => {
|
|
83
|
+
const [from, to] = rest
|
|
84
|
+
if (!from || !to) {
|
|
85
|
+
console.error('usage: konvoy rename <session> <new-name>')
|
|
86
|
+
return 2
|
|
87
|
+
}
|
|
88
|
+
return cmdRename(ctx.db, from, to)
|
|
89
|
+
},
|
|
81
90
|
rm: (ctx, rest) => {
|
|
82
91
|
const [target] = rest
|
|
83
92
|
if (!target) {
|
package/src/commands/new.ts
CHANGED
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
import type { Database } from 'bun:sqlite'
|
|
2
2
|
import type { Config } from '../config/schema'
|
|
3
|
-
import { newSession } from '../core/session'
|
|
4
|
-
import { sessionDir } from '../paths'
|
|
3
|
+
import { newSession, slugify } from '../core/session'
|
|
4
|
+
import { basename, join, sessionDir } from '../paths'
|
|
5
5
|
|
|
6
6
|
export async function cmdNew(db: Database, cfg: Config, cwd: string, goal: string): Promise<number> {
|
|
7
7
|
const lead = cfg.roles.lead ?? 'claude'
|
|
8
|
-
const
|
|
8
|
+
const suffix = Bun.randomUUIDv7().slice(-4)
|
|
9
|
+
const slug = goal ? undefined : `${slugify(basename(cwd)).slice(0, 30)}-${suffix}`
|
|
10
|
+
const session = newSession(db, { cwd, goal, lead, slug })
|
|
9
11
|
const dir = sessionDir(cwd, session.slug)
|
|
12
|
+
const ignore = Bun.file(join(cwd, '.konvoy', '.gitignore'))
|
|
13
|
+
if (!(await ignore.exists())) await Bun.write(ignore, '*\n!config.jsonc\n')
|
|
10
14
|
// `konvoy rm` frees the slug but leaves these files - they are the user's. A new session under
|
|
11
15
|
// an old slug adds its goal to the context and appends to the ledger; it truncates neither.
|
|
12
16
|
const context = Bun.file(`${dir}/CONTEXT.md`)
|
|
17
|
+
const goalSection = session.goal ? `\n## Goal\n\n${session.goal}\n` : ''
|
|
13
18
|
await Bun.write(
|
|
14
19
|
context,
|
|
15
|
-
(await context.exists())
|
|
16
|
-
? `${await context.text()}\n## Goal\n\n${session.goal}\n`
|
|
17
|
-
: `# ${session.slug}\n\n## Goal\n\n${session.goal}\n`,
|
|
20
|
+
(await context.exists()) ? `${await context.text()}${goalSection}` : `# ${session.slug}\n${goalSection}`,
|
|
18
21
|
)
|
|
19
22
|
const ledger = Bun.file(`${dir}/LEDGER.md`)
|
|
20
23
|
if (!(await ledger.exists())) await Bun.write(ledger, `# Ledger - ${session.slug}\n`)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Database } from 'bun:sqlite'
|
|
2
|
+
import { slugify } from '../core/session'
|
|
3
|
+
import { sessionDir } from '../paths'
|
|
4
|
+
import { getSessionBySlug, lockOwner, renameSession } from '../store/queries'
|
|
5
|
+
|
|
6
|
+
const isDir = async (path: string): Promise<boolean> => (await Bun.$`test -d ${path}`.quiet().nothrow()).exitCode === 0
|
|
7
|
+
|
|
8
|
+
async function retitle(path: string, from: string, to: string): Promise<void> {
|
|
9
|
+
const file = Bun.file(path)
|
|
10
|
+
if (!(await file.exists())) return
|
|
11
|
+
const text = await file.text()
|
|
12
|
+
if (text.startsWith(from)) await Bun.write(file, to + text.slice(from.length))
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function cmdRename(db: Database, from: string, to: string): Promise<number> {
|
|
16
|
+
const session = getSessionBySlug(db, from)
|
|
17
|
+
if (!session) {
|
|
18
|
+
console.error(`no konvoy session named "${from}"`)
|
|
19
|
+
return 2
|
|
20
|
+
}
|
|
21
|
+
const busy = lockOwner(db, session.id)
|
|
22
|
+
if (busy) {
|
|
23
|
+
console.error(`"${from}" has a turn running (${busy}) - wait for it to finish, then retry`)
|
|
24
|
+
return 2
|
|
25
|
+
}
|
|
26
|
+
const slug = slugify(to)
|
|
27
|
+
if (getSessionBySlug(db, slug)) {
|
|
28
|
+
console.error(`a session named "${slug}" already exists`)
|
|
29
|
+
return 2
|
|
30
|
+
}
|
|
31
|
+
const oldDir = sessionDir(session.cwd, from)
|
|
32
|
+
const newDir = sessionDir(session.cwd, slug)
|
|
33
|
+
if (await isDir(oldDir)) {
|
|
34
|
+
if (await isDir(newDir)) {
|
|
35
|
+
console.error(`${newDir} already exists - move it away first`)
|
|
36
|
+
return 2
|
|
37
|
+
}
|
|
38
|
+
const moved = await Bun.$`mv ${oldDir} ${newDir}`.quiet().nothrow()
|
|
39
|
+
if (moved.exitCode !== 0) {
|
|
40
|
+
console.error(`could not move ${oldDir} to ${newDir}: ${moved.stderr.toString().trim()}`)
|
|
41
|
+
return 1
|
|
42
|
+
}
|
|
43
|
+
await retitle(`${newDir}/CONTEXT.md`, `# ${from}\n`, `# ${slug}\n`)
|
|
44
|
+
await retitle(`${newDir}/LEDGER.md`, `# Ledger - ${from}\n`, `# Ledger - ${slug}\n`)
|
|
45
|
+
}
|
|
46
|
+
renameSession(db, session.id, slug)
|
|
47
|
+
console.log(`renamed ${from} -> ${slug}`)
|
|
48
|
+
return 0
|
|
49
|
+
}
|
package/src/commands/table.ts
CHANGED
|
@@ -21,6 +21,7 @@ export const commandTable = [
|
|
|
21
21
|
usage: 'rm <session> --yes',
|
|
22
22
|
summary: 'delete a konvoy session (foreign sessions survive)',
|
|
23
23
|
},
|
|
24
|
+
{ name: 'rename', aliases: [], usage: 'rename <session> <new-name>', summary: 'rename a session; its .konvoy folder follows' },
|
|
24
25
|
{ name: 'roster', aliases: [], usage: 'roster', summary: 'who is in the convoy' },
|
|
25
26
|
{ name: 'usage', aliases: [], usage: 'usage [--all] [--chart]', summary: 'what this session spent, per agent' },
|
|
26
27
|
{ name: 'status', aliases: [], usage: 'status', summary: 'versions, auth and roster' },
|
package/src/core/prelude.ts
CHANGED
|
@@ -117,5 +117,5 @@ export function buildPrelude(db: Database, session: Session, facts: string, opts
|
|
|
117
117
|
|
|
118
118
|
if (dropped > 0) turnBlocks.push(`(${dropped} earlier turn${dropped === 1 ? '' : 's'} not shown)`)
|
|
119
119
|
|
|
120
|
-
return [`goal: ${session.goal}
|
|
120
|
+
return [session.goal ? `goal: ${session.goal}` : '', facts, turnBlocks.join('\n\n')].filter(Boolean).join('\n\n')
|
|
121
121
|
}
|
package/src/paths.ts
CHANGED
|
@@ -14,6 +14,8 @@ export function join(...parts: string[]): string {
|
|
|
14
14
|
return (absolute ? '/' : '') + segments.join('/')
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
export const basename = (p: string): string => p.split('/').filter(Boolean).at(-1) ?? ''
|
|
18
|
+
|
|
17
19
|
export function dirname(p: string): string {
|
|
18
20
|
const cut = p.lastIndexOf('/')
|
|
19
21
|
if (cut < 0) return '.'
|
package/src/store/queries.ts
CHANGED
|
@@ -76,6 +76,10 @@ export function touchSession(db: Database, id: string): void {
|
|
|
76
76
|
).run({ id, now: now() })
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
export function renameSession(db: Database, id: string, slug: string): void {
|
|
80
|
+
db.query('UPDATE session SET slug = $slug WHERE id = $id').run({ slug, id })
|
|
81
|
+
}
|
|
82
|
+
|
|
79
83
|
export function deleteSession(db: Database, id: string): void {
|
|
80
84
|
db.transaction(() => {
|
|
81
85
|
db.query('DELETE FROM event WHERE turn_id IN (SELECT id FROM turn WHERE session_id = $id)').run({ id })
|