@dzhechkov/harness-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/.dz-manifest.json +2 -2
- package/README.md +135 -32
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +83 -16
- package/dist/cli.js.map +1 -1
- package/package.json +10 -10
- package/sbom.json +1 -1
- package/src/cli.ts +88 -15
- package/LICENSE +0 -21
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dzhechkov/harness-cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.6",
|
|
4
4
|
"description": "The dz CLI — install AI skills for Claude Code, Codex, OpenCode, Hermes, OpenClaude, GitHub Copilot. 35 commands, 13 presets, 6 platform targets.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -40,6 +40,13 @@
|
|
|
40
40
|
".dz-manifest.json",
|
|
41
41
|
"sbom.json"
|
|
42
42
|
],
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsc -p tsconfig.json",
|
|
45
|
+
"test": "vitest run",
|
|
46
|
+
"test:watch": "vitest",
|
|
47
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
48
|
+
"lint": "tsc -p tsconfig.json --noEmit"
|
|
49
|
+
},
|
|
43
50
|
"dependencies": {
|
|
44
51
|
"@dzhechkov/harness-core": "^0.4.0",
|
|
45
52
|
"@dzhechkov/harness-presets": "^0.5.0",
|
|
@@ -75,12 +82,5 @@
|
|
|
75
82
|
"url": "https://github.com/djd1m/dz-harness-hub.git",
|
|
76
83
|
"directory": "packages/@dzhechkov/harness-cli"
|
|
77
84
|
},
|
|
78
|
-
"homepage": "https://github.com/djd1m/dz-harness-hub/tree/main/packages/@dzhechkov/harness-cli#readme"
|
|
79
|
-
|
|
80
|
-
"build": "tsc -p tsconfig.json",
|
|
81
|
-
"test": "vitest run",
|
|
82
|
-
"test:watch": "vitest",
|
|
83
|
-
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
84
|
-
"lint": "tsc -p tsconfig.json --noEmit"
|
|
85
|
-
}
|
|
86
|
-
}
|
|
85
|
+
"homepage": "https://github.com/djd1m/dz-harness-hub/tree/main/packages/@dzhechkov/harness-cli#readme"
|
|
86
|
+
}
|
package/sbom.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -242,6 +242,7 @@ import {
|
|
|
242
242
|
isUsablePlaneResult,
|
|
243
243
|
renderDeliveryReview,
|
|
244
244
|
scanSkillsLayout,
|
|
245
|
+
declaredPluginSurface,
|
|
245
246
|
parseInitFacts,
|
|
246
247
|
verifyRegistration,
|
|
247
248
|
buildContentProbePrompt,
|
|
@@ -449,6 +450,8 @@ Usage:
|
|
|
449
450
|
dz import-ecc [--local-path <dir>] [--select id,id,...] [--limit N] [--output <dir>] [--force]
|
|
450
451
|
dz help
|
|
451
452
|
|
|
453
|
+
Global: --version | -v [--json] (prints this CLI's own semver on one line, exit 0; "unknown" + exit 1 when unresolvable)
|
|
454
|
+
|
|
452
455
|
Workflows: author loop-plan/1 plans with dz workflow init/validate/render; gate them with dz workflow-lint; read runs with dz workflow-trace (the ADR-005 templates are retired)
|
|
453
456
|
|
|
454
457
|
Targets: ${TARGET_NAMES.join(', ')}
|
|
@@ -935,8 +938,8 @@ function workflowInitPlan(name: string, pattern: string): object {
|
|
|
935
938
|
...base,
|
|
936
939
|
steps: [
|
|
937
940
|
{ stepId: 'fan', kind: 'fanout', phase: 'Work', concurrency: 'pipeline', budget: { maxAgents: 8 } },
|
|
938
|
-
{ stepId: 'a', kind: 'agent', phase: 'Work', prompt: 'TODO: stage A per item', budget: { maxAgents: 4 } }, // no-stubs: workflow-init scaffold sentinel the author replaces (deliberate authoring cue, not unfinished code)
|
|
939
|
-
{ stepId: 'b', kind: 'agent', phase: 'Work', prompt: 'TODO: stage B per item', budget: { maxAgents: 4 } }, // no-stubs: workflow-init scaffold sentinel the author replaces (deliberate authoring cue, not unfinished code)
|
|
941
|
+
{ stepId: 'a', kind: 'agent', phase: 'Work', prompt: 'TODO: stage A per item', budget: { maxAgents: 4 }, tools: [] }, // no-stubs: workflow-init scaffold sentinel the author replaces (deliberate authoring cue, not unfinished code)
|
|
942
|
+
{ stepId: 'b', kind: 'agent', phase: 'Work', prompt: 'TODO: stage B per item', budget: { maxAgents: 4 }, tools: [] }, // no-stubs: workflow-init scaffold sentinel the author replaces (deliberate authoring cue, not unfinished code)
|
|
940
943
|
{ stepId: 'jn', kind: 'join', phase: 'Work', deps: ['fan'] },
|
|
941
944
|
],
|
|
942
945
|
fanouts: [{ stage: 'fan', registry: ['item1', 'item2', 'item3'], maxFanout: 3, chain: ['a', 'b'] }],
|
|
@@ -948,10 +951,10 @@ function workflowInitPlan(name: string, pattern: string): object {
|
|
|
948
951
|
...base,
|
|
949
952
|
steps: [
|
|
950
953
|
{ stepId: 'fan', kind: 'fanout', phase: 'Lanes', concurrency: 'barrier', budget: { maxAgents: 6 } },
|
|
951
|
-
{ stepId: 'lane', kind: 'agent', phase: 'Lanes', prompt: 'TODO: one lane', budget: { maxAgents: 6 } }, // no-stubs: workflow-init scaffold sentinel the author replaces (deliberate authoring cue, not unfinished code)
|
|
954
|
+
{ stepId: 'lane', kind: 'agent', phase: 'Lanes', prompt: 'TODO: one lane', budget: { maxAgents: 6 }, tools: [] }, // no-stubs: workflow-init scaffold sentinel the author replaces (deliberate authoring cue, not unfinished code)
|
|
952
955
|
{ stepId: 'jn', kind: 'join', phase: 'Lanes', deps: ['fan'] },
|
|
953
956
|
// the consumer hangs off the BARRIER (jn), never the fork — barrier-postdominates teaches this
|
|
954
|
-
{ stepId: 'synthesize', kind: 'agent', phase: 'Synthesize', deps: ['jn'], prompt: 'TODO: synthesize across lanes', budget: { maxAgents: 1 } }, // no-stubs: workflow-init scaffold sentinel the author replaces (deliberate authoring cue, not unfinished code)
|
|
957
|
+
{ stepId: 'synthesize', kind: 'agent', phase: 'Synthesize', deps: ['jn'], prompt: 'TODO: synthesize across lanes', budget: { maxAgents: 1 }, tools: [] }, // no-stubs: workflow-init scaffold sentinel the author replaces (deliberate authoring cue, not unfinished code)
|
|
955
958
|
],
|
|
956
959
|
fanouts: [{ stage: 'fan', registry: ['lane1', 'lane2', 'lane3'], maxFanout: 3, chain: ['lane'] }],
|
|
957
960
|
joins: [{ stage: 'jn', forStage: 'fan', joinPolicy: 'all-activated', onInvalid: 'named-failure' }],
|
|
@@ -961,8 +964,8 @@ function workflowInitPlan(name: string, pattern: string): object {
|
|
|
961
964
|
return {
|
|
962
965
|
...base,
|
|
963
966
|
steps: [
|
|
964
|
-
{ stepId: 'work', kind: 'agent', phase: 'Work', prompt: 'TODO: produce the artifact', budget: { maxAgents: 2 } }, // no-stubs: workflow-init scaffold sentinel the author replaces (deliberate authoring cue, not unfinished code)
|
|
965
|
-
{ stepId: 'gate', kind: 'gate', phase: 'Gate', deps: ['work'], prompt: 'TODO: gate check (parse the verdict, never synthesize one)', budget: { maxAgents: 1 } }, // no-stubs: workflow-init scaffold sentinel the author replaces (deliberate authoring cue, not unfinished code)
|
|
967
|
+
{ stepId: 'work', kind: 'agent', phase: 'Work', prompt: 'TODO: produce the artifact', budget: { maxAgents: 2 }, tools: [] }, // no-stubs: workflow-init scaffold sentinel the author replaces (deliberate authoring cue, not unfinished code)
|
|
968
|
+
{ stepId: 'gate', kind: 'gate', phase: 'Gate', deps: ['work'], prompt: 'TODO: gate check (parse the verdict, never synthesize one)', budget: { maxAgents: 1 }, tools: [] }, // no-stubs: workflow-init scaffold sentinel the author replaces (deliberate authoring cue, not unfinished code)
|
|
966
969
|
],
|
|
967
970
|
gates: [{ stepId: 'gate', kind: 'parse-verdict', failRoute: 'work', maxRedos: 1 }],
|
|
968
971
|
};
|
|
@@ -970,7 +973,7 @@ function workflowInitPlan(name: string, pattern: string): object {
|
|
|
970
973
|
// minimal default: one agent step
|
|
971
974
|
return {
|
|
972
975
|
...base,
|
|
973
|
-
steps: [{ stepId: 'main', kind: 'agent', phase: 'Work', prompt: 'TODO: the one step', budget: { maxAgents: 1 } }], // no-stubs: workflow-init scaffold sentinel the author replaces (deliberate authoring cue, not unfinished code)
|
|
976
|
+
steps: [{ stepId: 'main', kind: 'agent', phase: 'Work', prompt: 'TODO: the one step', budget: { maxAgents: 1 }, tools: [] }], // no-stubs: workflow-init scaffold sentinel the author replaces (deliberate authoring cue, not unfinished code)
|
|
974
977
|
};
|
|
975
978
|
}
|
|
976
979
|
|
|
@@ -1161,8 +1164,12 @@ function cmdWorkflowLint(options: Map<string, string>, flags: Set<string>, cwd:
|
|
|
1161
1164
|
/** `dz workflow-trace` — timeline + invariant runner over a run's trace.jsonl. Scope is CAPPED
|
|
1162
1165
|
* (AM-8): <runDir|--slug|--run>, --invariants, --html, --json. NO watch/filter/compare/search/
|
|
1163
1166
|
* retention/access-control — adding one needs an ADR amendment (the surface test pins this). */
|
|
1164
|
-
/**
|
|
1165
|
-
|
|
1167
|
+
/**
|
|
1168
|
+
* This CLI's OWN semver, read from its package.json. `unknown` is honest; a throw is not, and an
|
|
1169
|
+
* invented number is worse than either — a downstream version guard that is handed a fabricated
|
|
1170
|
+
* version happily calls a binary it should have refused.
|
|
1171
|
+
*/
|
|
1172
|
+
function dzOwnVersion(): string {
|
|
1166
1173
|
try {
|
|
1167
1174
|
const req = createRequire(import.meta.url);
|
|
1168
1175
|
const pkg = req('../package.json') as { version?: unknown };
|
|
@@ -1170,6 +1177,11 @@ function traceBundleToolVersion(): string {
|
|
|
1170
1177
|
} catch { return 'unknown'; }
|
|
1171
1178
|
}
|
|
1172
1179
|
|
|
1180
|
+
/** The tool version stamped into a bundle's provenance. Unknown is honest; a throw is not. */
|
|
1181
|
+
function traceBundleToolVersion(): string {
|
|
1182
|
+
return dzOwnVersion();
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1173
1185
|
/** Resolve a run the SAME three ways the timeline reader does — positional, --slug, --run — so a
|
|
1174
1186
|
* bundle can never address a run by a scheme the rest of the command does not understand. */
|
|
1175
1187
|
function resolveTraceRun(options: Map<string, string>, cwd: string, positional: string | undefined):
|
|
@@ -8151,7 +8163,11 @@ const PROBE_SCRUB_ENV = [
|
|
|
8151
8163
|
* lands, then kill it — the model never answers, so the probe costs ~no tokens. Never throws:
|
|
8152
8164
|
* every failure becomes an `error` string, which the pure classifier turns into `inconclusive`.
|
|
8153
8165
|
*/
|
|
8154
|
-
function probeInitStream(
|
|
8166
|
+
function probeInitStream(
|
|
8167
|
+
projectDir: string,
|
|
8168
|
+
timeoutMs: number,
|
|
8169
|
+
pluginDir: string | null = null,
|
|
8170
|
+
): Promise<{ stream: string; error: string | null }> {
|
|
8155
8171
|
return new Promise((resolveProbe) => {
|
|
8156
8172
|
const env: NodeJS.ProcessEnv = { ...process.env };
|
|
8157
8173
|
for (const key of PROBE_SCRUB_ENV) delete env[key];
|
|
@@ -8181,7 +8197,12 @@ function probeInitStream(projectDir: string, timeoutMs: number): Promise<{ strea
|
|
|
8181
8197
|
|
|
8182
8198
|
try {
|
|
8183
8199
|
// NOT `--bare`: that mode skips plugin credentials and fails with "Not logged in".
|
|
8184
|
-
|
|
8200
|
+
const args = ['-p', 'ok', '--output-format', 'stream-json', '--verbose'];
|
|
8201
|
+
// Session-scoped plugin load — the marketplace-free vehicle (ADR-003 D-3). Without this the
|
|
8202
|
+
// probe reads a session in which the plugin was never loaded, and reports its commands
|
|
8203
|
+
// missing for a reason that has nothing to do with the package under test.
|
|
8204
|
+
if (pluginDir !== null) args.push('--plugin-dir', pluginDir);
|
|
8205
|
+
child = spawn('claude', args, {
|
|
8185
8206
|
cwd: projectDir,
|
|
8186
8207
|
env,
|
|
8187
8208
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
@@ -8255,8 +8276,11 @@ async function cmdSkillsVerify(
|
|
|
8255
8276
|
): Promise<number> {
|
|
8256
8277
|
const json = flags.has('json');
|
|
8257
8278
|
if (flags.has('help')) {
|
|
8258
|
-
write('dz skills-verify [--dir <project>] [--expect a,b] [--static] [--strict] [--timeout <s>] [--json]');
|
|
8279
|
+
write('dz skills-verify [--dir <project>] [--expect a,b] [--expect-commands a,b] [--plugin-dir <dir>] [--static] [--strict] [--timeout <s>] [--json]');
|
|
8259
8280
|
write(' Verifies that a project\'s .claude/skills/ actually register in Claude Code.');
|
|
8281
|
+
write(' --plugin-dir <dir> load a plugin into the probe session (session-scoped, no marketplace);');
|
|
8282
|
+
write(' with no --expect-commands, the expectation defaults to the manifest\'s own commands[]');
|
|
8283
|
+
write(' --expect-commands slash commands that MUST appear in the session listing, e.g. loop-designer:init');
|
|
8260
8284
|
write(' --static layout scan only (no Claude session, CI-safe): flags dirs that can never register');
|
|
8261
8285
|
write(' --live-content ADVISORY extra turn: ask a live model to name the skills and quote one, proving');
|
|
8262
8286
|
write(' the CONTENT is usable — registration is not usability. Never changes the exit code.');
|
|
@@ -8266,8 +8290,9 @@ async function cmdSkillsVerify(
|
|
|
8266
8290
|
}
|
|
8267
8291
|
|
|
8268
8292
|
const allowedFlags = new Set(['json', 'help', 'static', 'strict', 'live-content']);
|
|
8269
|
-
const allowedOptions = new Set(['dir', 'expect', 'timeout']);
|
|
8270
|
-
const usage =
|
|
8293
|
+
const allowedOptions = new Set(['dir', 'expect', 'expect-commands', 'plugin-dir', 'timeout']);
|
|
8294
|
+
const usage =
|
|
8295
|
+
' allowed: --dir <project>, --expect a,b, --expect-commands a,b, --plugin-dir <dir>, --timeout <s>, --static, --strict, --live-content, --json';
|
|
8271
8296
|
if (options.has('_positional_0')) {
|
|
8272
8297
|
const message = `unexpected argument "${options.get('_positional_0')}"`;
|
|
8273
8298
|
write(json ? JSON.stringify({ error: message, exitCode: 1 }) : `dz skills-verify: ${message}\n${usage}`);
|
|
@@ -8297,6 +8322,28 @@ async function cmdSkillsVerify(
|
|
|
8297
8322
|
.filter(Boolean)
|
|
8298
8323
|
: scan.registrable;
|
|
8299
8324
|
|
|
8325
|
+
// A plugin loaded with `--plugin-dir` is session-scoped, so its surface is NOT on the project's
|
|
8326
|
+
// disk and `scan.registrable` cannot describe it. The manifest can — and defaulting to the
|
|
8327
|
+
// manifest's own `commands[]` keeps the gate honest without a hand-typed list that silently
|
|
8328
|
+
// drifts from the manifest it is supposed to be checking. An UNREADABLE manifest is refused
|
|
8329
|
+
// rather than defaulted to an empty expectation: an empty expectation passes without checking.
|
|
8330
|
+
const pluginDir = options.has('plugin-dir') ? resolve(cwd, options.get('plugin-dir') ?? '') : null;
|
|
8331
|
+
let expectedCommands = options.has('expect-commands')
|
|
8332
|
+
? (options.get('expect-commands') ?? '')
|
|
8333
|
+
.split(',')
|
|
8334
|
+
.map((s) => s.trim())
|
|
8335
|
+
.filter(Boolean)
|
|
8336
|
+
: [];
|
|
8337
|
+
if (pluginDir !== null && !options.has('expect-commands')) {
|
|
8338
|
+
const surface = declaredPluginSurface(pluginDir);
|
|
8339
|
+
if (surface === null) {
|
|
8340
|
+
const message = `cannot read ${join(pluginDir, '.claude-plugin', 'plugin.json')} (or it declares no name) — pass --expect-commands explicitly`;
|
|
8341
|
+
write(json ? JSON.stringify({ error: message, exitCode: 1 }) : `dz skills-verify: ${message}`);
|
|
8342
|
+
return 1;
|
|
8343
|
+
}
|
|
8344
|
+
expectedCommands = surface.commands;
|
|
8345
|
+
}
|
|
8346
|
+
|
|
8300
8347
|
// ── L1 only: deterministic, no session, safe for CI ──
|
|
8301
8348
|
if (flags.has('static')) {
|
|
8302
8349
|
const exitCode = scan.findings.length > 0 ? 1 : 0;
|
|
@@ -8307,6 +8354,11 @@ async function cmdSkillsVerify(
|
|
|
8307
8354
|
if (!scan.exists) write(' no .claude/skills/ directory here');
|
|
8308
8355
|
for (const f of scan.findings) write(` [${f.kind}] ${f.detail}`);
|
|
8309
8356
|
write(scan.findings.length ? ` ${scan.findings.length} layout problem(s) — these can never register` : ' no layout problems found');
|
|
8357
|
+
// Advisories were collected but never PRINTED in static mode: a `.claude-plugin/plugin.json`
|
|
8358
|
+
// under `.claude/skills` produced "no layout problems found" and nothing else, so the one
|
|
8359
|
+
// shape most likely to be a silent non-registration was invisible in exactly the mode CI and
|
|
8360
|
+
// humans run most. Reported, still never fatal (that distinction is the whole point).
|
|
8361
|
+
for (const a of scan.advisories) write(` [${a.kind}] ADVISORY: ${a.detail}`);
|
|
8310
8362
|
write(' (static is a PROXY — run without --static to read the real registration listing)');
|
|
8311
8363
|
}
|
|
8312
8364
|
return exitCode;
|
|
@@ -8317,7 +8369,7 @@ async function cmdSkillsVerify(
|
|
|
8317
8369
|
const timeoutMs = Number.isFinite(timeoutSec) && timeoutSec > 0 ? timeoutSec * 1000 : 180_000;
|
|
8318
8370
|
if (!json) write(`dz skills-verify: starting a session in ${projectDir} to read the real registration listing…`);
|
|
8319
8371
|
|
|
8320
|
-
const { stream, error } = await probeInitStream(projectDir, timeoutMs);
|
|
8372
|
+
const { stream, error } = await probeInitStream(projectDir, timeoutMs, pluginDir);
|
|
8321
8373
|
|
|
8322
8374
|
// `init.skills` carries names, not provenance: a USER-level skill of the same name would satisfy
|
|
8323
8375
|
// the expectation while the project's own copy stays broken. Collect the collisions so the
|
|
@@ -8342,6 +8394,7 @@ async function cmdSkillsVerify(
|
|
|
8342
8394
|
// The provenance check RAN (that is what `checked: true` asserts) — see `ambiguous` above.
|
|
8343
8395
|
provenance: { checked: true, ambiguous },
|
|
8344
8396
|
...(options.has('expect') ? { expected } : {}),
|
|
8397
|
+
...(expectedCommands.length > 0 ? { expectedCommands } : {}),
|
|
8345
8398
|
},
|
|
8346
8399
|
{ resolvePath: canonical },
|
|
8347
8400
|
);
|
|
@@ -9295,6 +9348,26 @@ export async function runCli(argv: string[], io: CliIo = {}): Promise<number> {
|
|
|
9295
9348
|
};
|
|
9296
9349
|
const { command, options, optionLists, flags } = parseArgs(argv);
|
|
9297
9350
|
|
|
9351
|
+
// ── `dz --version` / `dz -v` / `dz version` — PRE-DISPATCH, before the help branch ──
|
|
9352
|
+
//
|
|
9353
|
+
// Until now `dz --version` printed the whole USAGE manual and exited 0 (MEASURED 2026-08-17,
|
|
9354
|
+
// reproducer `node dist/bin.js --version`). Exit 0 plus prose is the worst possible answer for a
|
|
9355
|
+
// caller that must decide whether a `dz` it found on PATH is safe to invoke: the status code says
|
|
9356
|
+
// "fine" and there is no number to parse. Any wrapper guarding a version range needs exactly one
|
|
9357
|
+
// parseable line. Recognised only as the FIRST token (or the `version` subcommand) so that a
|
|
9358
|
+
// later positional `-v` belonging to a subcommand keeps its own meaning.
|
|
9359
|
+
if (argv[0] === '--version' || argv[0] === '-v' || command === 'version') {
|
|
9360
|
+
const version = dzOwnVersion();
|
|
9361
|
+
if (flags.has('json')) {
|
|
9362
|
+
write(JSON.stringify({ name: 'dz', version, node: process.version, schemas: { loopPlan: 'loop-plan/1' } }));
|
|
9363
|
+
} else {
|
|
9364
|
+
write(version);
|
|
9365
|
+
}
|
|
9366
|
+
// An unresolvable version is a FAILURE, not a value: exiting 0 with the literal `unknown` would
|
|
9367
|
+
// let a guard treat "I could not tell you" as "I answered you".
|
|
9368
|
+
return version === 'unknown' ? 1 : 0;
|
|
9369
|
+
}
|
|
9370
|
+
|
|
9298
9371
|
if (command === '' || command === 'help' || flags.has('help')) {
|
|
9299
9372
|
write(USAGE);
|
|
9300
9373
|
return 0;
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 dzhechko
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|