motifcode 0.2.0 → 0.3.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 +4 -2
- package/dist/build-info.json +3 -3
- package/dist/motif.js +238 -93
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,11 +13,13 @@ failure handling follow from what is measurably true about Motif-3.
|
|
|
13
13
|
> Technologies or by Infron. *Motif* and *Motif-3* are their names; this package is a client of the model.
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
npm install -g motifcode # or: npx motifcode
|
|
17
16
|
cd your-project
|
|
18
|
-
|
|
17
|
+
npx motifcode # asks for your Infron API key once, then offers to install the `motif` command
|
|
19
18
|
```
|
|
20
19
|
|
|
20
|
+
Or `npm install -g motifcode` directly; either way `motif` (or `motifcode`) opens the session
|
|
21
|
+
from any folder afterwards, with the key saved in `~/.motif/.env`.
|
|
22
|
+
|
|
21
23
|
1. Sign in at [infron.ai/login](https://infron.ai/login), open [Dashboard → API Keys](https://infron.ai/dashboard/apiKeys), click **Add new key**.
|
|
22
24
|
2. Run `motif` and paste the key when asked. It is checked against the endpoint, saved to
|
|
23
25
|
`~/.motif/.env` (readable only by you), and never shown to the model. `motif login` does the
|
package/dist/build-info.json
CHANGED
package/dist/motif.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// packages/cli/src/main.ts
|
|
4
|
-
import { existsSync as
|
|
4
|
+
import { existsSync as existsSync10, readFileSync as readFileSync9, readdirSync as readdirSync4 } from "node:fs";
|
|
5
5
|
import { homedir as homedir5 } from "node:os";
|
|
6
|
-
import { join as
|
|
6
|
+
import { join as join10 } from "node:path";
|
|
7
7
|
|
|
8
8
|
// packages/tools/src/schemas.ts
|
|
9
9
|
var CORE_TOOLS = Object.freeze([
|
|
@@ -470,7 +470,7 @@ function openStringArray(block, arrayKeys) {
|
|
|
470
470
|
if (arrayKeys.size === 0) return block;
|
|
471
471
|
const alt = [...arrayKeys].map((k) => k.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
|
|
472
472
|
const re = new RegExp(`"(${alt})"(\\s*:\\s*)"`, "g");
|
|
473
|
-
return block.replace(re, (_m, key,
|
|
473
|
+
return block.replace(re, (_m, key, sep3) => `"${key}"${sep3}["`);
|
|
474
474
|
}
|
|
475
475
|
function balanceBracketsDetailed(block) {
|
|
476
476
|
const CLOSER = { "{": "}", "[": "]" };
|
|
@@ -831,6 +831,32 @@ function looksLikeLeakedToolCall(result) {
|
|
|
831
831
|
if (result.unrecoverable.length > 0) return true;
|
|
832
832
|
return /<tool_call>|<\/tool_call>/.test(result.content);
|
|
833
833
|
}
|
|
834
|
+
function stripLoneFence(s) {
|
|
835
|
+
const m = /^```(?:json|tool_call)?\s*([\s\S]*?)\s*```$/.exec(s.trim());
|
|
836
|
+
return m ? (m[1] ?? "").trim() : s.trim();
|
|
837
|
+
}
|
|
838
|
+
function recoverBareToolCall(content, ctx) {
|
|
839
|
+
const body = stripLoneFence(content);
|
|
840
|
+
if (!body.startsWith("{") || !body.endsWith("}")) return null;
|
|
841
|
+
const strict = strictLoad(body);
|
|
842
|
+
const outcome = strict !== null ? { value: coerceArgumentsWrapper(strict), info: CLEAN } : repairBlockDetailed(body, ctx);
|
|
843
|
+
if (outcome.value === null) return null;
|
|
844
|
+
const name = typeof outcome.value["name"] === "string" ? outcome.value["name"] : "";
|
|
845
|
+
if (name === "" || !ctx.specs.has(name)) return null;
|
|
846
|
+
return {
|
|
847
|
+
name,
|
|
848
|
+
arguments: asArguments(outcome.value["arguments"]),
|
|
849
|
+
repaired: true,
|
|
850
|
+
repair: { kind: "detag", lossy: outcome.info.lossy, complete: outcome.info.complete }
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
function contentLeaksToolCall(content, ctx) {
|
|
854
|
+
if (/<\/?tool_call>/.test(content)) return true;
|
|
855
|
+
for (const m of content.matchAll(/\{[^{}]*"name"\s*:\s*"([a-z_]+)"[\s\S]*?\}/g)) {
|
|
856
|
+
if (ctx.specs.has(m[1])) return true;
|
|
857
|
+
}
|
|
858
|
+
return false;
|
|
859
|
+
}
|
|
834
860
|
|
|
835
861
|
// packages/protocol/src/scrubber.ts
|
|
836
862
|
var MARKERS = [THINK_OPEN, THINK_CLOSE];
|
|
@@ -969,6 +995,24 @@ var ToolCallChannel = class {
|
|
|
969
995
|
...c.repair ? { repair: c.repair } : {}
|
|
970
996
|
}
|
|
971
997
|
);
|
|
998
|
+
if (actions.length === 0) {
|
|
999
|
+
const bare = recoverBareToolCall(r.content, ctx);
|
|
1000
|
+
if (bare) {
|
|
1001
|
+
const action = bare.name === "done" ? {
|
|
1002
|
+
kind: "done",
|
|
1003
|
+
summary: String(bare.arguments["summary"] ?? ""),
|
|
1004
|
+
...typeof bare.arguments["confirm"] === "boolean" ? { confirm: bare.arguments["confirm"] } : {},
|
|
1005
|
+
...bare.repair ? { repair: bare.repair } : {}
|
|
1006
|
+
} : {
|
|
1007
|
+
kind: "tool",
|
|
1008
|
+
name: bare.name,
|
|
1009
|
+
arguments: bare.arguments,
|
|
1010
|
+
repaired: true,
|
|
1011
|
+
...bare.repair ? { repair: bare.repair } : {}
|
|
1012
|
+
};
|
|
1013
|
+
return { actions: [action], content: "", unrecoverable: [], truncated: false };
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
972
1016
|
return {
|
|
973
1017
|
actions,
|
|
974
1018
|
content: r.content.trim(),
|
|
@@ -2931,7 +2975,7 @@ async function runLoop(opts) {
|
|
|
2931
2975
|
} : { id: nextId(), name: a.name, arguments: a.arguments }
|
|
2932
2976
|
);
|
|
2933
2977
|
if (parsed.actions.length === 0) {
|
|
2934
|
-
const leaked = parsed.unrecoverable.length > 0 || parsed.truncated || (parsed.invalidArguments?.length ?? 0) > 0 || channel === "toolcall" && looksLikeLeakedToolCall(parseToolCalls(split.content, ctx));
|
|
2978
|
+
const leaked = parsed.unrecoverable.length > 0 || parsed.truncated || (parsed.invalidArguments?.length ?? 0) > 0 || channel === "toolcall" && (looksLikeLeakedToolCall(parseToolCalls(split.content, ctx)) || contentLeaksToolCall(split.content, ctx));
|
|
2935
2979
|
if (replyEnds && !leaked && response.finishReason !== "length") {
|
|
2936
2980
|
session.appendAll(codec.serializeAssistant(split.content, split.reasoning, parsed));
|
|
2937
2981
|
checkpoint();
|
|
@@ -2960,7 +3004,8 @@ async function runLoop(opts) {
|
|
|
2960
3004
|
leaked ? "Your last turn did not produce a usable action. It looks like action syntax that failed to parse." : "Your last turn produced no action.",
|
|
2961
3005
|
channel,
|
|
2962
3006
|
consecutiveNoAction,
|
|
2963
|
-
lastObservation
|
|
3007
|
+
lastObservation,
|
|
3008
|
+
!leaked
|
|
2964
3009
|
);
|
|
2965
3010
|
if (consecutiveNoAction === 1) {
|
|
2966
3011
|
handBack(nudge);
|
|
@@ -3160,8 +3205,8 @@ function refusalPrompt(refusals, channel) {
|
|
|
3160
3205
|
repairPrompt("", channel).trim()
|
|
3161
3206
|
].join("\n");
|
|
3162
3207
|
}
|
|
3163
|
-
function repairPrompt(problem, channel, attempt = 1, lastObservation = "") {
|
|
3164
|
-
const how = channel === "toolcall" ? "Emit a well-formed `<tool_call>` block. Watch backslashes: inside JSON strings, shell `$` and regex metacharacters must be escaped or avoided." : channel === "object" ? "Reply with a single well-formed JSON object matching the schema you were given." : "Reply with the XML shape you were given. Command bodies are verbatim \u2014 do not escape anything inside them.";
|
|
3208
|
+
function repairPrompt(problem, channel, attempt = 1, lastObservation = "", cleanReply = false) {
|
|
3209
|
+
const how = cleanReply ? "If the task is already complete, or the message only needs an answer, call `done` now with that answer as the summary. Otherwise take the next concrete step toward the task above \u2014 do not read files or run commands looking for unrelated work to do." : channel === "toolcall" ? "Emit a well-formed `<tool_call>` block. Watch backslashes: inside JSON strings, shell `$` and regex metacharacters must be escaped or avoided." : channel === "object" ? "Reply with a single well-formed JSON object matching the schema you were given." : "Reply with the XML shape you were given. Command bodies are verbatim \u2014 do not escape anything inside them.";
|
|
3165
3210
|
const parts = [problem, "", how];
|
|
3166
3211
|
if (attempt >= 2) {
|
|
3167
3212
|
parts.push(
|
|
@@ -3698,7 +3743,7 @@ var RULES = [
|
|
|
3698
3743
|
// redact commit hashes, base64 test fixtures and half of a lockfile.
|
|
3699
3744
|
name: "secret-assignment",
|
|
3700
3745
|
pattern: /\b([A-Z0-9_]*(?:SECRET|TOKEN|PASSWORD|PASSWD|API_KEY|APIKEY|ACCESS_KEY|PRIVATE_KEY)[A-Z0-9_]*)(\s*[=:]\s*)(?:"([^"]*)"|'([^']*)'|(\S+))/g,
|
|
3701
|
-
replace: (_m, name,
|
|
3746
|
+
replace: (_m, name, sep3) => `${name}${sep3}${MASK}`
|
|
3702
3747
|
}
|
|
3703
3748
|
];
|
|
3704
3749
|
function redactText(text) {
|
|
@@ -6217,8 +6262,8 @@ var Screen = class {
|
|
|
6217
6262
|
};
|
|
6218
6263
|
|
|
6219
6264
|
// packages/cli/src/chat.ts
|
|
6220
|
-
import { appendFileSync as appendFileSync2, existsSync as
|
|
6221
|
-
import { dirname as dirname5, join as
|
|
6265
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync6, statSync as statSync3 } from "node:fs";
|
|
6266
|
+
import { dirname as dirname5, join as join7, resolve as resolve5 } from "node:path";
|
|
6222
6267
|
|
|
6223
6268
|
// packages/cli/src/files.ts
|
|
6224
6269
|
import { execFileSync } from "node:child_process";
|
|
@@ -6347,6 +6392,24 @@ function expandMentions(text, mentions, opts) {
|
|
|
6347
6392
|
${blocks.join("\n\n")}`, attached };
|
|
6348
6393
|
}
|
|
6349
6394
|
|
|
6395
|
+
// packages/cli/src/install.ts
|
|
6396
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
6397
|
+
import { delimiter, join as join5, sep as sep2 } from "node:path";
|
|
6398
|
+
function ranFromNpx(entry) {
|
|
6399
|
+
if (!entry) return false;
|
|
6400
|
+
return entry.split(/[\\/]/).includes("_npx");
|
|
6401
|
+
}
|
|
6402
|
+
function commandOnPath(name, pathEnv = process.env["PATH"] ?? "") {
|
|
6403
|
+
for (const dir of pathEnv.split(delimiter)) {
|
|
6404
|
+
if (dir === "" || dir.split(sep2).includes("_npx") || dir.includes(`${sep2}_npx${sep2}`) || dir.includes("/_npx/")) continue;
|
|
6405
|
+
if (existsSync5(join5(dir, name)) || existsSync5(join5(dir, `${name}.cmd`))) return true;
|
|
6406
|
+
}
|
|
6407
|
+
return false;
|
|
6408
|
+
}
|
|
6409
|
+
function installCommand(version) {
|
|
6410
|
+
return `npm install -g motifcode@${version}`;
|
|
6411
|
+
}
|
|
6412
|
+
|
|
6350
6413
|
// packages/cli/src/login.ts
|
|
6351
6414
|
import { homedir as homedir3 } from "node:os";
|
|
6352
6415
|
var KEY_PAGE = "https://infron.ai/dashboard/apiKeys";
|
|
@@ -6765,7 +6828,7 @@ async function runSlash(text, ctx) {
|
|
|
6765
6828
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
6766
6829
|
import { mkdtempSync, realpathSync as realpathSync2 } from "node:fs";
|
|
6767
6830
|
import { platform, tmpdir } from "node:os";
|
|
6768
|
-
import { join as
|
|
6831
|
+
import { join as join6 } from "node:path";
|
|
6769
6832
|
function has(binary) {
|
|
6770
6833
|
try {
|
|
6771
6834
|
execFileSync2("/bin/sh", ["-c", `command -v ${binary}`], { stdio: "ignore" });
|
|
@@ -6856,7 +6919,7 @@ function detectSandbox() {
|
|
|
6856
6919
|
return cached;
|
|
6857
6920
|
}
|
|
6858
6921
|
function makeScratch() {
|
|
6859
|
-
return mkdtempSync(
|
|
6922
|
+
return mkdtempSync(join6(tmpdir(), "motif-scratch-"));
|
|
6860
6923
|
}
|
|
6861
6924
|
|
|
6862
6925
|
// packages/cli/src/doctor.ts
|
|
@@ -6967,22 +7030,23 @@ async function doctor(opts) {
|
|
|
6967
7030
|
}
|
|
6968
7031
|
);
|
|
6969
7032
|
}
|
|
7033
|
+
const probeBody = JSON.stringify({
|
|
7034
|
+
model: opts.model,
|
|
7035
|
+
temperature: SAMPLING_DEFAULTS.temperature,
|
|
7036
|
+
top_p: SAMPLING_DEFAULTS.top_p,
|
|
7037
|
+
stream: false,
|
|
7038
|
+
max_tokens: 256,
|
|
7039
|
+
messages: [
|
|
7040
|
+
{ role: "system", content: "You are a coding agent. Finish by calling the `done` tool." },
|
|
7041
|
+
{ role: "user", content: 'Call `done` now with the summary "ok". Do nothing else.' }
|
|
7042
|
+
],
|
|
7043
|
+
tools: [PROBE_TOOL]
|
|
7044
|
+
});
|
|
6970
7045
|
try {
|
|
6971
7046
|
const res = await fetchImpl(`${endpoint}/v1/chat/completions`, {
|
|
6972
7047
|
method: "POST",
|
|
6973
7048
|
headers,
|
|
6974
|
-
body:
|
|
6975
|
-
model: opts.model,
|
|
6976
|
-
temperature: SAMPLING_DEFAULTS.temperature,
|
|
6977
|
-
top_p: SAMPLING_DEFAULTS.top_p,
|
|
6978
|
-
stream: false,
|
|
6979
|
-
max_tokens: 256,
|
|
6980
|
-
messages: [
|
|
6981
|
-
{ role: "system", content: "You are a coding agent. Finish by calling the `done` tool." },
|
|
6982
|
-
{ role: "user", content: 'Call `done` now with the summary "ok". Do nothing else.' }
|
|
6983
|
-
],
|
|
6984
|
-
tools: [PROBE_TOOL]
|
|
6985
|
-
})
|
|
7049
|
+
body: probeBody
|
|
6986
7050
|
});
|
|
6987
7051
|
if (res.status === 401 || res.status === 403) {
|
|
6988
7052
|
const text = (await res.text().catch(() => "")).trim().slice(0, 200);
|
|
@@ -7029,12 +7093,31 @@ async function doctor(opts) {
|
|
|
7029
7093
|
fix: "the harness splits it client-side; that is a fallback, not the design"
|
|
7030
7094
|
} : { name: "reasoning parser", state: "unknown", detail: "no reasoning in the probe response" }
|
|
7031
7095
|
);
|
|
7032
|
-
|
|
7096
|
+
let cached2 = json.usage?.prompt_tokens_details?.cached_tokens;
|
|
7097
|
+
const reports = cached2 !== void 0;
|
|
7098
|
+
try {
|
|
7099
|
+
const again = await fetchImpl(`${endpoint}/v1/chat/completions`, {
|
|
7100
|
+
method: "POST",
|
|
7101
|
+
headers,
|
|
7102
|
+
body: probeBody
|
|
7103
|
+
});
|
|
7104
|
+
if (again.ok) {
|
|
7105
|
+
const json2 = await again.json();
|
|
7106
|
+
const c2 = json2.usage?.prompt_tokens_details?.cached_tokens;
|
|
7107
|
+
if (typeof c2 === "number") cached2 = c2;
|
|
7108
|
+
}
|
|
7109
|
+
} catch {
|
|
7110
|
+
}
|
|
7033
7111
|
checks.push(
|
|
7034
|
-
typeof cached2 === "number" ? {
|
|
7112
|
+
typeof cached2 === "number" && cached2 > 0 ? {
|
|
7035
7113
|
name: "prefix caching",
|
|
7036
7114
|
state: "ok",
|
|
7037
|
-
detail: `the endpoint
|
|
7115
|
+
detail: `the endpoint served ${cached2} prompt tokens from its prefix cache on a repeated request`
|
|
7116
|
+
} : reports ? {
|
|
7117
|
+
name: "prefix caching",
|
|
7118
|
+
state: "unknown",
|
|
7119
|
+
detail: "the endpoint reports cached tokens but served none on this probe",
|
|
7120
|
+
fix: "a two-request probe cannot always warm the cache; in a real session the frozen tool order keeps the prefix alive across turns"
|
|
7038
7121
|
} : {
|
|
7039
7122
|
name: "prefix caching",
|
|
7040
7123
|
state: "unknown",
|
|
@@ -7096,7 +7179,7 @@ function worstState(checks) {
|
|
|
7096
7179
|
|
|
7097
7180
|
// packages/cli/src/executor.ts
|
|
7098
7181
|
import { spawn as spawn2 } from "node:child_process";
|
|
7099
|
-
import { existsSync as
|
|
7182
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
|
|
7100
7183
|
import { dirname as dirname4, resolve as resolve4 } from "node:path";
|
|
7101
7184
|
|
|
7102
7185
|
// packages/cli/src/policy.ts
|
|
@@ -7359,7 +7442,7 @@ function writeFile(path, content, cwd) {
|
|
|
7359
7442
|
try {
|
|
7360
7443
|
const target = resolve4(cwd, path);
|
|
7361
7444
|
mkdirSync4(dirname4(target), { recursive: true });
|
|
7362
|
-
const existed =
|
|
7445
|
+
const existed = existsSync6(target);
|
|
7363
7446
|
writeFileSync3(target, content, "utf8");
|
|
7364
7447
|
const lines = content === "" ? 0 : content.split("\n").length;
|
|
7365
7448
|
return {
|
|
@@ -7676,6 +7759,8 @@ var Chat = class {
|
|
|
7676
7759
|
shellBusy = false;
|
|
7677
7760
|
/** A tool call waiting for the person's yes or no, and which of the three answers is selected. */
|
|
7678
7761
|
pendingConfirm = null;
|
|
7762
|
+
/** A question with numbered answers in place of the prompt, and which one is selected. */
|
|
7763
|
+
pendingChoice = null;
|
|
7679
7764
|
/** A secret being typed in place of the prompt — the API key at login. */
|
|
7680
7765
|
pendingSecret = null;
|
|
7681
7766
|
/** The credential for this session. Starts as the caller's; `/login` replaces it, `/logout` drops it. */
|
|
@@ -7752,6 +7837,7 @@ var Chat = class {
|
|
|
7752
7837
|
this.screen.append({ kind: "system", title: "login", lines: await this.login("startup") });
|
|
7753
7838
|
this.refresh();
|
|
7754
7839
|
}
|
|
7840
|
+
if (this.opts.offerInstall) await this.offerInstall();
|
|
7755
7841
|
if (this.opts.continueFrom) {
|
|
7756
7842
|
try {
|
|
7757
7843
|
this.screen.append({ kind: "system", title: "continuing", lines: await this.resume(this.opts.continueFrom) });
|
|
@@ -7770,6 +7856,10 @@ var Chat = class {
|
|
|
7770
7856
|
this.answerSecret(key);
|
|
7771
7857
|
return;
|
|
7772
7858
|
}
|
|
7859
|
+
if (this.pendingChoice) {
|
|
7860
|
+
this.answerChoice(key);
|
|
7861
|
+
return;
|
|
7862
|
+
}
|
|
7773
7863
|
if (this.pendingConfirm) {
|
|
7774
7864
|
this.answerConfirm(key);
|
|
7775
7865
|
return;
|
|
@@ -8030,6 +8120,58 @@ var Chat = class {
|
|
|
8030
8120
|
get needsLogin() {
|
|
8031
8121
|
return this.opts.requireKey === true && this.apiKey === void 0;
|
|
8032
8122
|
}
|
|
8123
|
+
/** Take over the prompt for one question; resolves with the index chosen, or null on esc. */
|
|
8124
|
+
askChoice(title, lines, options) {
|
|
8125
|
+
return new Promise((resolve6) => {
|
|
8126
|
+
this.pendingChoice = { title, lines, options, selected: 0, resolve: resolve6 };
|
|
8127
|
+
this.refresh();
|
|
8128
|
+
});
|
|
8129
|
+
}
|
|
8130
|
+
/** A number, or ↑↓ and Enter, picks an answer; Esc (and Ctrl-C, Ctrl-D) declines. Nothing else does anything. */
|
|
8131
|
+
answerChoice(key) {
|
|
8132
|
+
const pending = this.pendingChoice;
|
|
8133
|
+
if (!pending) return;
|
|
8134
|
+
const n = pending.options.length;
|
|
8135
|
+
let choice;
|
|
8136
|
+
if (key.type === "enter") choice = pending.selected;
|
|
8137
|
+
else if (key.type === "escape" || key.type === "ctrl" && (key.key === "c" || key.key === "d")) choice = null;
|
|
8138
|
+
else if (key.type === "up") pending.selected = (pending.selected + n - 1) % n;
|
|
8139
|
+
else if (key.type === "down") pending.selected = (pending.selected + 1) % n;
|
|
8140
|
+
else if (key.type === "text" && /^[1-9]$/.test(key.text) && Number(key.text) <= n) choice = Number(key.text) - 1;
|
|
8141
|
+
if (choice !== void 0) {
|
|
8142
|
+
this.pendingChoice = null;
|
|
8143
|
+
pending.resolve(choice);
|
|
8144
|
+
}
|
|
8145
|
+
this.refresh();
|
|
8146
|
+
}
|
|
8147
|
+
/**
|
|
8148
|
+
* `npx motifcode` runs the package without leaving a command behind, and
|
|
8149
|
+
* the first person to try it typed `motif` afterwards and found nothing.
|
|
8150
|
+
* So a run from npx, on a machine without `motif`, is offered the global
|
|
8151
|
+
* install once — npm's own, of exactly the version that is running — and
|
|
8152
|
+
* the session carries on either way.
|
|
8153
|
+
*/
|
|
8154
|
+
async offerInstall() {
|
|
8155
|
+
const command = installCommand(this.opts.version);
|
|
8156
|
+
const choice = await this.askChoice(
|
|
8157
|
+
"Install the motif command?",
|
|
8158
|
+
["This run came from npx, which leaves no command behind.", `${command} puts motif and motifcode on your PATH.`],
|
|
8159
|
+
["1. Yes, install it now", "2. Not now \u2014 npx motifcode keeps working"]
|
|
8160
|
+
);
|
|
8161
|
+
if (choice !== 0) return;
|
|
8162
|
+
const id = `install-${++this.shellSequence}`;
|
|
8163
|
+
this.screen.append({ kind: "tool", id, name: "bash", args: { command }, repaired: false, hooks: [] });
|
|
8164
|
+
this.refresh();
|
|
8165
|
+
const r = await (this.opts.installGlobal ?? ((c) => runShell(c, { cwd: this.settings.cwd, timeoutMs: 18e4, outputCap: 2e4 })))(command);
|
|
8166
|
+
const ok2 = r.code === 0 && !r.timedOut;
|
|
8167
|
+
const output = r.timedOut ? `${r.output.trim()}
|
|
8168
|
+
(killed after ${Math.round(r.ms / 1e3)}s)`.trim() : r.output.trim() || `(exit ${r.code})`;
|
|
8169
|
+
this.screen.apply({ type: "tool_end", id, ok: ok2, output, ms: r.ms });
|
|
8170
|
+
this.screen.append(
|
|
8171
|
+
ok2 ? { kind: "notice", level: "info", text: "installed: from now on `motif` (or `motifcode`) opens this from any folder; this session carries on" } : { kind: "notice", level: "warn", text: `the install did not finish; run \`${command}\` yourself (with sudo if npm's global folder is not yours), or keep using npx motifcode` }
|
|
8172
|
+
);
|
|
8173
|
+
this.refresh();
|
|
8174
|
+
}
|
|
8033
8175
|
/** Take over the prompt for one secret; resolves with the text, or null when given up. */
|
|
8034
8176
|
askSecret(title, lines, prompt, cancelHint) {
|
|
8035
8177
|
return new Promise((resolve6) => {
|
|
@@ -8171,14 +8313,16 @@ var Chat = class {
|
|
|
8171
8313
|
draft: this.composer.snapshot(),
|
|
8172
8314
|
placeholder: PLACEHOLDER,
|
|
8173
8315
|
...this.pendingConfirm ? { confirm: this.confirmView(this.pendingConfirm.call, this.pendingConfirm.selected) } : {},
|
|
8316
|
+
...this.pendingChoice ? { confirm: { title: this.pendingChoice.title, lines: this.pendingChoice.lines, choices: this.pendingChoice.options.map((o, i) => `${i === this.pendingChoice.selected ? "\u276F" : " "} ${o}`) } } : {},
|
|
8174
8317
|
...this.pendingSecret ? { secret: { title: this.pendingSecret.title, lines: this.pendingSecret.lines, prompt: this.pendingSecret.prompt } } : {},
|
|
8175
|
-
...items.length > 0 && !this.pendingConfirm && !this.pendingSecret ? { menu: { items, selected: clampSelection(this.menuSelected, items.length), prefix: this.mentionOpen() ? "@" : "/" } } : {}
|
|
8318
|
+
...items.length > 0 && !this.pendingConfirm && !this.pendingSecret && !this.pendingChoice ? { menu: { items, selected: clampSelection(this.menuSelected, items.length), prefix: this.mentionOpen() ? "@" : "/" } } : {}
|
|
8176
8319
|
};
|
|
8177
8320
|
this.screen.setHint(this.hintText());
|
|
8178
8321
|
this.screen.setComposer(view);
|
|
8179
8322
|
}
|
|
8180
8323
|
hintText() {
|
|
8181
8324
|
if (this.pendingSecret) return this.pendingSecret.cancelHint;
|
|
8325
|
+
if (this.pendingChoice) return "1 2 or \u2191\u2193 enter \xB7 esc leaves it";
|
|
8182
8326
|
if (this.pendingConfirm) return "1 2 3 or \u2191\u2193 enter \xB7 esc declines";
|
|
8183
8327
|
if (this.ctrlCArmedAt > 0 && this.now() - this.ctrlCArmedAt <= CTRL_C_WINDOW_MS) return "ctrl-c again to quit";
|
|
8184
8328
|
if (this.queued.length > 0) {
|
|
@@ -8294,12 +8438,12 @@ ${output.slice(0, 8e3)}
|
|
|
8294
8438
|
* is built per task.
|
|
8295
8439
|
*/
|
|
8296
8440
|
addNote(note) {
|
|
8297
|
-
const path = this.opts.notesPath ??
|
|
8441
|
+
const path = this.opts.notesPath ?? join7(this.settings.cwd, ".motif", "NOTES.md");
|
|
8298
8442
|
try {
|
|
8299
8443
|
mkdirSync5(dirname5(path), { recursive: true, mode: 448 });
|
|
8300
|
-
const existing =
|
|
8301
|
-
const
|
|
8302
|
-
appendFileSync2(path, `${
|
|
8444
|
+
const existing = existsSync7(path) ? readFileSync6(path, "utf8") : "";
|
|
8445
|
+
const sep3 = existing === "" || existing.endsWith("\n") ? "" : "\n";
|
|
8446
|
+
appendFileSync2(path, `${sep3}- ${note}
|
|
8303
8447
|
`, "utf8");
|
|
8304
8448
|
this.projectNotes = readFileSync6(path, "utf8");
|
|
8305
8449
|
this.screen.append({ kind: "notice", level: "info", text: `noted in ${path}; the next task reads it` });
|
|
@@ -8322,7 +8466,7 @@ ${output.slice(0, 8e3)}
|
|
|
8322
8466
|
this.tasks.push(task);
|
|
8323
8467
|
const transport = (this.opts.makeTransport ?? defaultTransport)(this.settings, this.apiKey);
|
|
8324
8468
|
const runId = new Date(this.now()).toISOString().replace(/[:.]/g, "-") + `-${String(this.totals.tasks + 1).padStart(2, "0")}`;
|
|
8325
|
-
const journalPath =
|
|
8469
|
+
const journalPath = join7(this.opts.journalDir, `${runId}.jsonl`);
|
|
8326
8470
|
const scope = { scopeId: "root", scopeKind: "root" };
|
|
8327
8471
|
const journal = new Journal(
|
|
8328
8472
|
journalPath,
|
|
@@ -8681,8 +8825,8 @@ ${output.slice(0, 8e3)}
|
|
|
8681
8825
|
},
|
|
8682
8826
|
compact: (focus) => this.compactNow(focus),
|
|
8683
8827
|
notes: () => {
|
|
8684
|
-
const path = this.opts.notesPath ??
|
|
8685
|
-
if (!
|
|
8828
|
+
const path = this.opts.notesPath ?? join7(this.settings.cwd, ".motif", "NOTES.md");
|
|
8829
|
+
if (!existsSync7(path)) return [`no notes yet; # <text> at the prompt writes ${path}`];
|
|
8686
8830
|
return [path, "", ...readFileSync6(path, "utf8").replace(/\s+$/, "").split("\n")];
|
|
8687
8831
|
},
|
|
8688
8832
|
hooks: () => this.opts.hookLines ?? ["no hooks"],
|
|
@@ -8744,8 +8888,8 @@ ${output.slice(0, 8e3)}
|
|
|
8744
8888
|
const lines = rows.map(([k, v, from]) => `${k.padEnd(16)} ${v.padEnd(40)} ${from}`);
|
|
8745
8889
|
lines.push("");
|
|
8746
8890
|
if (info) {
|
|
8747
|
-
lines.push(`user file ${info.userPath}${
|
|
8748
|
-
const projectState = !
|
|
8891
|
+
lines.push(`user file ${info.userPath}${existsSync7(info.userPath) ? "" : " (absent)"}`);
|
|
8892
|
+
const projectState = !existsSync7(info.projectPath) ? " (absent)" : info.projectApplied ? " (applied)" : " (present, not trusted \u2014 run `motif trust`)";
|
|
8749
8893
|
lines.push(`project file ${info.projectPath}${projectState}`);
|
|
8750
8894
|
}
|
|
8751
8895
|
lines.push("flags and MOTIF_* in the environment or .env outrank both files; /model and the rest save to the user file");
|
|
@@ -8800,7 +8944,7 @@ ${output.slice(0, 8e3)}
|
|
|
8800
8944
|
file = picked.path;
|
|
8801
8945
|
}
|
|
8802
8946
|
const path = resolve5(this.settings.cwd, file);
|
|
8803
|
-
if (!
|
|
8947
|
+
if (!existsSync7(path)) throw new Error(`${path} does not exist`);
|
|
8804
8948
|
const state = loadResume(path);
|
|
8805
8949
|
if (state.corruption !== void 0) throw new Error(`this journal is corrupt (${state.corruption})`);
|
|
8806
8950
|
if (!state.checkpoint) throw new Error("no checkpoint was written in that session; there is nothing to continue");
|
|
@@ -8822,7 +8966,7 @@ ${output.slice(0, 8e3)}
|
|
|
8822
8966
|
}
|
|
8823
8967
|
setCwd(path) {
|
|
8824
8968
|
const target = resolve5(this.settings.cwd, path);
|
|
8825
|
-
if (!
|
|
8969
|
+
if (!existsSync7(target) || !statSync3(target).isDirectory()) return [`not a directory: ${target}`];
|
|
8826
8970
|
if (this.active) return ["a task is running; wait for it or interrupt it first"];
|
|
8827
8971
|
this.executor.close();
|
|
8828
8972
|
this.settings.cwd = target;
|
|
@@ -8839,7 +8983,7 @@ Input from the person:
|
|
|
8839
8983
|
${input}`;
|
|
8840
8984
|
}
|
|
8841
8985
|
function readHistory(path) {
|
|
8842
|
-
if (!
|
|
8986
|
+
if (!existsSync7(path)) return [];
|
|
8843
8987
|
const entries = [];
|
|
8844
8988
|
for (const line2 of readFileSync6(path, "utf8").split("\n")) {
|
|
8845
8989
|
if (!line2.trim()) continue;
|
|
@@ -8867,11 +9011,11 @@ function defaultTransport(settings, apiKey) {
|
|
|
8867
9011
|
}
|
|
8868
9012
|
|
|
8869
9013
|
// packages/cli/src/plugins.ts
|
|
8870
|
-
import { existsSync as
|
|
8871
|
-
import { join as
|
|
9014
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7, readdirSync as readdirSync3, statSync as statSync4 } from "node:fs";
|
|
9015
|
+
import { join as join8 } from "node:path";
|
|
8872
9016
|
function readManifest(dir) {
|
|
8873
|
-
const file =
|
|
8874
|
-
if (!
|
|
9017
|
+
const file = join8(dir, "plugin.json");
|
|
9018
|
+
if (!existsSync8(file)) return null;
|
|
8875
9019
|
const raw = JSON.parse(readFileSync7(file, "utf8"));
|
|
8876
9020
|
const name = typeof raw["name"] === "string" && raw["name"] !== "" ? raw["name"] : null;
|
|
8877
9021
|
if (!name) throw new Error(`${file}: plugin.json needs a name`);
|
|
@@ -8883,10 +9027,10 @@ function readManifest(dir) {
|
|
|
8883
9027
|
}
|
|
8884
9028
|
function loadPluginsFrom(root, source) {
|
|
8885
9029
|
const out = { plugins: [], skills: [], agents: [], problems: [] };
|
|
8886
|
-
const dir =
|
|
8887
|
-
if (!
|
|
9030
|
+
const dir = join8(root, "plugins");
|
|
9031
|
+
if (!existsSync8(dir)) return out;
|
|
8888
9032
|
for (const name of readdirSync3(dir).sort()) {
|
|
8889
|
-
const path =
|
|
9033
|
+
const path = join8(dir, name);
|
|
8890
9034
|
let st;
|
|
8891
9035
|
try {
|
|
8892
9036
|
st = statSync4(path);
|
|
@@ -8903,11 +9047,11 @@ function loadPluginsFrom(root, source) {
|
|
|
8903
9047
|
}
|
|
8904
9048
|
if (!manifest) continue;
|
|
8905
9049
|
const info = { ...manifest, path, source, skills: [], agents: [] };
|
|
8906
|
-
const skillsDir =
|
|
8907
|
-
if (
|
|
9050
|
+
const skillsDir = join8(path, "skills");
|
|
9051
|
+
if (existsSync8(skillsDir)) {
|
|
8908
9052
|
for (const s of readdirSync3(skillsDir).sort()) {
|
|
8909
|
-
const file =
|
|
8910
|
-
if (!
|
|
9053
|
+
const file = join8(skillsDir, s, "SKILL.md");
|
|
9054
|
+
if (!existsSync8(file)) continue;
|
|
8911
9055
|
try {
|
|
8912
9056
|
const skill = parseSkill(readFileSync7(file, "utf8"), source);
|
|
8913
9057
|
out.skills.push(skill);
|
|
@@ -8917,11 +9061,11 @@ function loadPluginsFrom(root, source) {
|
|
|
8917
9061
|
}
|
|
8918
9062
|
}
|
|
8919
9063
|
}
|
|
8920
|
-
const agentsDir =
|
|
8921
|
-
if (
|
|
9064
|
+
const agentsDir = join8(path, "agents");
|
|
9065
|
+
if (existsSync8(agentsDir)) {
|
|
8922
9066
|
for (const a of readdirSync3(agentsDir).sort()) {
|
|
8923
9067
|
if (!a.endsWith(".md")) continue;
|
|
8924
|
-
const file =
|
|
9068
|
+
const file = join8(agentsDir, a);
|
|
8925
9069
|
try {
|
|
8926
9070
|
const agent = parseAgent(readFileSync7(file, "utf8"), source);
|
|
8927
9071
|
out.agents.push(agent);
|
|
@@ -8936,8 +9080,8 @@ function loadPluginsFrom(root, source) {
|
|
|
8936
9080
|
return out;
|
|
8937
9081
|
}
|
|
8938
9082
|
function loadPlugins(opts) {
|
|
8939
|
-
const user = loadPluginsFrom(
|
|
8940
|
-
const project = loadPluginsFrom(
|
|
9083
|
+
const user = loadPluginsFrom(join8(opts.home, ".motif"), "user");
|
|
9084
|
+
const project = loadPluginsFrom(join8(opts.cwd, ".motif"), "project");
|
|
8941
9085
|
return {
|
|
8942
9086
|
plugins: [...user.plugins, ...project.plugins],
|
|
8943
9087
|
skills: [...user.skills, ...project.skills],
|
|
@@ -8955,9 +9099,9 @@ function describePlugins(loaded) {
|
|
|
8955
9099
|
}
|
|
8956
9100
|
|
|
8957
9101
|
// packages/cli/src/settings.ts
|
|
8958
|
-
import { existsSync as
|
|
9102
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "node:fs";
|
|
8959
9103
|
import { homedir as homedir4 } from "node:os";
|
|
8960
|
-
import { dirname as dirname6, join as
|
|
9104
|
+
import { dirname as dirname6, join as join9 } from "node:path";
|
|
8961
9105
|
var KEYS2 = [
|
|
8962
9106
|
"model",
|
|
8963
9107
|
"endpoint",
|
|
@@ -8971,10 +9115,10 @@ var KEYS2 = [
|
|
|
8971
9115
|
"permissions"
|
|
8972
9116
|
];
|
|
8973
9117
|
function userSettingsPath(home = homedir4()) {
|
|
8974
|
-
return
|
|
9118
|
+
return join9(home, ".motif", "settings.json");
|
|
8975
9119
|
}
|
|
8976
9120
|
function projectSettingsPath(cwd) {
|
|
8977
|
-
return
|
|
9121
|
+
return join9(cwd, ".motif", "settings.json");
|
|
8978
9122
|
}
|
|
8979
9123
|
function parseSettings(text) {
|
|
8980
9124
|
const values = {};
|
|
@@ -9030,7 +9174,7 @@ function loadSettings(opts) {
|
|
|
9030
9174
|
const sources = {};
|
|
9031
9175
|
let projectApplied = false;
|
|
9032
9176
|
const apply = (path, source, gate) => {
|
|
9033
|
-
if (!
|
|
9177
|
+
if (!existsSync9(path)) return false;
|
|
9034
9178
|
const content = readFileSync8(path, "utf8");
|
|
9035
9179
|
if (gate && !gate(content)) return false;
|
|
9036
9180
|
const { values: found, problems } = parseSettings(content);
|
|
@@ -9050,7 +9194,7 @@ function loadSettings(opts) {
|
|
|
9050
9194
|
function saveUserSetting(key, value, home = homedir4()) {
|
|
9051
9195
|
const path = userSettingsPath(home);
|
|
9052
9196
|
let current = {};
|
|
9053
|
-
if (
|
|
9197
|
+
if (existsSync9(path)) {
|
|
9054
9198
|
try {
|
|
9055
9199
|
const parsed = JSON.parse(readFileSync8(path, "utf8"));
|
|
9056
9200
|
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) current = parsed;
|
|
@@ -9065,7 +9209,7 @@ function saveUserSetting(key, value, home = homedir4()) {
|
|
|
9065
9209
|
}
|
|
9066
9210
|
|
|
9067
9211
|
// packages/cli/src/main.ts
|
|
9068
|
-
var VERSION = "0.
|
|
9212
|
+
var VERSION = "0.3.0";
|
|
9069
9213
|
var SHORT_FLAGS = { p: "print", c: "continue", i: "interactive", v: "verbose", h: "help" };
|
|
9070
9214
|
var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
9071
9215
|
"print",
|
|
@@ -9147,13 +9291,13 @@ function loadSkills(cwd) {
|
|
|
9147
9291
|
reg.registerAll(BUILTIN_SKILLS);
|
|
9148
9292
|
reg.registerAll(plugins(cwd).skills);
|
|
9149
9293
|
for (const [dir, source] of [
|
|
9150
|
-
[
|
|
9151
|
-
[
|
|
9294
|
+
[join10(homedir5(), CONFIG_DIR, "skills"), "user"],
|
|
9295
|
+
[join10(cwd, CONFIG_DIR, "skills"), "project"]
|
|
9152
9296
|
]) {
|
|
9153
|
-
if (!
|
|
9297
|
+
if (!existsSync10(dir)) continue;
|
|
9154
9298
|
for (const name of readdirSync4(dir)) {
|
|
9155
|
-
const file =
|
|
9156
|
-
if (!
|
|
9299
|
+
const file = join10(dir, name, "SKILL.md");
|
|
9300
|
+
if (!existsSync10(file)) continue;
|
|
9157
9301
|
try {
|
|
9158
9302
|
reg.register(parseSkill(readFileSync9(file, "utf8"), source));
|
|
9159
9303
|
} catch (err) {
|
|
@@ -9165,8 +9309,8 @@ function loadSkills(cwd) {
|
|
|
9165
9309
|
return reg;
|
|
9166
9310
|
}
|
|
9167
9311
|
function loadHooks(cwd, opts) {
|
|
9168
|
-
const file =
|
|
9169
|
-
if (!
|
|
9312
|
+
const file = join10(cwd, CONFIG_DIR, "settings.json");
|
|
9313
|
+
if (!existsSync10(file)) return DEFAULT_HOOKS;
|
|
9170
9314
|
const content = readFileSync9(file, "utf8");
|
|
9171
9315
|
let parsed;
|
|
9172
9316
|
try {
|
|
@@ -9204,13 +9348,13 @@ function loadAgents(cwd) {
|
|
|
9204
9348
|
reg.registerAll(BUILTIN_AGENTS);
|
|
9205
9349
|
reg.registerAll(plugins(cwd).agents);
|
|
9206
9350
|
for (const [dir, source] of [
|
|
9207
|
-
[
|
|
9208
|
-
[
|
|
9351
|
+
[join10(homedir5(), CONFIG_DIR, "agents"), "user"],
|
|
9352
|
+
[join10(cwd, CONFIG_DIR, "agents"), "project"]
|
|
9209
9353
|
]) {
|
|
9210
|
-
if (!
|
|
9354
|
+
if (!existsSync10(dir)) continue;
|
|
9211
9355
|
for (const name of readdirSync4(dir)) {
|
|
9212
9356
|
if (!name.endsWith(".md")) continue;
|
|
9213
|
-
const file =
|
|
9357
|
+
const file = join10(dir, name);
|
|
9214
9358
|
try {
|
|
9215
9359
|
reg.register(parseAgent(readFileSync9(file, "utf8"), source));
|
|
9216
9360
|
} catch (err) {
|
|
@@ -9234,8 +9378,8 @@ function describeHooks(cwd, hooks) {
|
|
|
9234
9378
|
for (const d of defs ?? []) lines.push(`${event.padEnd(12)} ${d.matcher ? `[${d.matcher}] ` : ""}${d.command}${d.blocking ? " (blocking)" : ""}`);
|
|
9235
9379
|
}
|
|
9236
9380
|
if (lines.length === 0) lines.push("no hooks configured");
|
|
9237
|
-
const file =
|
|
9238
|
-
if (
|
|
9381
|
+
const file = join10(cwd, CONFIG_DIR, "settings.json");
|
|
9382
|
+
if (existsSync10(file)) {
|
|
9239
9383
|
const content = readFileSync9(file, "utf8");
|
|
9240
9384
|
const trusted = checkTrust(loadTrustStore(), cwd, content).trusted;
|
|
9241
9385
|
lines.push("", `${file}: ${trusted ? "trusted" : "present but not trusted \u2014 its hooks and settings are not applied; run motif trust"}`);
|
|
@@ -9245,9 +9389,9 @@ function describeHooks(cwd, hooks) {
|
|
|
9245
9389
|
return lines;
|
|
9246
9390
|
}
|
|
9247
9391
|
function loadProjectNotes(cwd) {
|
|
9248
|
-
for (const name of ["AGENTS.md", "CLAUDE.md",
|
|
9249
|
-
const file =
|
|
9250
|
-
if (
|
|
9392
|
+
for (const name of ["AGENTS.md", "CLAUDE.md", join10(CONFIG_DIR, "NOTES.md")]) {
|
|
9393
|
+
const file = join10(cwd, name);
|
|
9394
|
+
if (existsSync10(file)) return readFileSync9(file, "utf8");
|
|
9251
9395
|
}
|
|
9252
9396
|
return void 0;
|
|
9253
9397
|
}
|
|
@@ -9487,8 +9631,8 @@ async function main() {
|
|
|
9487
9631
|
return 0;
|
|
9488
9632
|
}
|
|
9489
9633
|
case "trust": {
|
|
9490
|
-
const file =
|
|
9491
|
-
if (!
|
|
9634
|
+
const file = join10(cwd, CONFIG_DIR, "settings.json");
|
|
9635
|
+
if (!existsSync10(file)) {
|
|
9492
9636
|
process.stderr.write(`${file} does not exist; there is nothing to approve
|
|
9493
9637
|
`);
|
|
9494
9638
|
return 2;
|
|
@@ -9548,9 +9692,9 @@ ${"".padEnd(12)} tools: ${tools}
|
|
|
9548
9692
|
for (const [k, v, from] of rows) process.stdout.write(`${k.padEnd(16)} ${v.padEnd(40)} ${from}
|
|
9549
9693
|
`);
|
|
9550
9694
|
process.stdout.write(`
|
|
9551
|
-
user file ${stored.userPath}${
|
|
9695
|
+
user file ${stored.userPath}${existsSync10(stored.userPath) ? "" : " (absent)"}
|
|
9552
9696
|
`);
|
|
9553
|
-
const projectState = !
|
|
9697
|
+
const projectState = !existsSync10(stored.projectPath) ? " (absent)" : stored.projectApplied ? " (applied)" : " (present, not trusted \u2014 run `motif trust`)";
|
|
9554
9698
|
process.stdout.write(`project file ${stored.projectPath}${projectState}
|
|
9555
9699
|
`);
|
|
9556
9700
|
return 0;
|
|
@@ -9566,7 +9710,7 @@ user file ${stored.userPath}${existsSync9(stored.userPath) ? "" : " (absent)
|
|
|
9566
9710
|
return worstState(checks) === "fail" ? 1 : 0;
|
|
9567
9711
|
}
|
|
9568
9712
|
case "sessions": {
|
|
9569
|
-
const dir =
|
|
9713
|
+
const dir = join10(cwd, CONFIG_DIR, "sessions");
|
|
9570
9714
|
const sessions = listSessions(dir);
|
|
9571
9715
|
if (sessions.length === 0) {
|
|
9572
9716
|
process.stdout.write(`no sessions in ${dir}
|
|
@@ -9584,7 +9728,7 @@ user file ${stored.userPath}${existsSync9(stored.userPath) ? "" : " (absent)
|
|
|
9584
9728
|
return 0;
|
|
9585
9729
|
}
|
|
9586
9730
|
case "distil": {
|
|
9587
|
-
const dir = args.rest[0] ??
|
|
9731
|
+
const dir = args.rest[0] ?? join10(cwd, CONFIG_DIR, "sessions");
|
|
9588
9732
|
const format = flagEnum(args.flags, "format", DISTIL_FORMATS, "trajectory-jsonl");
|
|
9589
9733
|
const filter = flagEnum(args.flags, "filter", DISTIL_FILTERS, "grader-passed");
|
|
9590
9734
|
const includeChildren = args.flags["include-children"] === true;
|
|
@@ -9606,7 +9750,7 @@ user file ${stored.userPath}${existsSync9(stored.userPath) ? "" : " (absent)
|
|
|
9606
9750
|
return 0;
|
|
9607
9751
|
}
|
|
9608
9752
|
case "metrics": {
|
|
9609
|
-
const dir = args.rest[0] ??
|
|
9753
|
+
const dir = args.rest[0] ?? join10(cwd, CONFIG_DIR, "sessions");
|
|
9610
9754
|
for (const s of listSessions(dir)) {
|
|
9611
9755
|
const parsed = parseJournal(readFile(s.path, "utf8"));
|
|
9612
9756
|
for (const t of toTrajectories(parsed)) {
|
|
@@ -9712,22 +9856,23 @@ user file ${stored.userPath}${existsSync9(stored.userPath) ? "" : " (absent)
|
|
|
9712
9856
|
},
|
|
9713
9857
|
settingsInfo: stored,
|
|
9714
9858
|
persist: (key, value) => saveUserSetting(key, value),
|
|
9715
|
-
historyPath:
|
|
9859
|
+
historyPath: join10(cwd, CONFIG_DIR, "history.jsonl"),
|
|
9716
9860
|
pluginLines: describePlugins(plugins(cwd)),
|
|
9717
9861
|
hookLines: describeHooks(cwd, hooks),
|
|
9718
|
-
notesPath:
|
|
9719
|
-
...args.flags["continue"] === true ? { continueFrom: listSessions(
|
|
9862
|
+
notesPath: join10(cwd, CONFIG_DIR, "NOTES.md"),
|
|
9863
|
+
...args.flags["continue"] === true ? { continueFrom: listSessions(join10(cwd, CONFIG_DIR, "sessions"))[0]?.path ?? "" } : {},
|
|
9720
9864
|
channelPolicy,
|
|
9721
9865
|
...apiKey !== void 0 ? { apiKey } : {},
|
|
9722
9866
|
...connection.sources.apiKey !== void 0 ? { apiKeySource: connection.sources.apiKey } : {},
|
|
9723
9867
|
requireKey: keyRequired(endpoint),
|
|
9724
9868
|
envPath: defaultEnvPath(),
|
|
9869
|
+
offerInstall: ranFromNpx(process.argv[1]) && !commandOnPath("motif"),
|
|
9725
9870
|
skills,
|
|
9726
9871
|
agents,
|
|
9727
9872
|
hooks,
|
|
9728
9873
|
...projectNotes !== void 0 ? { projectNotes } : {},
|
|
9729
9874
|
tools: activeTools,
|
|
9730
|
-
journalDir:
|
|
9875
|
+
journalDir: join10(cwd, CONFIG_DIR, "sessions"),
|
|
9731
9876
|
version: VERSION,
|
|
9732
9877
|
hero: args.flags["no-hero"] !== true,
|
|
9733
9878
|
...task ? { initialTask: task } : {}
|
|
@@ -9789,7 +9934,7 @@ user file ${stored.userPath}${existsSync9(stored.userPath) ? "" : " (absent)
|
|
|
9789
9934
|
const transport = new HttpTransport({ endpoint, model, ...sessionKey !== void 0 ? { apiKey: sessionKey } : {} });
|
|
9790
9935
|
const screen = new Screen({ showThinking, verbose: args.flags["verbose"] === true, cwd });
|
|
9791
9936
|
const runId = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
9792
|
-
const journalPath = flagStr(args.flags, "journal", "") ||
|
|
9937
|
+
const journalPath = flagStr(args.flags, "journal", "") || join10(cwd, CONFIG_DIR, "sessions", `${runId}.jsonl`);
|
|
9793
9938
|
const journal = new Journal(
|
|
9794
9939
|
journalPath,
|
|
9795
9940
|
newHeader({
|