@skhema/cli 0.4.4 → 0.4.6
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 +15 -0
- package/dist/commands/import.d.ts +4 -0
- package/dist/commands/import.d.ts.map +1 -0
- package/dist/commands/import.js +238 -0
- package/dist/lib/api/import-transfer.d.ts +22 -0
- package/dist/lib/api/import-transfer.d.ts.map +1 -0
- package/dist/lib/api/import-transfer.js +89 -0
- package/dist/program.d.ts.map +1 -1
- package/dist/program.js +2 -0
- package/package.json +3 -3
- package/skills/.manifest.json +8 -10
- package/skills/skhema-calibrate/SKILL.md +90 -0
- package/skills/skhema-calibrate/references/assumptions.md +77 -0
- package/skills/skhema-calibrate/references/coherence.md +95 -0
- package/skills/skhema-calibrate/references/freshness.md +81 -0
- package/skills/skhema-calibrate/references/language.md +74 -0
- package/skills/skhema-communicate/SKILL.md +94 -0
- package/skills/skhema-communicate/references/audience-adaptation.md +103 -0
- package/skills/skhema-communicate/references/board-update.md +93 -0
- package/skills/skhema-communicate/references/decision-brief.md +93 -0
- package/skills/skhema-communicate/references/team-brief.md +91 -0
- package/skills/skhema-compose/SKILL.md +98 -0
- package/skills/skhema-compose/references/assemble.md +119 -0
- package/skills/skhema-compose/references/decompose.md +104 -0
- package/skills/skhema-compose/references/metrics-tree.md +101 -0
- package/skills/skhema-compose/references/options.md +109 -0
- package/skills/skhema-frame/SKILL.md +86 -0
- package/skills/skhema-frame/references/challenge.md +84 -0
- package/skills/skhema-frame/references/decision.md +92 -0
- package/skills/skhema-frame/references/outcome.md +82 -0
- package/skills/skhema-frame/references/policy.md +82 -0
- package/skills/skhema-frame/references/scope.md +79 -0
- package/skills/skhema-operate/SKILL.md +107 -0
- package/skills/skhema-operate/references/auth.md +110 -0
- package/skills/skhema-operate/references/author-elements.md +158 -0
- package/skills/skhema-operate/references/navigate.md +110 -0
- package/skills/skhema-operate/references/surfaces.md +67 -0
- package/skills/skhema-operate/references/validation-loop.md +84 -0
- package/skills/skhema-pressure-test/SKILL.md +107 -0
- package/skills/skhema-pressure-test/references/decision-grill.md +127 -0
- package/skills/skhema-pressure-test/references/full-grill.md +118 -0
- package/skills/skhema-pressure-test/references/pre-mortem.md +140 -0
- package/skills/skhema-challenge-framing/SKILL.md +0 -28
- package/skills/skhema-coherence-check/SKILL.md +0 -28
- package/skills/skhema-element-decomposition/SKILL.md +0 -28
- package/skills/skhema-element-writer/SKILL.md +0 -20
- package/skills/skhema-judgment-audit/SKILL.md +0 -28
- package/skills/skhema-semantic-sharpening/SKILL.md +0 -28
- package/skills/skhema-strategy-advisor/SKILL.md +0 -20
- package/skills/skhema-workspace-navigator/SKILL.md +0 -20
package/README.md
CHANGED
|
@@ -57,6 +57,21 @@ skhema api routes # every live route from the Ope
|
|
|
57
57
|
`--data` takes a JSON literal, `@file`, or `-` (stdin). `--query k=v` repeats. The
|
|
58
58
|
passthrough refuses any host other than the Skhema gateway.
|
|
59
59
|
|
|
60
|
+
### Strategy document imports
|
|
61
|
+
|
|
62
|
+
Import a local strategy document or public URL through the same reviewable
|
|
63
|
+
proposal used by the web and MCP surfaces:
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
skhema import start --file ./strategy.pdf --wait
|
|
67
|
+
skhema import get imp_... # inspect the mapped proposal
|
|
68
|
+
skhema import decide imp_... --file decisions.json
|
|
69
|
+
skhema import apply imp_... --workspace-name "Imported strategy" --yes
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Use `--json` for a stable machine envelope. Applying or discarding requires an
|
|
73
|
+
interactive confirmation, or an explicit `--yes` in scripts and CI.
|
|
74
|
+
|
|
60
75
|
## Credential lanes
|
|
61
76
|
|
|
62
77
|
The CLI accepts **two kinds** of credential and resolves them in a fixed
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"import.d.ts","sourceRoot":"","sources":["../../src/commands/import.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAkHnC,qEAAqE;AACrE,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA6L7D"}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import readline from 'readline';
|
|
3
|
+
import { resolveClient } from '../lib/api/client.js';
|
|
4
|
+
import { startFileImport, waitForImport } from '../lib/api/import-transfer.js';
|
|
5
|
+
import { parseJsonFile } from '../lib/api/payload.js';
|
|
6
|
+
import { runCommand, UsageError } from '../lib/api/run-command.js';
|
|
7
|
+
import { getGlobalOptions, log, logHeader, logTable } from '../lib/output.js';
|
|
8
|
+
function printSession(session) {
|
|
9
|
+
logHeader(session.proposalPayload?.summary.title ?? `Import ${session.id}`);
|
|
10
|
+
log(` Id: ${chalk.cyan(session.id)}`);
|
|
11
|
+
log(` Status: ${session.status}`);
|
|
12
|
+
const source = session.sourceFilename ?? session.sourceUrl;
|
|
13
|
+
if (source)
|
|
14
|
+
log(` Source: ${source}`);
|
|
15
|
+
if (session.progressStage) {
|
|
16
|
+
const count = session.progressCurrent !== null && session.progressTotal !== null
|
|
17
|
+
? ` ${session.progressCurrent}/${session.progressTotal}`
|
|
18
|
+
: '';
|
|
19
|
+
log(` Progress: ${session.progressStage}${count}`);
|
|
20
|
+
}
|
|
21
|
+
if (session.error)
|
|
22
|
+
log(` Error: ${session.error}`);
|
|
23
|
+
if (session.workspaceId)
|
|
24
|
+
log(` Workspace: ${session.workspaceId}`);
|
|
25
|
+
const proposal = session.proposalPayload;
|
|
26
|
+
if (!proposal)
|
|
27
|
+
return;
|
|
28
|
+
log('');
|
|
29
|
+
log(`${proposal.items.length} mapped item${proposal.items.length === 1 ? '' : 's'}, ` +
|
|
30
|
+
`${proposal.orphans.length} unmapped.`);
|
|
31
|
+
if (proposal.items.length > 0) {
|
|
32
|
+
logTable(['Id', 'Component', 'Type', 'Confidence', 'Content'], proposal.items.map((item) => [
|
|
33
|
+
item.id,
|
|
34
|
+
item.componentType,
|
|
35
|
+
item.elementType,
|
|
36
|
+
item.confidence,
|
|
37
|
+
item.proposed.content.length > 60
|
|
38
|
+
? `${item.proposed.content.slice(0, 57)}...`
|
|
39
|
+
: item.proposed.content,
|
|
40
|
+
]));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function organizationFor(explicit, credential) {
|
|
44
|
+
const organizationId = explicit ?? credential.organizationId;
|
|
45
|
+
if (credential.kind === 'oauth' && !organizationId) {
|
|
46
|
+
throw new UsageError('No organization is available for this OAuth session. Pass --organization <id>.');
|
|
47
|
+
}
|
|
48
|
+
return organizationId;
|
|
49
|
+
}
|
|
50
|
+
function interactiveTerminal() {
|
|
51
|
+
return (Boolean(process.stdin.isTTY && process.stdout.isTTY) &&
|
|
52
|
+
!getGlobalOptions().json);
|
|
53
|
+
}
|
|
54
|
+
async function confirm(question) {
|
|
55
|
+
const rl = readline.createInterface({
|
|
56
|
+
input: process.stdin,
|
|
57
|
+
output: process.stdout,
|
|
58
|
+
});
|
|
59
|
+
const answer = await new Promise((resolve) => {
|
|
60
|
+
rl.question(`${question} [y/N]: `, (value) => {
|
|
61
|
+
rl.close();
|
|
62
|
+
resolve(value.trim().toLowerCase());
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
return answer === 'y' || answer === 'yes';
|
|
66
|
+
}
|
|
67
|
+
async function authorizeMutation(question, yes) {
|
|
68
|
+
if (yes)
|
|
69
|
+
return true;
|
|
70
|
+
if (!interactiveTerminal()) {
|
|
71
|
+
throw new UsageError('This command requires --yes when run non-interactively.');
|
|
72
|
+
}
|
|
73
|
+
return confirm(question);
|
|
74
|
+
}
|
|
75
|
+
function decisionsFrom(source) {
|
|
76
|
+
const parsed = parseJsonFile(source);
|
|
77
|
+
const candidate = parsed.decisions &&
|
|
78
|
+
typeof parsed.decisions === 'object' &&
|
|
79
|
+
!Array.isArray(parsed.decisions)
|
|
80
|
+
? parsed.decisions
|
|
81
|
+
: parsed;
|
|
82
|
+
return candidate;
|
|
83
|
+
}
|
|
84
|
+
async function maybeWait(client, response, wait) {
|
|
85
|
+
if (!wait)
|
|
86
|
+
return response;
|
|
87
|
+
return waitForImport({ client, sessionId: response.session.id });
|
|
88
|
+
}
|
|
89
|
+
/** Register the complete proposal-first import session lifecycle. */
|
|
90
|
+
export function registerImportCommands(program) {
|
|
91
|
+
const imports = program
|
|
92
|
+
.command('import')
|
|
93
|
+
.description('Import a strategy document through a reviewable proposal');
|
|
94
|
+
imports
|
|
95
|
+
.command('start')
|
|
96
|
+
.description('Start an import from a local file or public URL')
|
|
97
|
+
.option('--file <path>', 'Local PDF, DOCX, Markdown, text, CSV, XLSX, or JSON')
|
|
98
|
+
.option('--url <url>', 'Public http(s) URL to import')
|
|
99
|
+
.option('--organization <id>', 'Organization id (OAuth user lane)')
|
|
100
|
+
.option('--wait', 'Poll until the proposal is ready or analysis fails')
|
|
101
|
+
.action(async (options) => {
|
|
102
|
+
await runCommand('import start', async () => {
|
|
103
|
+
if (Boolean(options.file) === Boolean(options.url)) {
|
|
104
|
+
throw new UsageError('Pass exactly one of --file <path> or --url <url>.');
|
|
105
|
+
}
|
|
106
|
+
if (options.url && !/^https?:\/\//i.test(options.url)) {
|
|
107
|
+
throw new UsageError('--url must be an http(s) URL.');
|
|
108
|
+
}
|
|
109
|
+
const { client, credential } = await resolveClient();
|
|
110
|
+
const organizationId = organizationFor(options.organization, credential);
|
|
111
|
+
let response = options.file
|
|
112
|
+
? await startFileImport({
|
|
113
|
+
client,
|
|
114
|
+
filePath: options.file,
|
|
115
|
+
organizationId,
|
|
116
|
+
})
|
|
117
|
+
: await client.imports.create({
|
|
118
|
+
sourceType: 'url',
|
|
119
|
+
...(organizationId ? { organizationId } : {}),
|
|
120
|
+
url: options.url,
|
|
121
|
+
});
|
|
122
|
+
response = await maybeWait(client, response, options.wait);
|
|
123
|
+
if (!getGlobalOptions().json)
|
|
124
|
+
printSession(response.session);
|
|
125
|
+
return response;
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
imports
|
|
129
|
+
.command('list')
|
|
130
|
+
.description('List recent import sessions')
|
|
131
|
+
.option('--organization <id>', 'Organization id (OAuth user lane)')
|
|
132
|
+
.action(async (options) => {
|
|
133
|
+
await runCommand('import list', async () => {
|
|
134
|
+
const { client, credential } = await resolveClient();
|
|
135
|
+
const organizationId = organizationFor(options.organization, credential);
|
|
136
|
+
const response = await client.imports.list(organizationId);
|
|
137
|
+
if (!getGlobalOptions().json) {
|
|
138
|
+
logHeader(`Imports (${response.sessions.length})`);
|
|
139
|
+
if (response.sessions.length === 0)
|
|
140
|
+
log(chalk.dim(' No imports.'));
|
|
141
|
+
else
|
|
142
|
+
logTable(['Status', 'Source', 'Updated', 'Id'], response.sessions.map((session) => [
|
|
143
|
+
session.status,
|
|
144
|
+
session.sourceFilename ?? session.sourceUrl ?? '—',
|
|
145
|
+
session.updatedAt,
|
|
146
|
+
session.id,
|
|
147
|
+
]));
|
|
148
|
+
}
|
|
149
|
+
return response;
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
imports
|
|
153
|
+
.command('get <session-id>')
|
|
154
|
+
.description('Inspect an import session and its proposal')
|
|
155
|
+
.option('--wait', 'Poll until the proposal is ready or analysis fails')
|
|
156
|
+
.action(async (sessionId, options) => {
|
|
157
|
+
await runCommand('import get', async () => {
|
|
158
|
+
const { client } = await resolveClient();
|
|
159
|
+
let response = await client.imports.get(sessionId);
|
|
160
|
+
response = await maybeWait(client, response, options.wait);
|
|
161
|
+
if (!getGlobalOptions().json)
|
|
162
|
+
printSession(response.session);
|
|
163
|
+
return response;
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
imports
|
|
167
|
+
.command('decide <session-id>')
|
|
168
|
+
.description('Replace proposal decisions from a JSON object')
|
|
169
|
+
.requiredOption('--file <path|->', 'Decisions JSON file, or - for stdin')
|
|
170
|
+
.action(async (sessionId, options) => {
|
|
171
|
+
await runCommand('import decide', async () => {
|
|
172
|
+
const { client } = await resolveClient();
|
|
173
|
+
const response = await client.imports.updateDecisions(sessionId, decisionsFrom(options.file));
|
|
174
|
+
if (!getGlobalOptions().json) {
|
|
175
|
+
log(chalk.green('Import decisions saved.'));
|
|
176
|
+
printSession(response.session);
|
|
177
|
+
}
|
|
178
|
+
return response;
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
imports
|
|
182
|
+
.command('apply <session-id>')
|
|
183
|
+
.description('Apply a reviewed proposal into a new workspace')
|
|
184
|
+
.requiredOption('--workspace-name <name>', 'Name for the new workspace')
|
|
185
|
+
.option('--visibility <visibility>', 'global | restricted', 'global')
|
|
186
|
+
.option('--yes', 'Confirm without prompting (required when non-interactive)')
|
|
187
|
+
.action(async (sessionId, options) => {
|
|
188
|
+
await runCommand('import apply', async () => {
|
|
189
|
+
if (options.visibility !== 'global' &&
|
|
190
|
+
options.visibility !== 'restricted') {
|
|
191
|
+
throw new UsageError('--visibility must be global or restricted.');
|
|
192
|
+
}
|
|
193
|
+
const authorized = await authorizeMutation(`Apply import ${sessionId} and create workspace "${options.workspaceName}"?`, options.yes ?? false);
|
|
194
|
+
if (!authorized)
|
|
195
|
+
return { cancelled: true, sessionId };
|
|
196
|
+
const { client } = await resolveClient();
|
|
197
|
+
const response = await client.imports.apply(sessionId, {
|
|
198
|
+
workspaceName: options.workspaceName,
|
|
199
|
+
visibility: options.visibility,
|
|
200
|
+
});
|
|
201
|
+
if (!getGlobalOptions().json) {
|
|
202
|
+
log(chalk.green('Import applied.'));
|
|
203
|
+
printSession(response.session);
|
|
204
|
+
}
|
|
205
|
+
return response;
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
imports
|
|
209
|
+
.command('discard <session-id>')
|
|
210
|
+
.description('Discard an eligible import session')
|
|
211
|
+
.option('--yes', 'Confirm without prompting (required when non-interactive)')
|
|
212
|
+
.action(async (sessionId, options) => {
|
|
213
|
+
await runCommand('import discard', async () => {
|
|
214
|
+
const authorized = await authorizeMutation(`Discard import ${sessionId}?`, options.yes ?? false);
|
|
215
|
+
if (!authorized)
|
|
216
|
+
return { cancelled: true, sessionId };
|
|
217
|
+
const { client } = await resolveClient();
|
|
218
|
+
const response = await client.imports.discard(sessionId);
|
|
219
|
+
if (!getGlobalOptions().json)
|
|
220
|
+
log(chalk.green('Import discarded.'));
|
|
221
|
+
return response;
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
imports
|
|
225
|
+
.command('retry <session-id>')
|
|
226
|
+
.description('Retry a failed or stale import analysis')
|
|
227
|
+
.option('--wait', 'Poll until the proposal is ready or analysis fails')
|
|
228
|
+
.action(async (sessionId, options) => {
|
|
229
|
+
await runCommand('import retry', async () => {
|
|
230
|
+
const { client } = await resolveClient();
|
|
231
|
+
let response = await client.imports.retry(sessionId);
|
|
232
|
+
response = await maybeWait(client, response, options.wait);
|
|
233
|
+
if (!getGlobalOptions().json)
|
|
234
|
+
printSession(response.session);
|
|
235
|
+
return response;
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ImportSessionResponse, SkhemaClient } from '@skhema/sdk';
|
|
2
|
+
/** Resolve the backend-supported import MIME for a local file. */
|
|
3
|
+
export declare function importMimeFor(filePath: string): string;
|
|
4
|
+
/**
|
|
5
|
+
* Start a file import, stream the bytes to its signed URL, and confirm upload.
|
|
6
|
+
* The file is never buffered in memory.
|
|
7
|
+
*/
|
|
8
|
+
export declare function startFileImport(options: {
|
|
9
|
+
client: SkhemaClient;
|
|
10
|
+
filePath: string;
|
|
11
|
+
organizationId?: string;
|
|
12
|
+
fetchImpl?: typeof fetch;
|
|
13
|
+
}): Promise<ImportSessionResponse>;
|
|
14
|
+
/** Poll the canonical session until it reaches a reviewable or terminal state. */
|
|
15
|
+
export declare function waitForImport(options: {
|
|
16
|
+
client: SkhemaClient;
|
|
17
|
+
sessionId: string;
|
|
18
|
+
intervalMs?: number;
|
|
19
|
+
maxWaitMs?: number;
|
|
20
|
+
sleep?: (ms: number) => Promise<void>;
|
|
21
|
+
}): Promise<ImportSessionResponse>;
|
|
22
|
+
//# sourceMappingURL=import-transfer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"import-transfer.d.ts","sourceRoot":"","sources":["../../../src/lib/api/import-transfer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAgBtE,kEAAkE;AAClE,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAStD;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE;IAC7C,MAAM,EAAE,YAAY,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAA;IAChB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;CACzB,GAAG,OAAO,CAAC,qBAAqB,CAAC,CA4CjC;AAUD,kFAAkF;AAClF,wBAAsB,aAAa,CAAC,OAAO,EAAE;IAC3C,MAAM,EAAE,YAAY,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CACtC,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAkBjC"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { Readable } from 'stream';
|
|
4
|
+
import { UsageError } from './run-command.js';
|
|
5
|
+
const IMPORT_MIME_BY_EXTENSION = {
|
|
6
|
+
pdf: 'application/pdf',
|
|
7
|
+
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
8
|
+
md: 'text/markdown',
|
|
9
|
+
txt: 'text/plain',
|
|
10
|
+
csv: 'text/csv',
|
|
11
|
+
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
12
|
+
json: 'application/json',
|
|
13
|
+
};
|
|
14
|
+
/** Resolve the backend-supported import MIME for a local file. */
|
|
15
|
+
export function importMimeFor(filePath) {
|
|
16
|
+
const extension = path.extname(filePath).slice(1).toLowerCase();
|
|
17
|
+
const mime = IMPORT_MIME_BY_EXTENSION[extension];
|
|
18
|
+
if (!mime) {
|
|
19
|
+
throw new UsageError('Unsupported import file. Use PDF, DOCX, Markdown, text, CSV, XLSX, or JSON.');
|
|
20
|
+
}
|
|
21
|
+
return mime;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Start a file import, stream the bytes to its signed URL, and confirm upload.
|
|
25
|
+
* The file is never buffered in memory.
|
|
26
|
+
*/
|
|
27
|
+
export async function startFileImport(options) {
|
|
28
|
+
let sizeBytes;
|
|
29
|
+
try {
|
|
30
|
+
const stat = fs.statSync(options.filePath);
|
|
31
|
+
if (!stat.isFile())
|
|
32
|
+
throw new Error('not a file');
|
|
33
|
+
sizeBytes = stat.size;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
throw new UsageError(`Cannot read import file: ${options.filePath}`);
|
|
37
|
+
}
|
|
38
|
+
const filename = path.basename(options.filePath);
|
|
39
|
+
const mime = importMimeFor(filename);
|
|
40
|
+
const created = await options.client.imports.create({
|
|
41
|
+
sourceType: 'file',
|
|
42
|
+
...(options.organizationId
|
|
43
|
+
? { organizationId: options.organizationId }
|
|
44
|
+
: {}),
|
|
45
|
+
filename,
|
|
46
|
+
mime,
|
|
47
|
+
sizeBytes,
|
|
48
|
+
});
|
|
49
|
+
if (!created.uploadUrl) {
|
|
50
|
+
throw new Error('The import service did not return an upload URL.');
|
|
51
|
+
}
|
|
52
|
+
const response = await (options.fetchImpl ?? globalThis.fetch)(created.uploadUrl, {
|
|
53
|
+
method: 'PUT',
|
|
54
|
+
headers: {
|
|
55
|
+
'Content-Type': mime,
|
|
56
|
+
'Content-Length': String(sizeBytes),
|
|
57
|
+
},
|
|
58
|
+
body: Readable.toWeb(fs.createReadStream(options.filePath)),
|
|
59
|
+
duplex: 'half',
|
|
60
|
+
});
|
|
61
|
+
if (!response.ok) {
|
|
62
|
+
throw new Error(`Uploading import file failed (HTTP ${response.status}).`);
|
|
63
|
+
}
|
|
64
|
+
return options.client.imports.confirmUpload(created.session.id);
|
|
65
|
+
}
|
|
66
|
+
const TERMINAL_IMPORT_STATUSES = new Set([
|
|
67
|
+
'proposed',
|
|
68
|
+
'applied',
|
|
69
|
+
'partially_applied',
|
|
70
|
+
'failed',
|
|
71
|
+
'discarded',
|
|
72
|
+
]);
|
|
73
|
+
/** Poll the canonical session until it reaches a reviewable or terminal state. */
|
|
74
|
+
export async function waitForImport(options) {
|
|
75
|
+
const intervalMs = options.intervalMs ?? 2000;
|
|
76
|
+
const maxWaitMs = options.maxWaitMs ?? 30 * 60000;
|
|
77
|
+
const sleep = options.sleep ??
|
|
78
|
+
((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
79
|
+
const startedAt = Date.now();
|
|
80
|
+
while (true) {
|
|
81
|
+
const response = await options.client.imports.get(options.sessionId);
|
|
82
|
+
if (TERMINAL_IMPORT_STATUSES.has(response.session.status))
|
|
83
|
+
return response;
|
|
84
|
+
if (Date.now() - startedAt >= maxWaitMs) {
|
|
85
|
+
throw new Error(`Timed out waiting for import ${options.sessionId}; inspect it with "skhema import get ${options.sessionId}".`);
|
|
86
|
+
}
|
|
87
|
+
await sleep(intervalMs);
|
|
88
|
+
}
|
|
89
|
+
}
|
package/dist/program.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"program.d.ts","sourceRoot":"","sources":["../src/program.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"program.d.ts","sourceRoot":"","sources":["../src/program.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAqBnC;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAiDrD"}
|
package/dist/program.js
CHANGED
|
@@ -7,6 +7,7 @@ import { registerContributeCommands } from './commands/contribute.js';
|
|
|
7
7
|
import { registerDocumentCommands } from './commands/document.js';
|
|
8
8
|
import { registerElementCommands } from './commands/element.js';
|
|
9
9
|
import { registerWorkspaceExportCommand } from './commands/export.js';
|
|
10
|
+
import { registerImportCommands } from './commands/import.js';
|
|
10
11
|
import { registerInitCommand } from './commands/init.js';
|
|
11
12
|
import { registerLinkCommands } from './commands/link.js';
|
|
12
13
|
import { registerResourceCommands } from './commands/resource.js';
|
|
@@ -47,6 +48,7 @@ export function buildProgram(version) {
|
|
|
47
48
|
program.help();
|
|
48
49
|
});
|
|
49
50
|
registerInitCommand(program);
|
|
51
|
+
registerImportCommands(program);
|
|
50
52
|
registerAuthCommands(program);
|
|
51
53
|
registerWorkspaceCommands(program);
|
|
52
54
|
registerElementCommands(program);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skhema/cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.6",
|
|
4
4
|
"description": "Skhema CLI - Authentication and AI skills management for agent platforms",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -43,14 +43,14 @@
|
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@skhema/agent-sdk": "0.1.4",
|
|
45
45
|
"@skhema/method": "0.3.0",
|
|
46
|
-
"@skhema/sdk": "0.2.
|
|
46
|
+
"@skhema/sdk": "0.2.3",
|
|
47
47
|
"chalk": "^5.3.0",
|
|
48
48
|
"commander": "^12.0.0",
|
|
49
49
|
"ora": "^8.0.0"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@mdx-js/mdx": "3.1.1",
|
|
53
|
-
"@skhema/linter": "2.
|
|
53
|
+
"@skhema/linter": "2.3.0",
|
|
54
54
|
"@skhema/prettier-config": "1.0.0",
|
|
55
55
|
"@types/node": "^22.0.0",
|
|
56
56
|
"eslint": "^9.0.0",
|
package/skills/.manifest.json
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
{
|
|
2
|
-
"syncedAt": "2026-07-
|
|
2
|
+
"syncedAt": "2026-07-09T17:15:20.908Z",
|
|
3
3
|
"source": "/Users/michaellovell/Desktop/GitHub/skhema-skills",
|
|
4
|
-
"sourceSha": "
|
|
4
|
+
"sourceSha": "2a3abbb24f2fb2539608102eabc90de10fc9984b",
|
|
5
5
|
"skills": [
|
|
6
|
-
"skhema-
|
|
7
|
-
"skhema-
|
|
8
|
-
"skhema-
|
|
9
|
-
"skhema-
|
|
10
|
-
"skhema-
|
|
11
|
-
"skhema-
|
|
12
|
-
"skhema-strategy-advisor",
|
|
13
|
-
"skhema-workspace-navigator"
|
|
6
|
+
"skhema-frame",
|
|
7
|
+
"skhema-compose",
|
|
8
|
+
"skhema-calibrate",
|
|
9
|
+
"skhema-pressure-test",
|
|
10
|
+
"skhema-communicate",
|
|
11
|
+
"skhema-operate"
|
|
14
12
|
]
|
|
15
13
|
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: skhema-calibrate
|
|
3
|
+
description: "Use when you already have a strategy (or a piece of one) written down and need to check it still holds up — spot internal contradictions, claims resting on nothing, words that mean different things to different readers, or judgments the world has since overtaken. A self-check, not an attack; for an adversarial interrogation, use skhema-pressure-test."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Calibrate
|
|
7
|
+
|
|
8
|
+
You have something written. A strategy, a plan, a decision memo, a page of it.
|
|
9
|
+
This skill checks whether it is still sound and still current. Not to tear it
|
|
10
|
+
down — to true it up, like checking a level before you build on it.
|
|
11
|
+
|
|
12
|
+
Calibration is a self-check you run WITH the person on their own work. You read
|
|
13
|
+
what they wrote, you point at specific lines, you ask specific questions. The
|
|
14
|
+
output is a short list of concrete problems, each tied to a place in the text,
|
|
15
|
+
each with a fix or a question that surfaces the fix.
|
|
16
|
+
|
|
17
|
+
This is not brainstorming and not interrogation. If the person wants their
|
|
18
|
+
strategy attacked from the outside — a devil's-advocate grilling, a pre-mortem —
|
|
19
|
+
that is **skhema-pressure-test**, a different skill. Calibrate stays inside the
|
|
20
|
+
document and asks: does this agree with itself, does it rest on real evidence,
|
|
21
|
+
does it say one clear thing, and is it still true today?
|
|
22
|
+
|
|
23
|
+
## The four checks
|
|
24
|
+
|
|
25
|
+
Run whichever the situation calls for. On a full review, run all four in this
|
|
26
|
+
order — each surfaces different faults, and later checks are cleaner once the
|
|
27
|
+
earlier ones are fixed.
|
|
28
|
+
|
|
29
|
+
| Check | You are hunting for | Route to |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| Coherence | Two parts that disagree, a tension nobody named, a decision with no follow-through | `references/coherence.md` |
|
|
32
|
+
| Assumptions | Claims stated as fact that are really beliefs; confidence that outruns the evidence | `references/assumptions.md` |
|
|
33
|
+
| Language | Words vague enough that two readers walk away believing different things | `references/language.md` |
|
|
34
|
+
| Freshness | Judgments made under conditions that no longer hold; facts that have gone stale | `references/freshness.md` |
|
|
35
|
+
|
|
36
|
+
## How to pick
|
|
37
|
+
|
|
38
|
+
- "Does this hang together?" / "something feels off but I can't say what" /
|
|
39
|
+
"we decided X here and Y there" → **coherence**.
|
|
40
|
+
- "How sure are we?" / "is this actually true or do we just think so?" /
|
|
41
|
+
before committing real money or people → **assumptions**.
|
|
42
|
+
- "Everyone read this and nodded but we're doing different things" /
|
|
43
|
+
"what does 'scale' even mean here" → **language**.
|
|
44
|
+
- "This was written months ago" / "the market moved" / "is this still our
|
|
45
|
+
situation?" → **freshness**.
|
|
46
|
+
|
|
47
|
+
If they just say "review this" or "is this any good?", run all four. Announce
|
|
48
|
+
which you're doing as you go so they can follow.
|
|
49
|
+
|
|
50
|
+
## One habit that runs through all four
|
|
51
|
+
|
|
52
|
+
Every line in a strategy is doing one of three jobs. Before you judge a line,
|
|
53
|
+
know which:
|
|
54
|
+
|
|
55
|
+
- **A claim about the world** — something believed true. "Enterprise buyers
|
|
56
|
+
won't self-serve." These get the assumptions check: what's the evidence?
|
|
57
|
+
- **A command** — something decided to do. "Ship the free tier first." These
|
|
58
|
+
get the coherence check: does the rest of the plan act on it?
|
|
59
|
+
- **A named thing** — a scope, an outcome, a solution. "The onboarding flow."
|
|
60
|
+
These get the language check: would two readers point at the same thing?
|
|
61
|
+
|
|
62
|
+
Most calibration faults are one of these three lines wearing another's clothes:
|
|
63
|
+
a belief written as if it were a decided fact, a hope written as if it were a
|
|
64
|
+
measurable outcome. Naming the job first makes the fault obvious.
|
|
65
|
+
|
|
66
|
+
## What good output looks like
|
|
67
|
+
|
|
68
|
+
Not "the strategy has some inconsistencies." Instead: a numbered list where
|
|
69
|
+
each item quotes the exact line, names the fault, and gives the fix or the
|
|
70
|
+
question that gets there. Three sharp findings the person acts on beat twenty
|
|
71
|
+
vague ones they ignore. If a section is clean, say so and move on — do not
|
|
72
|
+
manufacture problems to look thorough.
|
|
73
|
+
|
|
74
|
+
## Without a live respondent
|
|
75
|
+
|
|
76
|
+
Run headless, with no one to answer? Don't invent their answers. Make the
|
|
77
|
+
minimal reasonable assumption, mark it in the output as yours ("assumed: …"),
|
|
78
|
+
and list the questions only the owner can settle — what's in scope, what a word
|
|
79
|
+
means, whose dissent to record — at the end instead of papering over them. A
|
|
80
|
+
marked assumption is honest work; a silent one is a guess you built on.
|
|
81
|
+
|
|
82
|
+
## Sharpen with Skhema
|
|
83
|
+
|
|
84
|
+
These checks work on any document, in any editor, with nothing installed. If you
|
|
85
|
+
want them automated — every claim typed by its job (world-claim vs command vs
|
|
86
|
+
thing), contradictions flagged as you write, staleness dated — the Skhema method
|
|
87
|
+
gives each strategy element a typed structure the checks run against. Start with
|
|
88
|
+
`npm i @skhema/method` for the vocabulary and validators, or run them in a Skhema
|
|
89
|
+
workspace where the four checks are wired into the element itself. Optional: the
|
|
90
|
+
skill is complete without it.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Assumptions
|
|
2
|
+
|
|
3
|
+
Every strategy rests on things believed true. Some are backed by evidence. Some
|
|
4
|
+
are guesses wearing the clothes of fact. This check separates them, so nobody
|
|
5
|
+
bets real money or people on a sentence that only sounds certain.
|
|
6
|
+
|
|
7
|
+
The core move: take each claim about the world and ask one blunt question —
|
|
8
|
+
**what is the evidence?** A claim with good evidence is a fact you can lean on. A
|
|
9
|
+
claim with none is an assumption, and it gets labelled as one. That is the whole
|
|
10
|
+
job: type every claim, then look at what you're standing on.
|
|
11
|
+
|
|
12
|
+
## Find the claims
|
|
13
|
+
|
|
14
|
+
Walk the strategy and pull every line that states something about the world as
|
|
15
|
+
true. These are the "claims about the world" — the beliefs, not the decisions or
|
|
16
|
+
the named things. Signs of a claim: "customers want", "the market is",
|
|
17
|
+
"competitors can't", "this will grow", "nobody else", "it's too expensive to".
|
|
18
|
+
|
|
19
|
+
Skip the commands ("ship X") and the labels ("the growth team") — those get
|
|
20
|
+
other checks. You want the load-bearing beliefs, because those are what the
|
|
21
|
+
decisions rest on.
|
|
22
|
+
|
|
23
|
+
## Type each claim
|
|
24
|
+
|
|
25
|
+
For every claim, ask for the evidence behind it, then sort it into one bucket:
|
|
26
|
+
|
|
27
|
+
- **Fact** — direct evidence you can point at. Data, a documented result, a
|
|
28
|
+
signed contract, a thing you measured. "Churn is 4% monthly" and here's the
|
|
29
|
+
dashboard.
|
|
30
|
+
- **Assumption** — believed, not shown. No evidence, or only "it stands to
|
|
31
|
+
reason." "Enterprise buyers won't self-serve." Maybe true. Not demonstrated.
|
|
32
|
+
- **Inference** — reasoned from a fact, but a step removed. "Churn is 4%, so
|
|
33
|
+
buyers don't see the value." The 4% is a fact; the *why* is a guess dressed as
|
|
34
|
+
a conclusion. Flag the guess.
|
|
35
|
+
|
|
36
|
+
The forcing question for each: *if someone asked "how do we know?", what would
|
|
37
|
+
you show them?* If the honest answer is "we don't, we think so" — it's an
|
|
38
|
+
assumption. Label it. There is no shame in an assumption; there is danger in a
|
|
39
|
+
mislabelled one.
|
|
40
|
+
|
|
41
|
+
## Rate the confidence, and check it earns its weight
|
|
42
|
+
|
|
43
|
+
Two things matter about each claim, and people conflate them:
|
|
44
|
+
|
|
45
|
+
- **How sure are we?** High / medium / low, based on evidence quality.
|
|
46
|
+
- **How much rests on it?** If this claim is wrong, how much of the strategy
|
|
47
|
+
falls?
|
|
48
|
+
|
|
49
|
+
Plot them against each other. The claims that matter are **load-bearing and
|
|
50
|
+
low-confidence** — high stakes, weak evidence. Those are the ones to name out
|
|
51
|
+
loud and, ideally, turn into something testable before committing. A shaky
|
|
52
|
+
assumption nothing depends on can stay shaky. A shaky assumption the whole plan
|
|
53
|
+
stands on is the real finding of this whole exercise.
|
|
54
|
+
|
|
55
|
+
Also watch for **confidence that outruns the evidence**: language like
|
|
56
|
+
"obviously", "clearly", "everyone knows", "of course". Certainty in the wording
|
|
57
|
+
is not certainty in the world. The louder the claim, the harder you check it.
|
|
58
|
+
|
|
59
|
+
## Output
|
|
60
|
+
|
|
61
|
+
A table or numbered list, one row per claim: the claim quoted, its type (fact /
|
|
62
|
+
assumption / inference), confidence (high / med / low), and how load-bearing it
|
|
63
|
+
is. Then a short verdict that pulls out the two or three load-bearing,
|
|
64
|
+
low-confidence claims and says plainly: *these are what the strategy is really
|
|
65
|
+
betting on; here is what would raise our confidence.*
|
|
66
|
+
|
|
67
|
+
## Failure modes to avoid
|
|
68
|
+
|
|
69
|
+
- **Letting "obviously" pass.** The words that most need checking are the ones
|
|
70
|
+
that sound like they don't.
|
|
71
|
+
- **Rating confidence without asking for evidence.** Confidence is an output of
|
|
72
|
+
evidence, not a vibe. Always ask "how do we know?" first.
|
|
73
|
+
- **Treating every assumption as a problem.** The goal is to type them, not
|
|
74
|
+
eliminate them. Strategy runs on assumptions. The point is knowing which ones,
|
|
75
|
+
and which ones are load-bearing.
|
|
76
|
+
- **Missing the inferences.** These are the sneakiest — a real fact with a guess
|
|
77
|
+
bolted on, presented as one solid claim. Split them.
|