octwin-cli 0.6.1 → 0.7.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.
- package/CHANGELOG.md +553 -520
- package/dist/index.js +129 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -620,6 +620,54 @@ async function notifyIfKbStale(flags) {
|
|
|
620
620
|
}
|
|
621
621
|
catch { /* a KB check must never break the CLI */ }
|
|
622
622
|
}
|
|
623
|
+
/**
|
|
624
|
+
* Nudge (to stderr) when the platform has memos this workspace has not read — a reply to
|
|
625
|
+
* a `octwin feedback` report, or a notice we published (new capability, deprecation,
|
|
626
|
+
* breaking change).
|
|
627
|
+
*
|
|
628
|
+
* The sibling of `notifyIfKbStale`, and the same three decisions apply for the same
|
|
629
|
+
* reasons: it rides commands that ALREADY hit the platform so it costs one tiny GET on
|
|
630
|
+
* top of networked work; it never throws, because observing must not break a command;
|
|
631
|
+
* and it is **deliberately not TTY-gated**, because the primary reader of this CLI is an
|
|
632
|
+
* authoring agent whose output is piped — the one reader that cannot notice an unread
|
|
633
|
+
* reply on its own would otherwise be the only one never told.
|
|
634
|
+
*
|
|
635
|
+
* WHY THIS EXISTS AT ALL. `octwin feedback` was one-way. Measured 2026-08-18: four
|
|
636
|
+
* reports sat unread for nine days, and one author hardcoded English across 16 flows to
|
|
637
|
+
* work around a bug that had been fixed two days earlier, because nothing could tell
|
|
638
|
+
* them. A nudge that does not name the command to run does not motivate an agent, so the
|
|
639
|
+
* line always ends in `octwin memos`.
|
|
640
|
+
*
|
|
641
|
+
* Unlike the KB check there is NO local state to compare: read state is per-tenant on the
|
|
642
|
+
* platform, so an agent on a fresh machine or in a fresh container still learns about an
|
|
643
|
+
* unread memo. That is the whole reason it is not a local marker file.
|
|
644
|
+
*/
|
|
645
|
+
async function notifyIfMemosWaiting(flags) {
|
|
646
|
+
try {
|
|
647
|
+
const t = resolveTargetOrNull(flags);
|
|
648
|
+
if (!t)
|
|
649
|
+
return;
|
|
650
|
+
const { status, json } = await apiGet(`${t.url}/api/self/p/memos?meta=1`, t);
|
|
651
|
+
// Silent on ANY failure — an older platform has no such route, and a nudge is never
|
|
652
|
+
// worth a diagnostic of its own. Unlike the KB poll there is no scope to explain: the
|
|
653
|
+
// route is gated on tenant access precisely so every token can answer it.
|
|
654
|
+
if (status !== 200 || !json || typeof json !== 'object')
|
|
655
|
+
return;
|
|
656
|
+
const unread = Number(json.unread ?? 0);
|
|
657
|
+
if (!Number.isFinite(unread) || unread <= 0)
|
|
658
|
+
return;
|
|
659
|
+
const actionable = Number(json.unread_actionable ?? 0);
|
|
660
|
+
const what = unread === 1 ? '1 memo' : `${unread} memos`;
|
|
661
|
+
const tail = actionable > 0 ? ` (${actionable} needing action)` : '';
|
|
662
|
+
console.error(`\n✉ ${what} from the platform${tail} — read them: octwin memos`);
|
|
663
|
+
if (actionable > 0) {
|
|
664
|
+
// Said separately, because the whole point of the severity axis is that an agent
|
|
665
|
+
// mid-build should stop and read rather than finish first.
|
|
666
|
+
console.error(' One or more may change what you are building — read before continuing.');
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
catch { /* a memo check must never break the CLI */ }
|
|
670
|
+
}
|
|
623
671
|
/** Which commands already made a platform call, so the trailing KB-drift poll
|
|
624
672
|
* rides on existing network work (never on offline `validate` / `init`;
|
|
625
673
|
* `platform-kb` refreshes the reference itself, so it needs no nudge). */
|
|
@@ -632,6 +680,9 @@ function commandTouchesPlatform(command, flags) {
|
|
|
632
680
|
case 'chat':
|
|
633
681
|
case 'media':
|
|
634
682
|
case 'pull':
|
|
683
|
+
// `memos` is networked but needs NO memo nudge — it just read them. It still gets the
|
|
684
|
+
// KB drift check, which is a different question.
|
|
685
|
+
case 'memos': return true;
|
|
635
686
|
case 'records':
|
|
636
687
|
case 'work':
|
|
637
688
|
case 'logs':
|
|
@@ -1908,11 +1959,11 @@ async function cmdRecordsWrite(flags) {
|
|
|
1908
1959
|
if (json?.has_xrm === false)
|
|
1909
1960
|
die('this pack declares no XRM entities');
|
|
1910
1961
|
const rec = json?.record ?? {};
|
|
1911
|
-
// 200 = the
|
|
1912
|
-
// "created" for a match would misreport what the
|
|
1962
|
+
// 200 = the entity's identity matched an existing row; 201 = genuinely new.
|
|
1963
|
+
// Saying "created" for a match would misreport what the save actually did.
|
|
1913
1964
|
console.log(status === 201
|
|
1914
1965
|
? `✓ Created #${rec.record_number ?? '?'} ${rec.id ?? ''}`
|
|
1915
|
-
: `✓ Matched an EXISTING record (
|
|
1966
|
+
: `✓ Matched an EXISTING record (its unique constraint identified it) — #${rec.record_number ?? '?'} ${rec.id ?? ''}`);
|
|
1916
1967
|
if (rec.id)
|
|
1917
1968
|
readBack(rec.id);
|
|
1918
1969
|
return;
|
|
@@ -2058,6 +2109,60 @@ async function cmdFeedback(flags) {
|
|
|
2058
2109
|
console.log(' ⓘ no local capability reference found, so the report carries no KB version.');
|
|
2059
2110
|
console.log(' Pull it before your next session: octwin platform-kb');
|
|
2060
2111
|
}
|
|
2112
|
+
console.log(' A reply arrives as a memo — this CLI will tell you when one is waiting.');
|
|
2113
|
+
}
|
|
2114
|
+
/**
|
|
2115
|
+
* `octwin memos [--json] [--all]` — read what the platform has told you.
|
|
2116
|
+
*
|
|
2117
|
+
* The other half of `octwin feedback`. Two kinds arrive here: a REPLY to a report you
|
|
2118
|
+
* sent, and a NOTICE we published to every author (a new capability, a deprecation, a
|
|
2119
|
+
* breaking change). Both are markdown, printed in full — this is a channel for reading,
|
|
2120
|
+
* not a list to page through, so there is no `show <id>`.
|
|
2121
|
+
*
|
|
2122
|
+
* Reading ACKS: the default prints unread memos and marks them read, so the nudge stops.
|
|
2123
|
+
* `--all` re-reads history and acks nothing, which is also the escape hatch if a piped
|
|
2124
|
+
* `--json` consumer crashed halfway through.
|
|
2125
|
+
*/
|
|
2126
|
+
async function cmdMemos(flags) {
|
|
2127
|
+
const t = resolveTarget(flags);
|
|
2128
|
+
const all = flags.all === true;
|
|
2129
|
+
const { status, json } = await apiGet(`${t.url}/api/self/p/memos${all ? '?all=1' : ''}`, t);
|
|
2130
|
+
if (status !== 200) {
|
|
2131
|
+
die(`could not read memos (HTTP ${status})${errDetail(json)}${authFailureDetail(status, t.url)}`);
|
|
2132
|
+
}
|
|
2133
|
+
const rows = (json?.rows ?? []);
|
|
2134
|
+
if (flags.json === true) {
|
|
2135
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
2136
|
+
return;
|
|
2137
|
+
}
|
|
2138
|
+
if (rows.length === 0) {
|
|
2139
|
+
console.log(all ? 'No memos yet.' : 'No unread memos. (History: octwin memos --all)');
|
|
2140
|
+
return;
|
|
2141
|
+
}
|
|
2142
|
+
const mark = (s) => s === 'breaking' ? '⛔ BREAKING' : s === 'action_required' ? '⚠ ACTION' : 'ⓘ';
|
|
2143
|
+
for (const m of rows) {
|
|
2144
|
+
const when = String(m.published_at ?? '').slice(0, 10);
|
|
2145
|
+
const about = m.pack_id ? ` · ${m.pack_id}` : '';
|
|
2146
|
+
const seen = m.read_at ? ' · (already read)' : '';
|
|
2147
|
+
console.log(`\n${'─'.repeat(72)}`);
|
|
2148
|
+
console.log(`${mark(m.severity)} ${m.title}`);
|
|
2149
|
+
console.log(`${m.kind === 'reply' ? 'reply to your report' : 'platform notice'} · ${when}${about}${seen}`);
|
|
2150
|
+
console.log(`${'─'.repeat(72)}\n`);
|
|
2151
|
+
console.log(m.body_md.trimEnd());
|
|
2152
|
+
}
|
|
2153
|
+
console.log();
|
|
2154
|
+
// Ack only what we just showed as unread, and only on the default path — `--all`
|
|
2155
|
+
// deliberately leaves read state alone so history can be re-read any number of times.
|
|
2156
|
+
if (!all) {
|
|
2157
|
+
const ids = rows.filter(m => !m.read_at).map(m => m.id);
|
|
2158
|
+
if (ids.length > 0) {
|
|
2159
|
+
const res = await apiSend('POST', `${t.url}/api/self/p/memos/read`, { ids }, t);
|
|
2160
|
+
// A failed ack is not a failed command — the author HAS read them. Say so rather
|
|
2161
|
+
// than dying after printing everything, and the nudge simply fires again.
|
|
2162
|
+
if (res.status !== 200)
|
|
2163
|
+
console.error(`ⓘ could not mark them read (HTTP ${res.status}) — they will be offered again.`);
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2061
2166
|
}
|
|
2062
2167
|
/** `octwin logs [conversationId] [--as <handle>] [--json]` — list conversations
|
|
2063
2168
|
* or show one's event timeline (full text + the renders each turn produced). */
|
|
@@ -2642,10 +2747,17 @@ async function cmdWorkWrite(flags) {
|
|
|
2642
2747
|
}
|
|
2643
2748
|
if (dryRun) {
|
|
2644
2749
|
console.log('Preview (nothing was committed):');
|
|
2750
|
+
// Stated in words BEFORE the dump. `to_stage: null` is already in the JSON below, but a null
|
|
2751
|
+
// among twenty keys is not an answer anyone reads — a pack author previewed exactly this,
|
|
2752
|
+
// shipped an action with no `to_stage`, and lost the `awaiting_customer` their whole
|
|
2753
|
+
// reply-capture branched on. Same reason the console preview now says it too.
|
|
2754
|
+
console.log(json?.to_stage
|
|
2755
|
+
? ` → moves the record to '${json.to_stage}'.`
|
|
2756
|
+
: ' → does NOT change the status (this action only records a decision).');
|
|
2645
2757
|
console.log(JSON.stringify(json, null, 2));
|
|
2646
2758
|
return;
|
|
2647
2759
|
}
|
|
2648
|
-
console.log(`✓ Applied '${action}'${json?.to_stage ? ` — record is now '${json.to_stage}'` : ''}.`);
|
|
2760
|
+
console.log(`✓ Applied '${action}'${json?.to_stage ? ` — record is now '${json.to_stage}'` : ' — status unchanged (the action declares no to_stage)'}.`);
|
|
2649
2761
|
// `relayed`/`notified` are the customer-facing half; silence here usually means
|
|
2650
2762
|
// the action had no message template, which is easy to mistake for a failure.
|
|
2651
2763
|
const reached = json?.relayed || json?.notified;
|
|
@@ -3751,6 +3863,7 @@ function help() {
|
|
|
3751
3863
|
octwin platform-kb [pull] [--if-stale|--check] [--dir .] [--url <url>] # no token needed
|
|
3752
3864
|
octwin test [--dir .] # = validate --remote (the full platform check)
|
|
3753
3865
|
octwin feedback [--dir .] # submit this pack's FEEDBACK.md to the platform team
|
|
3866
|
+
octwin memos [--all] [--json] # read the platform's replies + notices (a reply to your feedback lands here)
|
|
3754
3867
|
|
|
3755
3868
|
Writes — exercise the state your pack creates (each needs the matching :write scope):
|
|
3756
3869
|
octwin records create <entity> --set field=value … # also: patch <id> --entity <e>, stage <id> --to <s>, note <id> "…"
|
|
@@ -3980,6 +4093,13 @@ octwin projects rm <slug> [--yes]
|
|
|
3980
4093
|
agent loops that want to branch without parsing prose.`,
|
|
3981
4094
|
test: `octwin test [--dir .]
|
|
3982
4095
|
Alias for \`octwin validate --remote\` — the full platform check.`,
|
|
4096
|
+
memos: `octwin memos [--all] [--json]
|
|
4097
|
+
Read what the platform has told you: a REPLY to a report you sent with
|
|
4098
|
+
\`octwin feedback\`, or a NOTICE published to every author (a new capability,
|
|
4099
|
+
a deprecation, a breaking change). Bodies are printed in full.
|
|
4100
|
+
Reading marks them read, so the reminder stops. --all re-reads history and
|
|
4101
|
+
acks nothing. --json to branch on \`severity\`
|
|
4102
|
+
(info | action_required | breaking).`,
|
|
3983
4103
|
feedback: `octwin feedback [--dir .]
|
|
3984
4104
|
Submit this pack's FEEDBACK.md to the platform team.
|
|
3985
4105
|
The octwin-pack skill writes that file in its last step — findings grouped by
|
|
@@ -4070,6 +4190,9 @@ async function main() {
|
|
|
4070
4190
|
case 'feedback':
|
|
4071
4191
|
await cmdFeedback(flags);
|
|
4072
4192
|
break;
|
|
4193
|
+
case 'memos':
|
|
4194
|
+
await cmdMemos(flags);
|
|
4195
|
+
break;
|
|
4073
4196
|
case 'test':
|
|
4074
4197
|
await cmdValidate({ ...flags, remote: true });
|
|
4075
4198
|
break; // A6: `test` = the full remote validate, not a validate-clone
|
|
@@ -4091,6 +4214,8 @@ async function main() {
|
|
|
4091
4214
|
// offline paths); CLI-upgrade always.
|
|
4092
4215
|
if (commandTouchesPlatform(command, flags))
|
|
4093
4216
|
await notifyIfKbStale(flags);
|
|
4217
|
+
if (commandTouchesPlatform(command, flags))
|
|
4218
|
+
await notifyIfMemosWaiting(flags);
|
|
4094
4219
|
await notifyIfOutdated();
|
|
4095
4220
|
}
|
|
4096
4221
|
main().catch((err) => die(err?.message ?? String(err)));
|
package/package.json
CHANGED