dreamteamer 0.30.0 → 0.32.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 +47 -153
- package/collections/collections.collection.yaml +26 -9
- package/package.json +14 -2
- package/skills/using-dreamteamer/SKILL.md +16 -10
- package/skills/using-dreamteamer/references/before-you-build.md +4 -3
- package/skills/using-dreamteamer/references/collections.md +71 -1
- package/skills/using-dreamteamer/references/commands.md +3 -3
- package/skills/using-dreamteamer/references/data-modeling.md +9 -1
- package/skills/using-dreamteamer/references/extensions.md +60 -0
- package/skills/using-dreamteamer/references/records.md +17 -0
- package/skills/using-dreamteamer/references/sessions.md +4 -5
- package/skills/using-dreamteamer/references/skills.md +3 -5
- package/src/api.d.ts +109 -0
- package/src/api.js +70 -0
- package/src/check.js +57 -2
- package/src/checkout.js +105 -330
- package/src/cli.js +90 -274
- package/src/collections-cli.js +16 -165
- package/src/commit.js +7 -0
- package/src/compile.js +211 -155
- package/src/events.js +7 -2
- package/src/extensions.js +135 -0
- package/src/filter.js +1 -1
- package/src/harnesses.js +82 -135
- package/src/init.js +9 -3
- package/src/placement.js +209 -0
- package/src/records-api.d.ts +103 -0
- package/src/records-api.js +40 -0
- package/src/runtime.js +2 -2
- package/src/schema-ops.js +26 -9
- package/src/store.js +396 -35
- package/collections/containers.collection.yaml +0 -81
- package/collections/images.collection.yaml +0 -46
- package/collections/proofs.collection.yaml +0 -96
- package/skills/using-dreamteamer/references/exporting.md +0 -53
- package/skills/using-dreamteamer/references/proofs.md +0 -435
- package/skills/using-dreamteamer/references/worktrees.md +0 -235
- package/src/container-archive.js +0 -356
- package/src/containers.js +0 -635
- package/src/export-notebooklm.js +0 -502
- package/src/land.js +0 -743
- package/src/prove.js +0 -1922
- package/src/server.js +0 -481
package/src/cli.js
CHANGED
|
@@ -12,22 +12,17 @@
|
|
|
12
12
|
import fs from 'node:fs';
|
|
13
13
|
import path from 'node:path';
|
|
14
14
|
import { execFileSync } from 'node:child_process';
|
|
15
|
-
import {
|
|
16
|
-
import { compile, staleness, warnIfStale, discoverModules, CHANNEL_LABEL, locationOf,
|
|
15
|
+
import { openWorkspace } from './api.js';
|
|
16
|
+
import { compile, staleness, warnIfStale, discoverModules, CHANNEL_LABEL, locationOf, kindsOf } from './compile.js';
|
|
17
17
|
import { check } from './check.js';
|
|
18
|
-
import { collectionCommand, emit, relationsCommand
|
|
19
|
-
import { driverTarget, driverCommand, setup as hostSetup, parseFlags as hostFlags, DRIVER_VERBS, LIFECYCLE_VERBS, CONTAINER_FLAGS } from './containers.js';
|
|
20
|
-
import { archiveCommand } from './container-archive.js';
|
|
18
|
+
import { collectionCommand, emit, relationsCommand } from './collections-cli.js';
|
|
21
19
|
import { init, installClone, update, listRepos } from './init.js';
|
|
22
|
-
import { installCommand, describeCheckout
|
|
23
|
-
import { proveCommand, readLedger, flagEnabled } from './prove.js';
|
|
24
|
-
import { landCommand } from './land.js';
|
|
20
|
+
import { installCommand, describeCheckout } from './checkout.js';
|
|
25
21
|
import { deriveEvents } from './events.js';
|
|
26
22
|
import { commitPending } from './commit.js';
|
|
27
23
|
import { Store } from './store.js';
|
|
28
24
|
import { splitRef, canonicalCollection } from './ref.js';
|
|
29
25
|
import { envContext, renderTemplate } from './env-vars.js';
|
|
30
|
-
import { exportCommand, EXPORT_FLAGS } from './export-notebooklm.js';
|
|
31
26
|
|
|
32
27
|
// git calls whose failure we CATCH must not print git's own error: execFileSync forwards the
|
|
33
28
|
// child's stderr to ours unless told otherwise, so a handled "not a git repository" still
|
|
@@ -45,10 +40,6 @@ record verbs (hard validation — invalid writes are rejected before disk).
|
|
|
45
40
|
A <target> is either a collection name or a <collection>/<id> reference; the reference splits at
|
|
46
41
|
the longest DECLARED collection prefix, so finance/transactions/2026/03/coffee is ONE argument:
|
|
47
42
|
list <collection> [--filter k=v] [--where <json>] [--sort [-]<field>] [--json]
|
|
48
|
-
list proofs [--missing] (a proof listing appends two COMPUTED columns:
|
|
49
|
-
availability on THIS machine, and the last verdict
|
|
50
|
-
from its ledger. --missing inverts it — one line per
|
|
51
|
-
artifact no proof is about, so it takes no filter)
|
|
52
43
|
(--filter is ONE condition — repeat it to AND more
|
|
53
44
|
(--filter a=1 --filter b=2 wants both);
|
|
54
45
|
anything compound goes in one --where, operator
|
|
@@ -88,6 +79,17 @@ the longest DECLARED collection prefix, so finance/transactions/2026/03/coffee i
|
|
|
88
79
|
relations [<collection>] (every two-way pair: owner.field → target.mirror)
|
|
89
80
|
relations rebuild <collection> [--drop <f>] (regenerate mirror VALUES from the owning side;
|
|
90
81
|
--drop removes a stale ex-mirror key from records)
|
|
82
|
+
relocate <target> [--dry-run] [--json] (move record FILES to where the compiled descriptor
|
|
83
|
+
puts them — a record stored under another
|
|
84
|
+
collection (storage.under) whose folder disagrees
|
|
85
|
+
with its owner field, or a file record in a
|
|
86
|
+
collection that became shape: folder. Ids and
|
|
87
|
+
references never change; a pending edit on a
|
|
88
|
+
moved file, or a dangling owner, refuses the whole
|
|
89
|
+
plan — commit, or fix the field, first)
|
|
90
|
+
relocate <collection> --to-root [--dry-run] (the REVERSE: every placed record back into the
|
|
91
|
+
collection's own folder, ids unchanged — the step
|
|
92
|
+
before removing or changing storage.under)
|
|
91
93
|
resolve '<string>' | <collection>/<id> <field>
|
|
92
94
|
(render \${env:NAME} · \${workspaceFolder} ·
|
|
93
95
|
\${userHome} — the ONLY substitution point; a
|
|
@@ -95,7 +97,8 @@ the longest DECLARED collection prefix, so finance/transactions/2026/03/coffee i
|
|
|
95
97
|
field prints one item per line)
|
|
96
98
|
|
|
97
99
|
system verbs — the SAME verbs, on the entities the compiler materializes (modules, collections,
|
|
98
|
-
skills, agents, commands, command-bindings, ui-views, collection-templates,
|
|
100
|
+
skills, agents, commands, command-bindings, ui-views, collection-templates, and any kind an
|
|
101
|
+
installed extension adds). ⚠ ONE
|
|
99
102
|
difference in POLICY, not in spelling: a SYSTEM write commits itself, because an uncompilable or
|
|
100
103
|
unpublished schema is not a state a workspace should sit in; a RECORD write does not — \`commit\`
|
|
101
104
|
publishes it. The commit lands in the repo that holds the source, so a write into a git module
|
|
@@ -181,7 +184,7 @@ Every verb that MOVES records or CLEARS values takes --dry-run and prints its pl
|
|
|
181
184
|
|
|
182
185
|
workspace verbs:
|
|
183
186
|
init write the workspace skeleton into the current directory (never compiles)
|
|
184
|
-
[--harnesses claude-code,codex,pi,gemini-cli,cursor
|
|
187
|
+
[--harnesses claude-code,codex,pi,gemini-cli,cursor]
|
|
185
188
|
--version print the engine version (works anywhere)
|
|
186
189
|
install make THIS checkout ready — the engine, .env (linked from the primary when this is a
|
|
187
190
|
worktree), declared local assets, git modules, compile, and a declared postinstall.
|
|
@@ -190,105 +193,20 @@ workspace verbs:
|
|
|
190
193
|
[--link-env] link .env even into a worktree OUTSIDE the primary root
|
|
191
194
|
install --hook | --print-adapters
|
|
192
195
|
--hook: read a harness hook's JSON payload from stdin and install the checkout its
|
|
193
|
-
\`cwd\` names — the process cwd is the harness's, never the worktree's
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
196
|
+
\`cwd\` names — the process cwd is the harness's, never the worktree's
|
|
197
|
+
--print-adapters: print the hook snippet for each declared harness, with every hook an
|
|
198
|
+
installed extension adds. Merging it into the harness's own settings file is the
|
|
199
|
+
operator's act — the engine never writes one
|
|
197
200
|
install repos/<id> | repos --all [--json]
|
|
198
201
|
materialize an attached repo's working tree ON DEMAND — never as part of making a
|
|
199
202
|
checkout ready; --all is the explicit opt-in, e.g. before going offline
|
|
200
203
|
install --clone <url> [name] attach a git module to this workspace
|
|
201
|
-
add worktrees --name <n> [--path <dir>] [--base <ref>] [--temp] | --hook
|
|
202
|
-
cut a linked git worktree on branch worktree-<n> and \`install\` it, so it is ready
|
|
203
|
-
to work in; it prints its absolute path LAST, which is what a creation hook echoes.
|
|
204
|
-
--temp: detached, no branch, under .worktrees/.tmp-* inside this root — a sandbox
|
|
205
|
-
--hook: take the name from a WorktreeCreate payload on stdin; implies .worktrees/<n>
|
|
206
|
-
list worktrees | get worktrees/<n> | rm worktrees/<n> [--force]
|
|
207
|
-
observed from \`git worktree list\`, never stored. rm refuses a worktree holding
|
|
208
|
-
dirty records or commits not on the primary branch — neither is visible from here
|
|
209
|
-
land worktrees/<name|path> [--keep] [--dry-run] [--branch <n>] [--json]
|
|
210
|
-
land a worktree's commits onto the primary branch — the one MOVEMENT verb. It refuses
|
|
211
|
-
while the primary holds pending record writes, rebases a COPY of the branch under one
|
|
212
|
-
engine-owned lock, resolves ONLY the generated harness block, fast-forwards, recompiles
|
|
213
|
-
the primary, then removes the worktree and its branch. A conflict in a record aborts
|
|
214
|
-
and leaves every tree exactly as it was
|
|
215
|
-
[--keep] keep the worktree, reset onto what landed
|
|
216
|
-
[--branch <n>] give a DETACHED worktree the branch worktree-<n> first
|
|
217
|
-
[--dry-run] print the plan and change nothing
|
|
218
|
-
land --hook [--dry-run] [--json]
|
|
219
|
-
read a WorktreeRemove payload on stdin and report what that worktree still holds.
|
|
220
|
-
ALWAYS a dry run — the engine never lands while a harness is deleting the tree
|
|
221
204
|
update pull git_modules clones forward (ff-only on the lockfile ref), rebuild,
|
|
222
205
|
then compile; [<name>] updates just one. dirty clones are skipped
|
|
223
206
|
compile materialize modules + workspace sources into .dreamteamer (+ harness adapters)
|
|
224
207
|
[--watch] recompile on source changes
|
|
225
208
|
check validate every record against the compiled descriptors (report-only)
|
|
226
|
-
|
|
227
|
-
run a proof and answer with an EXIT CODE: 0 pass · 1 fail · 3 unavailable (this
|
|
228
|
-
machine lacks a required var or binary) · 4 no-fixture (the given matched no record)
|
|
229
|
-
· 5 a \`perform\` step is owed a human/agent · 6 vacuous (every expectation already
|
|
230
|
-
held, so the proof cannot fail). Evidence lands in .dreamteamer/.proofs/<id>.jsonl
|
|
231
|
-
<proof> [--record <c>/<id>] finish the pending run for that record
|
|
232
|
-
[--restart] discard a pending run and start over
|
|
233
|
-
[--here] run a \`writes\` proof in this workspace, not a sandbox
|
|
234
|
-
[--keep] keep the sandbox afterwards — under --json that is
|
|
235
|
-
\`kept: true\` beside the \`sandbox\` path [--json] one object on stdout
|
|
236
|
-
<artifact-ref> every proof whose \`about\` names skills/<id>, commands/<id>,
|
|
237
|
-
command-bindings/<id> or <module>/bin/<file> — board semantics, so
|
|
238
|
-
it takes the same [--kind gate|live] [--external] [--strict] [--json]
|
|
239
|
-
--all every proof; a \`perform\` one is LISTED, never started, so this
|
|
240
|
-
never exits 5. ONE line per proof — a step transcript is what a
|
|
241
|
-
single-proof run is for [--kind gate|live] [--external] include
|
|
242
|
-
external proofs [--strict] make an unavailable fatal [--json]
|
|
243
|
-
status workspace status: compiled runtime freshness, per-module channel/ref, staleness,
|
|
244
|
-
and one \`proofs:\` line counting each proof's LAST verdict on this machine
|
|
245
|
-
[--strict] exit 1 when any proof's ledger tail is a FAIL
|
|
246
|
-
start serve the clean REST api at /api [--port <n>]
|
|
247
|
-
|
|
248
|
-
containers — a workspace as a running container (Docker Engine API over its socket, no dependency;
|
|
249
|
-
these verbs work with NO workspace, so npm i -g dreamteamer and Docker Desktop are enough):
|
|
250
|
-
setup make THIS MACHINE ready: checks Docker, writes ~/.dreamteamer/.env with its defaults
|
|
251
|
-
(DT_PORT_BASE 8100 · DT_BIND 127.0.0.1 · DT_REGISTRY · DT_TEMPLATE_TAG ·
|
|
252
|
-
DT_DOCKER_TIMEOUT 30 — seconds a request to Docker may sit idle before the verb
|
|
253
|
-
fails), lists the templates present, pulls one on request [--template <t>] [--json]
|
|
254
|
-
start container <name> --template <t> create-if-absent and start: a code-server editor at
|
|
255
|
-
http://localhost:<port>/ — the machine home — over a compiled workspace, three named
|
|
256
|
-
volumes (workspace · home · files), its own bridge network dreamteamer-<name>, image
|
|
257
|
-
<DT_REGISTRY>/<template>:<tag> or DT_IMAGE_<template>. The workspace is mounted at
|
|
258
|
-
/workspaces/<name>. Idempotent. NO credential is ever injected — log in INSIDE, once;
|
|
259
|
-
the home volume keeps it. An image with a URL token (hq 0.6+) prints the URL as
|
|
260
|
-
?tkn=<token>, read from the container; the token is never written on the host.
|
|
261
|
-
[--rotate-token] replace the image's URL token and print the new URL
|
|
262
|
-
[--workspace [<w>]] open /workspaces/<w> (default: its own) instead of the home
|
|
263
|
-
[--repo <git url>] clone an EXISTING workspace into the volume on first start, instead
|
|
264
|
-
of laying the template down — how a person joins one on GitHub
|
|
265
|
-
[--mount <host-path|volume>:<container-path>[:ro]] extra mounts, repeatable; targets
|
|
266
|
-
under /workspaces · /home/node · /files · /mnt, none at or under
|
|
267
|
-
another mount's target, no bind source inside another
|
|
268
|
-
[--name <git name>] [--email <git email>] [--no-open] [--json]
|
|
269
|
-
stop container <name> stop it; every volume kept [--json]
|
|
270
|
-
open container <name> print (and open) its URL, token included [--no-open] [--json]
|
|
271
|
-
[--workspace [<w>]]
|
|
272
|
-
[--vscode] print (and open) the Dev Containers attach URI instead — the host's own
|
|
273
|
-
VS Code inside the container, extensions from the image's metadata label
|
|
274
|
-
list containers | images the record verbs, answered over Docker instead of a
|
|
275
|
-
get container <name> | image <ref> folder — singular or plural, either spelling.
|
|
276
|
-
rm container <name> [--force] removes it and its network; keeps the volumes unless --force
|
|
277
|
-
add image --template <t> pull a template's image; rm image <ref> removes one
|
|
278
|
-
export container <name> --out <file> its WORKSPACES as one file — every folder under
|
|
279
|
-
/workspaces, never the home or a login; node_modules and .files stay behind. Works on
|
|
280
|
-
a stopped container. Encrypted with the owner passphrase (DT_EXPORT_PASSPHRASE, else a
|
|
281
|
-
prompt — never a flag). [--workspace <w>]... only these [--no-encrypt] a plain .tar.gz
|
|
282
|
-
[--json] (both verbs) the summary as JSON on stdout, every other line on stderr
|
|
283
|
-
Secrets stay behind: .env and .env.* (not .example/.sample/.template), .envrc,
|
|
284
|
-
.npmrc, .netrc, .git-credentials, .pypirc, .docker/config.json, and the credentials in
|
|
285
|
-
each .git/config. [--with-secrets] carries them unchanged
|
|
286
|
-
import container <name> <file> unpack an export into a RUNNING container, owned by
|
|
287
|
-
node. Refuses a wrong passphrase, a damaged file, an entry leaving its workspace and a
|
|
288
|
-
workspace already holding files — each before anything is written.
|
|
289
|
-
[--workspace <w>]... only these [--replace] empty a workspace that holds files first
|
|
290
|
-
[--as <name>] land the ONE workspace in /workspaces/<name> — e.g. another container's
|
|
291
|
-
own volume folder (dt-new's name rule; never the container's own layer)
|
|
209
|
+
status workspace status: compiled runtime freshness, per-module channel/ref, staleness
|
|
292
210
|
|
|
293
211
|
changes what changed in every repo that holds records, as record events
|
|
294
212
|
[--since <sha|YYYY-MM-DD>] (default: HEAD~1 — the last commit's own changes) [--json]
|
|
@@ -298,17 +216,10 @@ these verbs work with NO workspace, so npm i -g dreamteamer and Docker Desktop a
|
|
|
298
216
|
<collection> or one <collection>/<id> — the record form is what keeps a
|
|
299
217
|
concurrent session's pending records out of your commit.
|
|
300
218
|
[<collection>|<collection>/<id> …] [-m <subject>] [--dry-run] [--json]
|
|
301
|
-
export render the workspace for a consumer that is not a coding agent, and optionally sync it
|
|
302
|
-
export notebooklm [--out <dir>] [--plan standard|plus|pro|ultra|<n>] [--max-words <n>]
|
|
303
|
-
[--collections a,b] [--instructions <template.md>]
|
|
304
|
-
[--notebook <id> | --create "<title>"] [--response-length default|longer|shorter]
|
|
305
|
-
[--mode default|learning-guide|concise|detailed] [--wait] [--json]
|
|
306
|
-
writes one schema source (workspace → module → collection → field), one source per
|
|
307
|
-
collection (sharded by --max-words, titled \`dt · <c> [n/m]\`), and the persona from a
|
|
308
|
-
template; a collection marked \`sensitive: true\` and a field marked \`x-sensitive: true\`
|
|
309
|
-
never travel. Refuses when the sources exceed the plan. With --notebook/--create it
|
|
310
|
-
adds, replaces and removes its own sources to match and applies the persona.
|
|
311
219
|
help this text
|
|
220
|
+
|
|
221
|
+
extension verbs — an extension (a workspace module, or a dependency, whose package.json declares
|
|
222
|
+
\`dreamteamer.extension\`) adds its own verbs, listed below when this workspace has any.
|
|
312
223
|
`;
|
|
313
224
|
|
|
314
225
|
// Record verbs, split by what their <target> means. `move` and `next` are in NEITHER set: both
|
|
@@ -344,31 +255,28 @@ export const GLOBAL_FLAGS = ['vault'];
|
|
|
344
255
|
export const WORKSPACE_FLAGS = {
|
|
345
256
|
init: ['name', 'data-path', 'harnesses', 'workspace-module'], update: [],
|
|
346
257
|
install: ['clone', 'dry-run', 'json', 'link-env', 'all', 'hook', 'print-adapters'],
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
start: ['port', ...CONTAINER_FLAGS], compile: ['watch'], check: [], status: ['strict'],
|
|
350
|
-
setup: ['template', 'json'], stop: ['json'], open: ['json', 'no-open', 'vscode', 'workspace'],
|
|
351
|
-
changes: ['since', 'json'], commit: ['dry-run', 'json'],
|
|
352
|
-
export: EXPORT_FLAGS,
|
|
353
|
-
// the UNION of every form's flags — the outer typo gate. Which flags each FORM takes is refused
|
|
354
|
-
// inside `proveCommand`, where the target has been resolved against the compiled proofs.
|
|
355
|
-
prove: ['all', 'kind', 'record', 'restart', 'json', 'keep', 'here', 'external', 'strict'],
|
|
356
|
-
// same shape: the union of both forms, with `--hook`'s vocabulary refused in the arm below
|
|
357
|
-
land: ['keep', 'dry-run', 'branch', 'hook', 'json'],
|
|
258
|
+
compile: ['watch'], check: [], status: [],
|
|
259
|
+
changes: ['since', 'json'], commit: ['dry-run', 'json'], relocate: ['dry-run', 'json', 'to-root'],
|
|
358
260
|
};
|
|
359
261
|
|
|
360
|
-
|
|
262
|
+
/** Every verb this CLI answers itself — the set an extension's `commands` may not claim. The retired
|
|
263
|
+
* spellings are in it too: they answer with their replacement, and an extension taking one over
|
|
264
|
+
* would turn a loud translation into a different command. */
|
|
265
|
+
export const CORE_VERBS = [
|
|
266
|
+
'init', 'install', 'update', 'compile', 'check', 'status', 'changes', 'commit', 'help', 'version', '--version', '-v',
|
|
267
|
+
'list', 'add', 'values', 'get', 'set', 'rm', 'rename', 'history', 'diff', 'revert', 'move', 'next',
|
|
268
|
+
'add-field', 'set-field', 'rm-field', 'rename-field', 'relations', 'resolve', 'relocate',
|
|
269
|
+
'schema', 'ensure', 'update-field', 'remove-field', 'commands',
|
|
270
|
+
];
|
|
271
|
+
|
|
272
|
+
/** Verbs that left core in 0.31.0. The extensions that will answer them are not published, so the old
|
|
273
|
+
* spelling in a script or a skill fails saying what happened — never "unknown verb", and never an
|
|
274
|
+
* install line for a package a stranger cannot install. */
|
|
275
|
+
const MOVED_VERBS = new Set(['prove', 'land', 'worktree', 'serve', 'notebooklm', 'start', 'export', 'setup', 'stop', 'open', 'import']);
|
|
276
|
+
|
|
277
|
+
export async function run(argv) {
|
|
361
278
|
const [cmd, ...rest] = argv;
|
|
362
279
|
try {
|
|
363
|
-
// HOST VERBS resolve BEFORE workspace discovery: `setup`, and any verb whose target is a
|
|
364
|
-
// driver collection (`containers`, `images`, singular or plural). They answer identically on a
|
|
365
|
-
// bare machine — `npm i -g dreamteamer` and Docker Desktop, nothing else — and inside a
|
|
366
|
-
// workspace, because the thing they make IS the workspace (src/containers.js).
|
|
367
|
-
const host = hostDispatch(cmd, rest);
|
|
368
|
-
if (host) {
|
|
369
|
-
host.then((code) => process.exit(code)).catch((e) => { console.error(`✖ ${e.message}`); process.exit(1); });
|
|
370
|
-
return;
|
|
371
|
-
}
|
|
372
280
|
if (cmd in WORKSPACE_FLAGS) {
|
|
373
281
|
const bad = rest.filter((a) => a.startsWith('--')).map((a) => a.slice(2).split('=')[0]).find((f) => !WORKSPACE_FLAGS[cmd].includes(f));
|
|
374
282
|
if (bad) throw new Error(`unknown flag "--${bad}" on \`dt ${cmd}\`\n known: ${WORKSPACE_FLAGS[cmd].map((f) => `--${f}`).join(', ') || '(none — this verb takes no flags)'}`);
|
|
@@ -386,12 +294,19 @@ export function run(argv) {
|
|
|
386
294
|
process.exit(init({ flags }));
|
|
387
295
|
}
|
|
388
296
|
if (!cmd || cmd === 'help') {
|
|
389
|
-
// `help` works OUTSIDE a workspace too —
|
|
390
|
-
//
|
|
391
|
-
|
|
297
|
+
// `help` works OUTSIDE a workspace too — a person who just ran `npm i -g dreamteamer` on a
|
|
298
|
+
// bare machine has nothing else to read. Inside one, each installed extension's own usage
|
|
299
|
+
// follows the engine's.
|
|
300
|
+
let ws = null;
|
|
301
|
+
try { ws = await openWorkspace(); } catch { /* no workspace, or an extension that will not load — plain help */ }
|
|
302
|
+
emit(USAGE + extensionUsage(ws));
|
|
392
303
|
process.exit(0);
|
|
393
304
|
}
|
|
394
|
-
const ws =
|
|
305
|
+
const ws = await openWorkspace();
|
|
306
|
+
// An EXTENSION'S VERB runs in-process, handed this workspace — the same engine instance, the
|
|
307
|
+
// same loaded extensions. Its exit code is the process's.
|
|
308
|
+
const ext = ws.extensions.find((e) => cmd in e.commands);
|
|
309
|
+
if (ext) process.exit((await ext.commands[cmd].run(ws, rest)) ?? 0);
|
|
395
310
|
switch (cmd) {
|
|
396
311
|
// ONE verb makes a thing present and ready: this checkout, or an attached repo's
|
|
397
312
|
// working tree. `ensure` was the second spelling of the same idea and is retired in the
|
|
@@ -435,57 +350,20 @@ export function run(argv) {
|
|
|
435
350
|
// as a symlink placed by a verb whose whole job is to print.
|
|
436
351
|
if (given.includes('--hook')) {
|
|
437
352
|
refuse('dt install --hook', ['--hook']);
|
|
438
|
-
process.exit(installCommand(ws, rest));
|
|
353
|
+
process.exit(await installCommand(ws, rest, { open: openWorkspace }));
|
|
439
354
|
}
|
|
440
355
|
if (given.includes('--print-adapters')) {
|
|
441
356
|
refuse('dt install --print-adapters', ['--print-adapters']);
|
|
442
|
-
process.exit(installCommand(ws, rest));
|
|
357
|
+
process.exit(await installCommand(ws, rest, { open: openWorkspace }));
|
|
443
358
|
}
|
|
444
359
|
refuse('dt install', ['--dry-run', '--json', '--link-env']);
|
|
445
|
-
process.exit(installCommand(ws, rest));
|
|
446
|
-
}
|
|
447
|
-
// ⚠ THE EXIT CODE IS THE WHOLE POINT OF THIS VERB, so `proveCommand` RETURNS it and this
|
|
448
|
-
// line is the only place it becomes a process exit. Six codes are a contract — 0 pass ·
|
|
449
|
-
// 1 fail · 3 unavailable · 4 no-fixture · 5 an actor is owed a step · 6 vacuous — and a
|
|
450
|
-
// `throw` inside `prove` becomes 1 through the shared catch below, which is right for
|
|
451
|
-
// every refusal it makes (a target that names nothing, a resume with nothing to resume,
|
|
452
|
-
// a stray flag of the other form): those are errors about the INVOCATION, not verdicts
|
|
453
|
-
// about an artifact, and 2 is already spoken for by "you typed a verb that is gone".
|
|
454
|
-
//
|
|
455
|
-
// The per-form flag refusal lives in `proveCommand` rather than here, unlike `install`'s:
|
|
456
|
-
// deciding which form was typed means resolving the target against the compiled proofs
|
|
457
|
-
// and artifacts, and a second copy of that resolution in this file — purely to choose
|
|
458
|
-
// which message to print — is the drift the comment above `case 'install':` describes.
|
|
459
|
-
case 'prove':
|
|
460
|
-
warnIfStale(ws.root);
|
|
461
|
-
process.exit(proveCommand(ws, rest).code);
|
|
462
|
-
// THE ONE MOVEMENT VERB (decision 309). Two forms and, like `install`'s, each refuses the
|
|
463
|
-
// other's vocabulary here: the flag table can only say which flags `land` HAS, and a
|
|
464
|
-
// `--hook --keep` accepted-and-dropped would promise a worktree kept by a form that is
|
|
465
|
-
// always a dry run. A refusal or a conflict throws or returns 1; 2 stays what it is
|
|
466
|
-
// everywhere else — "you typed a verb that is gone".
|
|
467
|
-
case 'land': {
|
|
468
|
-
const given = rest.filter((a) => a.startsWith('--'));
|
|
469
|
-
const stray = given.filter((f) => !(given.includes('--hook') ? ['--hook', '--dry-run', '--json'] : ['--keep', '--dry-run', '--branch', '--json']).includes(f));
|
|
470
|
-
if (stray.length) {
|
|
471
|
-
const form = given.includes('--hook') ? 'dt land --hook' : 'dt land worktrees/<name>';
|
|
472
|
-
throw new Error(`${stray.join(' ')} ${stray.length > 1 ? 'are not flags' : 'is not a flag'} of \`${form}\` — that form takes ${(given.includes('--hook') ? ['--hook', '--dry-run', '--json'] : ['--keep', '--dry-run', '--branch', '--json']).join(' ')}`);
|
|
473
|
-
}
|
|
474
|
-
warnIfStale(ws.root);
|
|
475
|
-
process.exit(landCommand(ws, rest));
|
|
360
|
+
process.exit(await installCommand(ws, rest, { open: openWorkspace }));
|
|
476
361
|
}
|
|
477
362
|
case 'update': {
|
|
478
363
|
const code = update(ws, rest.find((a) => !a.startsWith('--')));
|
|
479
364
|
compile(ws); // pulled modules may carry new sources — prints its own summary
|
|
480
365
|
process.exit(code);
|
|
481
366
|
}
|
|
482
|
-
case 'start': {
|
|
483
|
-
warnIfStale(ws.root);
|
|
484
|
-
const portIdx = rest.indexOf('--port');
|
|
485
|
-
import('./server.js').then(({ startServer }) =>
|
|
486
|
-
startServer(ws, { port: portIdx > -1 ? Number(rest[portIdx + 1]) : 8080 }));
|
|
487
|
-
return; // keep the process alive
|
|
488
|
-
}
|
|
489
367
|
case 'compile': {
|
|
490
368
|
const code = compile(ws);
|
|
491
369
|
if (!rest.includes('--watch')) process.exit(code);
|
|
@@ -611,51 +489,13 @@ export function run(argv) {
|
|
|
611
489
|
console.log(line);
|
|
612
490
|
}
|
|
613
491
|
// ⚠ WHICH CHECKOUT AM I. Everything `install` decides turns on this, and a linked
|
|
614
|
-
// worktree is indistinguishable from the primary by eye — so it is stated
|
|
615
|
-
//
|
|
616
|
-
// here. Wrapped like every other block below: `status` is the command run when
|
|
617
|
-
// things are already wrong, and it must still print.
|
|
492
|
+
// worktree is indistinguishable from the primary by eye — so it is stated. Wrapped like
|
|
493
|
+
// every other block below: `status` is the command run when things are already wrong.
|
|
618
494
|
try {
|
|
619
495
|
const co = describeCheckout(ws.root);
|
|
620
496
|
console.log(`checkout: ${co.kind === 'primary' ? 'primary' : `linked worktree of ${co.primary}`}`);
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
} catch { /* not a git checkout — nothing to report about worktrees */ }
|
|
624
|
-
// ⚠ WHAT THIS MACHINE HAS ACTUALLY PROVED. A ledger is per-machine and gitignored, so
|
|
625
|
-
// this line cannot be derived from the repo — and it is the only place a FAIL from
|
|
626
|
-
// last week surfaces without being asked for. The TAIL per proof, not every row: a
|
|
627
|
-
// proof that failed on Monday and passed on Tuesday is passing. Wrapped like every
|
|
628
|
-
// block here — an older runtime has no `proofs` descriptor, and status must still print.
|
|
629
|
-
// ⚠ M5 — COUNTED AS THE WALK GOES, NEVER ASSIGNED AT THE END OF IT. `proofsFailed =
|
|
630
|
-
// tally.FAIL` sat below the loop, inside the try — so a throw partway through (an
|
|
631
|
-
// unreadable ledger, a record that will not parse) left the gate reading ZERO failures
|
|
632
|
-
// and `--strict` exiting 0 BECAUSE the count broke. That is the one direction a gate
|
|
633
|
-
// must never fail: a silent green bought with a swallowed exception.
|
|
634
|
-
let proofsFailed = 0;
|
|
635
|
-
try {
|
|
636
|
-
const tally = { PASS: 0, FAIL: 0, UNAVAILABLE: 0 };
|
|
637
|
-
let declared = 0; let never = 0; let other = 0;
|
|
638
|
-
// ⚠ A SANDBOX NOTHING WILL COME BACK FOR. `.worktrees/` is gitignored, so a kept or
|
|
639
|
-
// un-removable one accumulates in silence and the ledger is the only thing that
|
|
640
|
-
// knows the directory exists. `kept` is not a row field: a terminal row with a
|
|
641
|
-
// sandbox and NO removal attempted (`null`) is exactly what `--keep` leaves behind.
|
|
642
|
-
// `existsSync` because a count nothing can clear is a lie.
|
|
643
|
-
const left = new Set();
|
|
644
|
-
for (const { id } of new Store(ws).readAll('proofs')) {
|
|
645
|
-
declared++;
|
|
646
|
-
const rows = readLedger(ws.root, id);
|
|
647
|
-
const t = rows[rows.length - 1];
|
|
648
|
-
if (!t) never++;
|
|
649
|
-
else if (t.verdict in tally) { tally[t.verdict]++; if (t.verdict === 'FAIL') proofsFailed++; }
|
|
650
|
-
else other++;
|
|
651
|
-
for (const r of rows) {
|
|
652
|
-
const behind = r.sandbox_removed === false || (r.verdict !== 'PENDING' && r.sandbox_removed === null);
|
|
653
|
-
if (r.sandbox && behind && fs.existsSync(r.sandbox)) left.add(r.sandbox);
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
console.log(`proofs: ${declared} declared · ${tally.PASS} passed · ${tally.FAIL} failed · ${tally.UNAVAILABLE} unavailable · ${never} never${other ? ` · ${other} other` : ''}`);
|
|
657
|
-
if (left.size) console.log(` sandboxes left behind: ${left.size} — dt list worktrees`);
|
|
658
|
-
} catch { /* no proofs descriptor compiled — nothing to report */ }
|
|
497
|
+
} catch { /* not a git checkout */ }
|
|
498
|
+
if (ws.extensions.length) console.log(`extensions: ${ws.extensions.map((e) => `${e.name}@${e.version}`).join(' · ')}`);
|
|
659
499
|
console.log(`entries: ${Object.keys(s.manifest.entries).length}`);
|
|
660
500
|
// repos materialize LAZILY, so presence is REPORTED here rather than stored on the
|
|
661
501
|
// record. Wrapped: an older workspace may predate the repos descriptor, and status
|
|
@@ -686,45 +526,14 @@ export function run(argv) {
|
|
|
686
526
|
process.exit(1);
|
|
687
527
|
}
|
|
688
528
|
console.log('✔ .dreamteamer is fresh');
|
|
689
|
-
// ⚠ THE FAIL IS FATAL ONLY WHEN ASKED. `status` is the command you run when things are
|
|
690
|
-
// already wrong, so it prints EVERYTHING first and gates last — the same shape the
|
|
691
|
-
// staleness exit above has.
|
|
692
|
-
// ⚠ R38/R46 — `--strict=true` IS THE SAME FLAG, AND `--strict=false` IS OFF. The
|
|
693
|
-
// unknown-flag gate above splits on `=`, so the `=` spelling was ACCEPTED and then read
|
|
694
|
-
// as "no --strict at all" — a CI step written that way stayed green over a failing
|
|
695
|
-
// proof, for a reason nothing printed. `flagEnabled` is the one reader of that shape,
|
|
696
|
-
// shared with `dt prove`, so the two verbs cannot disagree about what was typed.
|
|
697
|
-
if (flagEnabled(rest, 'strict') && proofsFailed) {
|
|
698
|
-
console.log(`✖ ${proofsFailed} proof(s) FAILED on this machine — dt list proofs`);
|
|
699
|
-
process.exit(1);
|
|
700
|
-
}
|
|
701
529
|
process.exit(0);
|
|
702
530
|
}
|
|
703
|
-
// `export <target>` — the workspace rendered for a consumer that is not a coding agent. One
|
|
704
|
-
// target today; the map in export-notebooklm.js is where a second one would register, the way
|
|
705
|
-
// harnesses do. Without --notebook/--create it is a pure render and touches no network.
|
|
706
|
-
case 'export': {
|
|
707
|
-
warnIfStale(ws.root);
|
|
708
|
-
const { flags, pos } = parseArgs(rest);
|
|
709
|
-
process.exit(exportCommand(ws, pos[0], flags));
|
|
710
|
-
}
|
|
711
531
|
case 'help':
|
|
712
532
|
emit(USAGE);
|
|
713
533
|
process.exit(0);
|
|
714
534
|
case 'list': case 'add': case 'values':
|
|
715
535
|
case 'get': case 'set': case 'rm': case 'rename': case 'history': case 'diff': case 'revert':
|
|
716
536
|
case 'move': case 'next': {
|
|
717
|
-
// ⚠ `worktrees` IS NOT A COLLECTION — it is observed from git — so it is intercepted
|
|
718
|
-
// here rather than being dispatched. Which means it never reaches
|
|
719
|
-
// `collectionCommand`, where every other verb's flags are refused: the parse and the
|
|
720
|
-
// refusal have to be done HERE or `--tmep` is swallowed and a request for a
|
|
721
|
-
// throwaway sandbox silently becomes a permanent branch worktree.
|
|
722
|
-
const target = rest[0];
|
|
723
|
-
if (target === 'worktrees' || target?.startsWith('worktrees/')) {
|
|
724
|
-
const { flags } = parseArgs(rest.slice(1));
|
|
725
|
-
refuseUnknownFlags(null, 'worktrees', cmd, flags);
|
|
726
|
-
process.exit(worktreeCommand(ws, cmd, target, flags));
|
|
727
|
-
}
|
|
728
537
|
warnIfStale(ws.root);
|
|
729
538
|
process.exit(dispatchRecordVerb(ws, cmd, rest));
|
|
730
539
|
}
|
|
@@ -749,6 +558,24 @@ export function run(argv) {
|
|
|
749
558
|
case 'relations':
|
|
750
559
|
warnIfStale(ws.root);
|
|
751
560
|
process.exit(relationsCommand(ws, rest));
|
|
561
|
+
case 'relocate': {
|
|
562
|
+
warnIfStale(ws.root);
|
|
563
|
+
const store = new Store(ws);
|
|
564
|
+
const target = rest.find((a) => !a.startsWith('--'));
|
|
565
|
+
if (!target) throw new Error('dt relocate needs a target: dreamteamer relocate <collection> | <collection>/<id> [--dry-run]');
|
|
566
|
+
// a collection, or one record of it — the either-shape every other target has
|
|
567
|
+
const asCollection = canonicalCollection(store.descriptors, target);
|
|
568
|
+
const { collection, id } = asCollection ? { collection: asCollection, id: null } : splitRef(store.descriptors, target);
|
|
569
|
+
const out = store.relocate(collection, { only: id ? [id] : null, dryRun: rest.includes('--dry-run'), toRoot: rest.includes('--to-root') });
|
|
570
|
+
if (rest.includes('--json')) { emit(JSON.stringify(out, null, 2)); process.exit(out.problems.length ? 1 : 0); }
|
|
571
|
+
const rel = (p) => path.relative(ws.root, p);
|
|
572
|
+
for (const m of out.moves) console.log(`${out.applied ? '✔' : '→'} ${collection}/${m.id} ${rel(m.from)} → ${rel(m.to)}`);
|
|
573
|
+
for (const p of out.problems) console.error(`✖ ${p}`);
|
|
574
|
+
if (!out.moves.length && !out.problems.length) console.log(`nothing to relocate — every ${collection} record is where its descriptor puts it`);
|
|
575
|
+
else if (!out.applied && !out.problems.length) console.log(`${out.moves.length} move(s) planned (dry run) — nothing was moved`);
|
|
576
|
+
else if (out.applied) console.log(`${out.moves.length} record(s) relocated — ids and references unchanged; \`dreamteamer commit ${collection}\` publishes the moves`);
|
|
577
|
+
process.exit(out.problems.length ? 1 : 0);
|
|
578
|
+
}
|
|
752
579
|
case 'resolve':
|
|
753
580
|
process.exit(resolveVariables(ws, rest));
|
|
754
581
|
default:
|
|
@@ -795,6 +622,10 @@ export function run(argv) {
|
|
|
795
622
|
console.error(' dt list commands the command entities this workspace ships');
|
|
796
623
|
process.exit(2);
|
|
797
624
|
}
|
|
625
|
+
if (MOVED_VERBS.has(cmd)) {
|
|
626
|
+
console.error(`✖ \`dt ${cmd}\` left core in 0.31.0 and returns as an extension, which is not published yet — dreamteamer 0.30.x still has it, and a workspace module can carry its own verb (\`dreamteamer.extension\`)`);
|
|
627
|
+
process.exit(2);
|
|
628
|
+
}
|
|
798
629
|
console.error(`✖ unknown verb "${cmd}" — dreamteamer is verb-first since 0.12.0: dt <verb> [<target>]`);
|
|
799
630
|
emit(USAGE, 2);
|
|
800
631
|
process.exit(1);
|
|
@@ -811,26 +642,11 @@ export function run(argv) {
|
|
|
811
642
|
}
|
|
812
643
|
}
|
|
813
644
|
|
|
814
|
-
/**
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
if (bad) throw new Error(`unknown flag "--${bad}" on \`dt setup\`\n known: ${WORKSPACE_FLAGS.setup.map((f) => `--${f}`).join(', ')}`);
|
|
820
|
-
return hostSetup(hostFlags(rest).flags);
|
|
821
|
-
}
|
|
822
|
-
const target = driverTarget(rest[0]);
|
|
823
|
-
// `export container` is the driver's; `export notebooklm` stays the workspace verb below
|
|
824
|
-
if (target && (cmd === 'export' || cmd === 'import')) return archiveCommand(cmd, target, rest.slice(1));
|
|
825
|
-
if (cmd === 'import') return Promise.reject(new Error('dt import container <name> <file> [--workspace <w>]... [--replace]'));
|
|
826
|
-
if (target && DRIVER_VERBS.has(cmd)) return driverCommand(cmd, target, rest.slice(1));
|
|
827
|
-
// A lifecycle verb aimed at anything else is refused by name: `dt start tasks` is not a
|
|
828
|
-
// server and not a container, and "unknown collection" would send the reader the wrong way.
|
|
829
|
-
if (LIFECYCLE_VERBS.has(cmd) && rest[0] && !rest[0].startsWith('--')) {
|
|
830
|
-
return Promise.reject(new Error(`\`${cmd}\` is a container lifecycle verb — "${rest[0]}" is not a container. dt ${cmd} container <name>${cmd === 'start' ? ' --template <t>' : ''}${cmd === 'start' ? '; a bare `dt start` serves the REST api' : ''}`));
|
|
831
|
-
}
|
|
832
|
-
if (cmd === 'stop' || cmd === 'open') return Promise.reject(new Error(`dt ${cmd} container <name> — see \`dreamteamer help\``));
|
|
833
|
-
return null;
|
|
645
|
+
/** Each installed extension's usage block, headed by its package — '' outside a workspace. */
|
|
646
|
+
function extensionUsage(ws) {
|
|
647
|
+
const blocks = (ws?.extensions ?? []).filter((e) => Object.keys(e.commands).length).map((e) =>
|
|
648
|
+
`\n${e.name}@${e.version}:\n${Object.entries(e.commands).map(([verb, c]) => (c.usage ?? ` ${verb}`).replace(/\s+$/, '')).join('\n')}`);
|
|
649
|
+
return blocks.length ? `\ninstalled extensions:${blocks.join('\n')}` : '';
|
|
834
650
|
}
|
|
835
651
|
|
|
836
652
|
/** Translate `dt <verb> <target> …` into the noun-verb call the implementation layer takes. */
|
|
@@ -940,7 +756,7 @@ function watchAndRecompile(ws) {
|
|
|
940
756
|
};
|
|
941
757
|
// 'system' plus the flat kinds: the classic layout can put sources at the workspace root under
|
|
942
758
|
// either spelling, and a watcher that misses one makes --watch quietly stop recompiling.
|
|
943
|
-
for (const dir of ['system', ...
|
|
759
|
+
for (const dir of ['system', ...kindsOf(ws), 'modules', 'git_modules'].map((d) => path.join(ws.root, d))) {
|
|
944
760
|
if (fs.existsSync(dir)) fs.watch(dir, { recursive: true }, trigger);
|
|
945
761
|
}
|
|
946
762
|
}
|